4

I have a series of steps in a stage that I want to run even if the first one fails. I want the stage result to fail and the build to get aborted, but only after all steps have run. For example,

pipeline {
    agent any
    stages {
        stage('Run Test') {
            steps {
                sh "echo running unit-tests"
                sh "echo running linting && false"  // failure
                sh "echo generating report"  // This should still run (It currently doesn't)
                publishCoverage adapters: [coberturaAdapter("coverage.xml")]  // This should still run (It currently doesn't)
                junit 'unit-test.xml'  // This should still run (It currently doesn't)
            }
        }
        stage('Deploy') {
            steps {
                echo "deploying"  // This should NOT run
            }
        }
    }
}

The result should be a failed build where the "Run Test" stage failed and the "Deploy" stage did not run. Is this possible?

P.S.

I am NOT asking for the same behavior as in Continue Jenkins pipeline past failed stage. I want to run the steps following the failure, but not any of the stages afterwards. I tried to enclose each of the test steps with catchError (buildResult: 'FAILURE', stageResult: 'FAILURE'), but the "Deploy" stage still runs.

EDIT:

I cannot combine all the steps into one big sh step and capture its return code because some of the steps are not shell commands, but instead jenkins steps like junit and publishCoverage.

Cnoor0171
  • 368
  • 3
  • 12

4 Answers4

4

I found a slightly hacky way to get the behavior I want. The other answers didn't work for me, either because they need all the steps to be sh steps, or they don't stop the deploy stage from running. I used catchError to set the build and stage result. But to prevent the next stage from running, I needed to an explicit call to error if the stage failed.

pipeline {
    agent any
    stages {
        stage('Run Test') {
            steps {
                script {
                    // catchError sets the stageResult to FAILED, but does not stop next stages from running
                    catchError (buildResult: 'FAILURE', stageResult: 'FAILURE') {
                        sh "echo running unit-tests"
                    }
                    catchError (buildResult: 'FAILURE', stageResult: 'FAILURE') {
                        sh "echo running linting && false"  // failure
                    }
                    catchError (buildResult: 'FAILURE', stageResult: 'FAILURE') {
                        sh "echo generating report"  // This still runs
                    }
                    publishCoverage adapters: [coberturaAdapter("coverage.xml")]  // This still runs
                    junit 'unit-test.xml'  // This still runs

                    if (currentBuild.result == "FAILURE") {  // This is needed to stop the next stage from running
                        error("Stage Failed")
                    }
                }
            }
        }
        stage('Deploy') {
            steps {
                echo "deploying"  // This should NOT run
            }
        }
    }
}
Cnoor0171
  • 368
  • 3
  • 12
1

A script witha non-zero exit code will always cause a jenkins step to fail. You can use returnStatus as true so that jenkins does not fails the step.
Additionally considering your use case, you could use a post always execution, so that the steps are always carried out.

Please see below reference example:

stage('Run Test') {
            steps {
                def unit_test_result= sh returnStatus: true, script: 'echo "running unit-tests"'
                def lint_result= sh returnStatus: true, script: 'echo "running linting"'       
                if (unit_test_result!=0 || lint_result!=0 ) {
                   // If the unit_test_result or lint_result status is not 0 then mark this stage as unstable to continue ahead 
                   // and all later stages will be executed 
                   unstable ('Testing failed')
                   // You can also mark as failed as below and it will not conintue other stages:
                   // error ('Testing failed')
                   }
                
            }
           post {
                always {
                   // This block would always be executed inspite of failure
                    sh "echo generating report"
                publishCoverage adapters: [coberturaAdapter("coverage.xml")]
                junit 'unit-test.xml' 
                       }
                }
        }
Altaf
  • 2,838
  • 1
  • 16
  • 8
0

Theoretically you should be able to use sh "<command>||true" It would ignore the error on command and continue. However, Jenkins will not fail as it would ignore the error.

If you don't want Jenkins to ignore the error and want it to stop at the end of the stage, you can do something like: sh "<command>||$error=true" then fail the build based on the $error variable. (sh "$error" might be enough but I am not sure, may require an if statement at the end.) It will be only set to true iff command fails.

