83

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.

Jesse Glick
  • 24,539
  • 10
  • 90
  • 112
techgnosis
  • 1,879
  • 2
  • 17
  • 19

9 Answers9

70

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.

Erik B
  • 40,889
  • 25
  • 119
  • 135
  • 3
    I'm getting `Invalid parameter "buildResult", did you mean "null"?` and `Invalid parameter "stageResult", did you mean "null"?` – JShorthouse Jul 12 '19 at 11:00
  • 3
    @JShorthouse Pipeline: Basic Steps needs to be 2.18 or newer. What version are you on? – Erik B Jul 12 '19 at 12:40
  • 2
    I've found another way that works now but this was probably the problem - I assumed "fairly new" would mean it would work on my couple of months old Jenkins install but from that version's release date I take it you meant within the last week. – JShorthouse Jul 12 '19 at 14:16
  • Unfortunately, this marks my failed stage and the pipeline as succeeded. – pedroapero Mar 10 '21 at 16:13
  • Is this magic `sh 'exit 0'` also working on windows installations? – grenix Dec 15 '21 at 11:40
  • How do you do this in a scripted pipeline? – agirault Apr 14 '22 at 22:28
  • Instead of `sh "exit 1"`, I prefer the more OS independent [error step](https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/#error-error-signal). – not2savvy Aug 03 '23 at 12:35
27

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
   }
}
vaza
  • 296
  • 3
  • 5
14

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
}
Jesse Glick
  • 24,539
  • 10
  • 90
  • 112
14

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: 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')
}
grenix
  • 623
  • 7
  • 15
5

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...
}
scottysB
  • 51
  • 1
  • 3
4

You can use the following code in your else statement:

catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
  error "some err msg"
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
3

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) {
   }
}
3

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

Marcello DeSales
  • 21,361
  • 14
  • 77
  • 80
  • I tested this approach today in a DSL pipeline and it did not work to mark the whole step as a failure, even though there's a failure step within it... Jenkins 2.164.2 – Marcello DeSales Jun 17 '19 at 19:04
0

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)
        }
    }
}
Axel Heider
  • 557
  • 4
  • 14