Cnoor0171
Cnoor0171

Reputation: 374

Jenkins pipeline: Run all steps in stage, even if the first one fails

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.

Upvotes: 5

Views: 15024

Answers (4)

meaningqo
meaningqo

Reputation: 1918

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

Upvotes: 0

Cnoor0171
Cnoor0171

Reputation: 374

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

Upvotes: 4

Altaf
Altaf

Reputation: 3076

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

Upvotes: 1

Yamuk
Yamuk

Reputation: 769

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.

Upvotes: 0

Related Questions