Yamuk
  • 750
  • 8
  • 27
  • But `$error` here is just a shell variable, which means it won't be available across different steps. I cannot combine all the steps into one big `sh` step because some of them aren't actually shell commands. I'm only using `sh` as an example here. Some of the steps are actually `junit` or `cobertura` steps. – Cnoor0171 Jul 01 '21 at 22:51
  • Ah I thought it was consisting of shell steps. Then I think you can consider dividing them in stages which are conceptually related. For instance you can put the steps that should always run to a "post" stage with always condition. – Yamuk Jul 01 '21 at 22:57
  • Another thing is, some steps have parameters that return the status of the step rather than failing the build (`returnStatus` for shell step is one of them). You can use them if the steps you need support them. Consult their documentation. One drawback though: Declarative Pipeline is not suitable for such kind of behavior. There may be work-arounds, but it is possible that you would need to enclose steps in a `script` section. – Yamuk Jul 01 '21 at 23:02
0

Another option is to wrap your build steps in a try-catch block! if there's an exception, i.e. return code of build is not 0 you can catch it, mark the build as unstable and then the rest of the pipeline continues on.

here's an example ` pipeline {

agent {
    node {
        label 'linux'
    }
}

options {
    timestamps()
    disableConcurrentBuilds()
    buildDiscarder(logRotator(numToKeepStr: '3'))
}

tools {
    maven 'Maven 3.6.3'
    jdk 'jdk11'
}

stages {

    stage('CleanWS') {
        steps {
            cleanWs()
        }
    }

  

    stage('Build') {
        steps {
            withMaven(options: [artifactsPublisher(disabled: true)]) {
                sh "export NLS_LANG=GERMAN_GERMANY.WE8ISO8859P1 && mvn -f pom.xml clean install -DskipTests -Pregression-test  -Dmaven.javadoc.skip=true"
            }
        }
    }

    stage('Test') {
        steps {
            script {
                try {
                    withMaven(options: [artifactsPublisher(disabled: true)]) {
                        sh "export MAVEN_OPTS=\"-Xmx2048m\" && export NLS_LANG=GERMAN_GERMANY.WE8ISO8859P1 && mvn -B verify   -Dmaven.source.skip=true -Dmaven.javadoc.skip=true"
                    }

                } catch (exc) {
                    currentBuild.result = 'UNSTABLE'
                }
            }
        }
        post {
            always {
                script {
                    junit "**/surefire-reports/*.xml"
                }
            }
        }
    }

    stage('Sonar Analyse') {
        steps {
            script {
                withMaven(options: [artifactsPublisher(disabled: true)]) {
                    withSonarQubeEnv("SonarQube") {
                        sh "export MAVEN_OPTS=\"-Xmx2048m\" && export NLS_LANG=GERMAN_GERMANY.WE8ISO8859P1 && mvn sonar:sonar"
                    }
                }
            }
        }
    }

    stage('Deploy to Nexus') {
        steps {
            sh "export NLS_LANG=GERMAN_GERMANY.WE8ISO8859P1 && mvn -f pom.xml -B clean deploy -DdeployAtEnd=true -DskipTests"
        }
    }

}

post {
    failure {
        script {
            emailext(
                    body: "Please go to ${env.BUILD_URL}/console for more details.",
                    to: emailextrecipients([developers(), requestor()]),
                    subject: "Nightly-Build-Pipeline Status is ${currentBuild.result}. ${env.BUILD_URL}"
            )
        }
    }
    unstable {
        script {
            emailext(
                    body: "Please go to ${env.BUILD_URL}/console for more details.",
                    to: emailextrecipients([developers(), requestor()]),
                    subject: "Nightly-Build-Pipeline Build Status is ${currentBuild.result}. ${env.BUILD_URL}"
            )
        }
    }
}

}`

meaningqo
  • 1,653
  • 10
  • 16
  • Surrounding the steps in `try ... catch` and setting `currentBuild.result` in case of error didn't seem to help. The stage shows as unstable, and the build fails but the subsequent stages still run. – Cnoor0171 Jul 02 '21 at 21:06
  • sorry, seems i have missunderstood your question than as I thought that you wanted the other stages to run as well and not only the steps of the curent stage. you can add an additional if-statement at the start of your deploy stage, checking the currentBuild.result and only proceed if its not unstable – meaningqo Jul 04 '21 at 13:29