Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional execution based on result of previous stage in declarative pipeline

My pipeline setup is as follows. enter image description here

I need to get it working with adherence to following conditions. Help me defining when blocks and other code to be used and in which stages?

  1. If A fails, no other stage is executed and job run is marked failed.
  2. If any of the B stage fails, then corresponding C stage should not be invoked.
  3. Stage D should get executed when either C1 or C2 have been executed regardless of failure in their execution.
  4. Also, if any of the stages have been failed, then the over all job run status should be fail.

What have I tried & observed? From above conditions defined, 1 and 2 are working as expected but not 3 and 4 with my following attempt.

In C1 and C2, I added catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') referring to Continue Jenkins pipeline past failed stage.

But what I have observed is -

  1. D executes if C1 or C2 fails, but over all job run is marked Success. Expected is failure as one of the C steps have failed. But due to catch it gets success status.
  2. When any of the B stages fail, their corresponding C also doesn't execute (expected) but it doesn't trigger D either. As I need D to be triggered because some other C has been executed.
like image 706
Nazil Khan Avatar asked Jan 26 '23 21:01

Nazil Khan


1 Answers

This is what you need:

stageResultMap = [:]

pipeline {
    agent any
    stages {
        stage('A') {
            steps {
                println("This is stage: ${STAGE_NAME}")
            }
        }
        stage('BC') {
            parallel {
                stage ('1'){
                    stages {
                        stage('B1') {
                            steps {
                                script {
                                    // Catch exceptions, set the stage result as unstable,
                                    // build result as failure, and the variable didB1Succeed to false
                                    try {                                        
                                        sh "exit 1"
                                        stageResultMap.didB1Succeed = true
                                    }
                                    catch (Exception e) {
                                        unstable("${STAGE_NAME} failed!")
                                        currentBuild.result = 'FAILURE'
                                        stageResultMap.didB1Succeed = false                                        
                                    }
                                }
                            }
                        }
                        stage('C1') {
                            // Execute only if B1 succeeded
                            when {
                                expression {
                                    return stageResultMap.find{ it.key == "didB1Succeed" }?.value
                                }
                            }
                            steps {
                                // Mark the stage and build results as failure on error but continue pipeline execution
                                catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
                                    sh "echo Hello"
                                }
                            }
                        }
                    } 
                }
                stage ('2'){
                    stages {
                        stage('B2') {
                            steps {
                                script {
                                    // Catch exceptions, set the stage result as unstable,
                                    // build result as failure, and the variable didB2Succeed to false
                                    try {
                                        sh "echo Hello"
                                        stageResultMap.didB2Succeed = true
                                    }
                                    catch (Exception e) {
                                        unstable("${STAGE_NAME} failed!")
                                        currentBuild.result = 'FAILURE'
                                        stageResultMap.didB2Succeed = false                                        
                                    }
                                }
                            }
                        }
                        stage('C2') {
                            // Execute only if B2 succeeded
                            when {
                                expression {
                                    return stageResultMap.find{ it.key == "didB2Succeed" }?.value
                                }
                            }
                            steps {
                                // Mark the stage and build results as failure on error but continue pipeline execution
                                catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
                                    sh "echo Hello"
                                }
                            }
                        }
                    } 
                }
            }
        }
        stage('D') {
            // Execute only when C1 or C2 have executed, that is B1 or B2 have succeeded
            when {
                expression {
                    return stageResultMap.any {it.value}
                }
            }
            steps {
                println("This is stage: ${STAGE_NAME}")
            }
        }
    }
}
  1. For stages C1 & C2, use catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') to mark both the stage and build results as FAILURE but continue pipeline execution.

enter image description here enter image description here

  1. For stages B1 & B2, first initialize an empty map stageResultMap = [:] at the top of your Jenkinsfile to capture the results of each stage. Now, for every Bn stage, use a try-catch block so that on success a unique key-value pair stageResultsMap.didBnSucceed = true is added to the map, and on exception the stage result is set to UNSTABLE, build result to FAILURE, and stageResultsMap.didBnSucceed = false. We use the method unstable here because none of the other two methods catchError or warnError let us add to the map. Then, in the corresponding Cn stage, evaluate the map and execute only if Bn did succeed. Also, if both B1 and B2 fail, i.e., both C1 and C2 do not execute, D wouldn't execute either because when {expression {return stageResultMap.any {it.value}}} will evaluate to false. But D will execute if either C1 or C2 executed regardless of failures.

enter image description here enter image description here

In both the above scenarios, the overall build status will be marked as FAILURE if any of the stages fail.

Of course, a green build on no failures.

enter image description here

like image 117
Dibakar Aditya Avatar answered Jan 28 '23 11:01

Dibakar Aditya