I have a Jenkinsfile like this:
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '7'))
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
timestamps()
}
stages {
stage('Admin') {
steps {
script {
try {
result = "FAIL"
} catch(error) {
result = "FAIL"
}
}
}
}
stage('Normal') {
steps {
script {
try {
sh("echo 'hi'")
} catch(error) {
}
}
}
}
}
}
How do I fail the first step and have it show red and have the pipeline continue for more steps?
I have looked at all the SO answers for this and can't make it work.
Setting result = "FAIL" does not cause the step to fail. How can I do this but continue the next step(s)?
You can mark a build status as failed by setting a new value for the global jenkins variable currentBuild. The variable has 3 states: SUCCESS (green), FAILURE (red) and UNSTABLE (yellow). The catch should prevent the build from exiting so you can set the status of the currentBuild and simply continue.
try {
//so something
} catch(error) {
//mark build as failed
currentBuild.result = 'FAILURE'
}
}
I think this should work. Sadly I don't have a way to validate this atm. If it does not work, please tell me.
As per my knowledge we can implement same as below.
Use script step and try-catch block
stage('someStage') {
steps {
script {
try {
//Do something
} catch (err) {
echo err
}
}
echo currentBuild.result = 'FAILURE'
}
}
You can use propagate: false which is available in build step. Somethng like below
stage("example") {
b = build(job: "example-job", propagate: false).result
if(b == 'FAILURE') {
echo "First job failed"
currentBuild.result = 'FAILURE' // of FAILURE
}
}
stage("test") {
build("test-job")
}
Links :
https://jenkins.io/doc/pipeline/steps/pipeline-build-step/
https://medium.com/#Lenkovits/jenkins-pipelines-and-their-dirty-secrets-1-9e535cd603f4
Do you want to show one stage "red" and all other (not failing) "green"?
As far as I know, that is not possible.
Do you want to know which stage is failing and which one runs correctly?
Well, it depends how you want it to be displayed
The easy way:
as suggested in a previous answer, you can use "echo" to print it in the console
The 'I want it full power' way:
capsulate a try-catch inside another try-catch and use the "error" signal:
stages {
try {
stage ('Normal') {
try {
/* Do your thing */
} catch (err) {
/* Mark this stage as "failure" using error
error 'Stage Normal failing'
}
} // stage 'Normal'
} catch (err) {
// here is the 'error' signal captured and omitted by now
}
Stage failing appears with the text "failed" shown in the box and in a darker red
Related
This is a follow-up to my earlier question:
Set a stage status in Jenkins Pipelines
It turns out I can keep a pipeline as SUCCESS but can mark an individual stage as UNSTABLE if I want via catchError like this:
node()
{
stage("Stage 1")
{
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE')
{
sh 'exit 1'
}
}
}
If I want to get the current status of the pipeline itself, I can use currentBuild.getCurrentResult() but I don't see a currentStage analog to this.
I'm interested in trying out a pattern that might look something like this in my stages:
stage("1") {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
// do stuff
}
// perhaps more catchError() blocks
if(currentStage.getCurrentResult() == "UNSTABLE") {
// do something special if we're unstable
}
}
but that would fail because there's no currentStage available.
So basically, catchError() is nice but I'd like to know how I can catch the status change to my stage if it gets changed... Does anyone know how you access the status of the current stage you're in from a pipeline?
I did it like this (to keep the catchError):
def boolean test_results = false
pipeline {
...
stage( 'x' ) {
steps{
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
<Do some dangerous stuff here>
//
// If we reached here the above step hasn't failed
//
script { test_results = true }
}
}
}
stage( 'y' ) {
steps{
script{
if( test_results == true ) {
} else {
}
}
}
}
}
Though there is no direct method for accessing the result of a stage in a pipeline as of now, you can work around it. This is considering you are only interested in either SUCCESS or UNSTABLE stage results as per the question and not in FAILURE.
The workaround is to initialize an empty map at the top of your pipeline to store the result of each stage. Now, instead of the catchError() method, use the unstable() method in combination with a try-catch block. This is because the latter not only lets you set the result as unstable but also perform other operations such as add the result to the map in the except block. Then you can read this stored result from the map in your if statement.
Example
stageResults = [:]
...
stage("1") {
try {
// do stuff
// Add to map as SUCCESS on successful execution
stageResults."{STAGE_NAME}" = "SUCCESS"
} catch (Exception e) {
// Set the result and add to map as UNSTABLE on failure
unstable("[ERROR]: ${STAGE_NAME} failed!")
currentBuild.result = "SUCCESS"
stageResult."{STAGE_NAME}" = "UNSTABLE"
}
if(stageResults.find{ it.key == "{STAGE_NAME}" }?.value == "UNSTABLE") {
// do something special if we're unstable
}
}
As for me the most elegant way to do it, it's a post section.
In your example, you mark the stage as UNSTABLE so after that you can catch it using post->unstable.
stage("1") {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
error 'Something goes wrong'
}
}
post {
always { echo 'Executed on every build'}
unstable { echo 'Executed only if build is unstable (marked by catchError)'}
}
}
As an alternative to add to Dibakar Aditya's answer, it is possible to wrap everything in a function which resembles regular steps. I.e.:
stage("1") {
def localSuccess = catchLocalError {
// do stuff
}
if(!localSuccess) {
// do something special if we're unstable
}
}
boolean catchLocalError(Closure c) {
try {
c()
return true
} catch (Exception e) {
return false
}
}
I'd like a step to only be performed when the currentBuild.result is not set to UNSTABLE, this is immediately after a test step has been performed, I've managed to figure out that if this is not the case it gets set to null in my pipeline. Comparing a variable to "" should work when trying to determine that it is null, however this does not appear to work in my job step:
stage('Post test') {
when {
expression {
return (currentBuild.result == "")
}
}
steps {
Can someone please advise as to what I should be using in my conditional step expression.
Untested and not sure if this is exactly what you mean, but something like this?
stage('Post test') {
steps {
conditionalSteps {
condition {
status("Success","Success") # worst result, best result
}
steps {
shell("echo 'this is my command'")
}
}
}
}
If you want to run this at the end of a build you can just wrap it in a post. If you want to run a command while continuing the pipeline, I think you would have to wrap it in a script closure. This is untested, but I believe it will work for what you want:
stages {
stage('1') {}
stage('2') {}
stage('3') {
steps {
script {
if (currentBuild.result == "UNSTABLE") {
println "this should be unstable"
}
}
}
}
post {
unstable {
println "here be unstable"
}
}
}
I have a declarative Jenkins pipeline with stage1, stage2, stage3 and so on. I want to stop stage2 from running if stage1 sets the build unstable/fail.
I know I can stop the steps in stage1 from running using return when the build is not success but couldn't find a way where I can just exit the pipeline without running the stages below stage1
Here is what I have:
stage('stage1') {
steps {
script{
//somesteps
if ("${stdout}" == "1"){
currentBuild.result = 'UNSTABLE'
return
} //if
//somesteps
} //script
} //steps
} //stage
// run only when stage1 is success
stage('stage2'){
when {
expression {
params.name ==~ /x|y/
}
}
steps {
script{
//stage2 steps
}
}
}
If params.name ==~ /z/ stage 3 will be executed skippping stage2
Note: I cannot include the steps in stage2/3/.. in stage1. It should be that way. Based on the build paramters stage2/3/4... will be called after stage1
The easiest way to skip remaining pipeline stages is to set up a variable which will control if following stages should be skipped or not. Something like this:
def skipRemainingStages = false
pipeline {
agent any
stages {
stage("Stage 1") {
steps {
script {
skipRemainingStages = true
println "skipRemainingStages = ${skipRemainingStages}"
}
}
}
stage("Stage 2") {
when {
expression {
!skipRemainingStages
}
}
steps {
script {
println "This text wont show up...."
}
}
}
stage("Stage 3") {
when {
expression {
!skipRemainingStages
}
}
steps {
script {
println "This text wont show up...."
}
}
}
}
}
This is very simple example that sets skipRemainingStages to true at Stage 1 and Stage 2 and Stage 3 get skipped because expression in the when block does not evaluates to true.
Alternatively you can call error(String message) step to stop the pipeline and set its status to FAILED. For example, if your stage 1 calls error(msg) step like:
stage("Stage 1") {
steps {
script {
error "This pipeline stops here!"
}
}
}
In this case pipeline stops whenever error(msg) step is found and all remaining stages are ignored (when blocks are not even checked).
Of course you can call error(msg) depending on some condition to make it FAILED only if specific conditions are met.
You can use post in a stage to exit as follows:
pipeline {
stages {
stage('stage 1') {
steps {
//step 1
}
}
stage('stage 2') {
steps {
script{
//step 2
}
}
post{
success {
}
failure {
script{
sh "exit 1"
//or
error "Failed, exiting now..."
}
}
aborted {
}
unstable {
script{
sh "exit 1"
//or
error "Unstable, exiting now..."
}
}
}
}
}
}
This will abort the build and job wouldn't run further.
You can also simply throw an Exception. That will abort the build.
In fact simply setting the build status in a catch clause works pretty well.
You can also then add custom logic in the finally block for sending notifications for build status changes (email, Slack message etc)
So perhaps something like the following. NOTE: I have copied some of this from an existing Jenkinsfile. So not 100% sure this is the same syntax as you were using:
pipeline {
try {
stages {
stage("stage1") {
if (something) {
throw new RuntimeException("Something went wrong")
}
}
stage("stage2") {
}
}
} catch (e) {
currentBuild.result = "FAILED"
throw e
}
}
You can try:
stage('Set skipRemainingStages variable which decides, whether to run next stages or not') {
steps {
script {
skipRemainingStages = true
try {
println("In if block")
skipRemainingStages = true
}
catch (Exception exc) {
println("Exception block: ${exc}")
skipRemainingStages = false
}
if (skipRemainingStages) {
currentBuild.result = 'FAILURE'
error("Stopping early!")
}
}
}
}
stage('This will not execute if skipRemainingStages=true')
{.
.
.
}
You can use mark the build as failed and then use sh "exit 1" to interrupt its execution in your Jenkins pipelines like below:
pipeline {
stages {
stage('stage 1') {
steps {
}
}
stage('stage 2') {
steps {
script{
if (something) {
currentBuild.result = "FAILURE"
sh "exit 1"
}
}
}
}
}
}
I have a Jenkins pipeline which has multiple stages, for example:
node("nodename") {
stage("Checkout") {
git ....
}
stage("Check Preconditions") {
...
if(!continueBuild) {
// What do I put here? currentBuild.xxx ?
}
}
stage("Do a lot of work") {
....
}
}
I want to be able to cancel (not fail) the build if certain preconditions are not met and there is no actual work to be done. How can I do this? I know the currentBuild variable is available, but I can't find the documentation for it.
You can mark the build as ABORTED, and then use the error step to cause the build to stop:
if (!continueBuild) {
currentBuild.result = 'ABORTED'
error('Stopping early…')
}
In the Stage View, this will show that the build stopped at this stage, but the build overall will be marked as aborted, rather than failed (see the grey icon for build #9):
After some testing I came up with the following solution:
def autoCancelled = false
try {
stage('checkout') {
...
if (your condition) {
autoCancelled = true
error('Aborting the build to prevent a loop.')
}
}
} catch (e) {
if (autoCancelled) {
currentBuild.result = 'ABORTED'
echo('Skipping mail notification')
// return here instead of throwing error to keep the build "green"
return
}
// normal error handling
throw e
}
This will result into following stage view:
failed stage
If you don't like the failed stage, you have to use return. But be aware you have to skip each stage or wrapper.
def autoCancelled = false
try {
stage('checkout') {
...
if (your condition) {
autoCancelled = true
return
}
}
if (autoCancelled) {
error('Aborting the build to prevent a loop.')
// return would be also possible but you have to be sure to quit all stages and wrapper properly
// return
}
} catch (e) {
if (autoCancelled) {
currentBuild.result = 'ABORTED'
echo('Skipping mail notification')
// return here instead of throwing error to keep the build "green"
return
}
// normal error handling
throw e
}
The result:
custom error as indicator
You can also use a custom message instead of a local variable:
final autoCancelledError = 'autoCancelled'
try {
stage('checkout') {
...
if (your condition) {
echo('Aborting the build to prevent a loop.')
error(autoCancelledError)
}
}
} catch (e) {
if (e.message == autoCancelledError) {
currentBuild.result = 'ABORTED'
echo('Skipping mail notification')
// return here instead of throwing error to keep the build "green"
return
}
// normal error handling
throw e
}
Following this documentation from Jenkins, you should be able to generate an error to stop the build and set the build result like this:
currentBuild.result = 'ABORTED'
Hope that helps.
The thing that we use is:
try {
input 'Do you want to abort?'
} catch (Exception err) {
currentBuild.result = 'ABORTED';
return;
}
The "return" at the end makes sure that no further code is executed.
I handled in a declarative way as shown below:
Based on catchError block it will execute post block.
If post result falls under failure category, the error block will be executed to stop upcoming stages like Production, PreProd etc.
pipeline {
agent any
stages {
stage('Build') {
steps {
catchError {
sh '/bin/bash path/To/Filename.sh'
}
}
post {
success {
echo 'Build stage successful'
}
failure {
echo 'Compile stage failed'
error('Build is aborted due to failure of build stage')
}
}
}
stage('Production') {
steps {
sh '/bin/bash path/To/Filename.sh'
}
}
}
}
Inspired by all the answers I have put all the stuff together into one Scripted Pipeline. Keep in mind this is not a Declarative Pipeline.
To get this example working you will need:
QuickFIX form this answer Jenkins CI Pipeline Scripts not permitted to use method groovy.lang.GroovyObject
discord notifier plugin - https://plugins.jenkins.io/discord-notifier/
Discord channel webhook url filled in the code
The idea I had was to abort the pipeline if it is "replayed" vs started by "run button"(in branches tab of Jenskins BlueOcean):
def isBuildAReplay() {
// https://stackoverflow.com/questions/51555910/how-to-know-inside-jenkinsfile-script-that-current-build-is-an-replay/52302879#52302879
def replyClassName = "org.jenkinsci.plugins.workflow.cps.replay.ReplayCause"
currentBuild.rawBuild.getCauses().any{ cause -> cause.toString().contains(replyClassName) }
}
node {
try {
stage('check replay') {
if (isBuildAReplay()) {
currentBuild.result = 'ABORTED'
error 'Biuld REPLAYED going to EXIT (please use RUN button)'
} else {
echo 'NOT replay'
}
}
stage('simple stage') {
echo 'hello from simple stage'
}
stage('error stage') {
//error 'hello from simple error'
}
stage('unstable stage') {
unstable 'hello from simple unstable'
}
stage('Notify sucess') {
//Handle SUCCESS|UNSTABLE
discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}", footer: 'No-Code', unstable: true, link: env.BUILD_URL, result: "${currentBuild.currentResult}", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
}
} catch (e) {
echo 'This will run only if failed'
if(currentBuild.result == 'ABORTED'){
//Handle ABORTED
discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}\n\nERROR.toString():\n"+e.toString()+"\nERROR.printStackTrace():\n"+e.printStackTrace()+" ", footer: 'No-Code', unstable: true, link: env.BUILD_URL, result: "ABORTED", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
throw e
}else{
//Handle FAILURE
discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}\n\nERROR.toString():\n"+e.toString()+"\nERROR.printStackTrace():\n"+e.printStackTrace()+" ", footer: 'No-Code', link: env.BUILD_URL, result: "FAILURE", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
throw e
}
} finally {
echo 'I will always say Hello again!'
}
}
Main trick was the order of lines to achive abort state:
currentBuild.result = 'ABORTED'
error 'Biuld REPLAYED going to EXIT (please use RUN button)'
First set the state then throw an exception.
In the catch block both work:
currentBuild.result
currentBuild.currentResult
If you're able to approve the constructor for FlowInterruptedException, then you can do the following:
throw new FlowInterruptedException(Result.ABORTED, new UserInterruption(getCurrentUserId()))
You can add to your shared library repo a file var/abortError.groovy:
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
import jenkins.model.CauseOfInterruption.UserInterruption
def call(message)
{
currentBuild.displayName = "#${env.BUILD_NUMBER} $message"
echo message
currentBuild.result = 'ABORTED'
throw new FlowInterruptedException(Result.ABORTED, new UserInterruption(env.BUILD_USER_ID))
}
Then you can use it this way (after importing library):
abortError("some message")
Note that if you se following error in console logs:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new org.jenkinsci.plugins.workflow.steps.FlowInterruptedException hudson.model.Result jenkins.model.CauseOfInterruption[]
You need follow the link form log and approve security exception.
You can go to the script console of Jenkins and run the following to abort a hung / any Jenkins job build/run:
Jenkins .instance.getItemByFullName("JobName")
.getBuildByNumber(JobNumber)
.finish(hudson.model.Result.ABORTED, new java.io.IOException("Aborting build"));
This is the way to abort the currently running build pipeline in Jenkins UI(in Build History there is a cancel button), for capture:
The Executor.interrupt(Result) method is the cleanest, most direct way I could find to stop a build prematurely and choose the result.
script {
currentBuild.getRawBuild().getExecutor().interrupt(Result.ABORTED)
sleep(1) // Interrupt is not blocking and does not take effect immediately.
}
Pros:
Works in a declarative pipeline just as well as a scripted one.
No try/catch or exceptions to handle.
Marks the calling stage and any successive stages as green/passing in the UI.
Cons:
Requires a number of in-process script approvals, including one that is considered insecure. Approve and use with caution.
Taken from my answer on devops.stackexchange.com.
As for currentBuild, have a look at the docs for the RunWrapper class.
Here's the code I'm playing with
node {
stage 'build'
echo 'build'
stage 'tests'
echo 'tests'
stage 'end-to-end-tests'
def e2e = build job:'end-to-end-tests', propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
?????? I want to just fail this stage
}
}
Is there any way for me to mark the 'end-to-end-tests' stage as failed without failing the whole job? Propagate false just always marks the stage as true, which is not what I want, but Propagate true marks the job as failed which I also don't want.
This is now possible, even with declarative pipelines:
pipeline {
agent any
stages {
stage('1') {
steps {
sh 'exit 0'
}
}
stage('2') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh "exit 1"
}
}
}
stage('3') {
steps {
sh 'exit 0'
}
}
}
}
In the example above, all stages will execute, the pipeline will be successful, but stage 2 will show as failed:
As you might have guessed, you can freely choose the buildResult and stageResult, in case you want it to be unstable or anything else. You can even fail the build and continue the execution of the pipeline.
Just make sure your Jenkins is up to date, since this is a fairly new feature.
Stage takes a block now, so wrap the stage in try-catch. Try-catch inside the stage makes it succeed.
The new feature mentioned earlier will be more powerful. In the meantime:
try {
stage('end-to-end-tests') {
node {
def e2e = build job:'end-to-end-tests', propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
} else {
sh "exit 1" // this fails the stage
}
}
}
} catch (e) {
result = "FAIL" // make sure other exceptions are recorded as failure too
}
stage('deploy') {
if (result.equals("SUCCESS")) {
build 'deploy'
} else {
echo "Cannot deploy without successful build" // it is important to have a deploy stage even here for the current visualization
}
}
Sounds like JENKINS-26522. Currently the best you can do is set an overall result:
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
currentBuild.result = e2e.result
// but continue
}
I recently tried to use vaza's answer
Show a Jenkins pipeline stage as failed without failing the whole job as template for writing a function that excutes a job in an own stage named like the job name. Surprisingly it worked, but maybe some groovy experts have a look at it :)
Here is how it looks like if one of the jobs is aborted:
def BuildJob(projectName) {
try {
stage(projectName) {
node {
def e2e = build job:projectName, propagate: false
result = e2e.result
if (result.equals("SUCCESS")) {
} else {
error 'FAIL' //sh "exit 1" // this fails the stage
}
}
}
} catch (e) {
currentBuild.result = 'UNSTABLE'
result = "FAIL" // make sure other exceptions are recorded as failure too
}
}
node {
BuildJob('job1')
BuildJob('job2')
}
In order to show a successful build with a failed stage when a downstream job fails AND support a user being able to cancel a build (including all subsequent stages), I had to use a combination of various solutions, specifically when, try/catch, throw and catchError().
env.GLOBAL_BUILD_ABORTED = false // Set if the user aborts the build
pipeline {
agent any
stages {
stage('First Stage') {
when { expression { env.GLOBAL_BUILD_ABORTED.toBoolean() == false } }
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
myLocalBuildMethod('Stage #1, build #1')
myLocalBuildMethod('Stage #1, build #2')
}
}
}
stage('Second Stage') {
when { expression { env.GLOBAL_BUILD_ABORTED.toBoolean() == false } }
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
myLocalBuildMethod('Stage #2, build #1')
myLocalBuildMethod('Stage #2, build #2')
myLocalBuildMethod('Stage #2, build #3')
}
}
}
}
}
def myLocalBuildMethod(myString) {
/* Dummy method to show User Aborts vs Build Failures */
echo "My Local Build Method: " + myString
try {
build (
job: "Dummy_Downstream_Job"
)
} catch (e) {
/* Build Aborted by user - Stop All Test Executions */
if (e.getMessage().contains("was cancelled") || e.getMessage().contains("ABORTED")) {
env.GLOBAL_BUILD_ABORTED = true
}
/* Throw the execiption to be caught by catchError() to mark the stage failed. */
throw (e)
}
// Do other stuff...
}
You could add a explicit fail task, such as 'sh "not exist command"' in the stage.
if (result.equals("SUCCESS")) {
stage 'deploy'
build 'deploy'
} else {
try {
sh "not exist command"
}catch(e) {
}
}
Solution steps
You must emit an error in a stage to mark it as an error
Outside the scope of the stage, handle the exception and choose the build status
This makes the effect desired by a couple of users here, including myself, #user3768904, #Sviatlana
Success with failed Step Example
node("node-name") {
try {
stage("Process") {
error("This will fail")
}
} catch(Exception error) {
currentBuild.result = 'SUCCESS'
return
}
stage("Skipped") {
// This stage will never run
}
}
Aborted with failure Step Example
node("node-name") {
try {
stage("Process") {
error("This will fail")
}
} catch(Exception error) {
currentBuild.result = 'ABORTED'
return
}
stage("Skipped") {
// This stage will never run
}
}
You can use the following code in your else statement:
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
error "some err msg"
}
This could be a general pattern showing how to customize the stage result with nice messages using the built-in functions and propagate the sub-job's result to the stage result. That the overall build is marked unstable if a sub-job is not successful is just a implementation choice for this example.
def run_sub_job() {
def jobBuild = build(job: 'foo', wait: true, propagate: false)
def result = jobBuild.getResult()
def msg = 'sub-job: ' + result
if ('SUCCESS' == result) {
println(msg)
} else if ('UNSTABLE' == result) {
unstable(msg) // will also set buildResult to UNSTABLE
} else { // anything else (FAILURE, ABORTED ...) is considered an error
catchError(
buildResult: 'UNSTABLE',
stageResult: result // propagate sub-job result
) {
error(msg)
}
}
}