Show a Jenkins pipeline stage as failed without failing the whole job

JenkinsGroovyJenkins WorkflowJenkins Pipeline

Jenkins Problem Overview


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.

Jenkins Solutions


Solution 1 - Jenkins

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:

Pipeline Example

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.

Solution 2 - Jenkins

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
   }
}

Solution 3 - Jenkins

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
}

Solution 4 - Jenkins

I recently tried to use vaza's answer https://stackoverflow.com/questions/36852310/show-a-jenkins-pipeline-stage-as-failed-without-failing-the-whole-job/42147678#42147678 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: enter image description here

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')
}

Solution 5 - Jenkins

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...
}

Solution 6 - Jenkins

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 7 - Jenkins

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
  }
}

enter image description here

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
  }
}

enter image description here

Solution 8 - Jenkins

You can use the following code in your else statement:

catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
  error "some err msg"
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontechgnosisView Question on Stackoverflow
Solution 1 - JenkinsErik BView Answer on Stackoverflow
Solution 2 - JenkinsvazaView Answer on Stackoverflow
Solution 3 - JenkinsJesse GlickView Answer on Stackoverflow
Solution 4 - JenkinsgrenixView Answer on Stackoverflow
Solution 5 - JenkinsscottysBView Answer on Stackoverflow
Solution 6 - Jenkinsanyone.can.testView Answer on Stackoverflow
Solution 7 - JenkinsMarcello de SalesView Answer on Stackoverflow
Solution 8 - JenkinsSofia ZelentsovView Answer on Stackoverflow