catchError() sets currentBuild.currentResult when it should only set the stageResult - jenkins

I have a Jenkinsfile like this:
pipeline {
agent any
stages {
stage('Parallel') {
parallel {
stage('Non-critical stage') {
steps {
script {
// Other steps...
if (/*some error condition*/) {
catchError(buildResult: null, stageResult: 'UNSTABLE') {
// Fail only the stage, but leave the buildResult as is
error("Error message")
}
}
}
}
}
stage('critical stage') {
steps {
// a different stage that really matters
}
}
}
post {
always {
script {
if (currentBuild.currentResult != 'SUCCESS') {
// When the Non-critical stage fails, the currentResult is UNSTABLE...
// ...even though only the stageResult should have been set.
// However, in the Jenkins UI the build result is SUCCESS
}
}
}
}
}
}
}
In the Non-critical stage the goal is to not let the build fail, when some error condition is met. But to only mark the stage as UNSTABLE. This works fine in Jenkins. The build result will be SUCCESS, even when the Non-critical stage is UNSTABLE. However, in the post block of the Parallel stage currentBuild.currentResult is set to UNSTABLE.
The post block is the last thing being executed. So I do not understand, how the build result can be shown as SUCCESS in Jenkins, when the currentResult is UNSTABLE in the last bit of Jenkinsfile code being executed.
Also when the Non-critical stage is skipped, currentBuild.currentResult is SUCCESS as expected. So the result being UNSTABLE is definitely caused by the error() call.

I found out that my problem was, that the post stage was not directly in the scope of the pipeline block. And instead was directly in the stage('Parallel') block.
After moving the post block down like this:
pipeline {
agent any
stages {
stage('Parallel') {
parallel {
stage('Non-critical stage') {
steps {
script {
// Other steps...
if (/*some error condition*/) {
catchError(buildResult: null, stageResult: 'UNSTABLE') {
// Fail only the stage, but leave the buildResult as is
error("Error message")
}
}
}
}
}
stage('critical stage') {
steps {
// a different stage that really matters
}
}
}
}
}
post {
always {
script {
if (currentBuild.currentResult != 'SUCCESS') {
// Now the currentResult is 'SUCCESS'
}
}
}
}
}
the currentBuild.currentResult property correctly holds the value SUCCESS

Related

Jenkins Declarative Pipeline - Running multiple things paralellel but skip "branch" if earlier failure occured

I wanna build one Jenkins pipeline that builds and runs tests on multiple versions of a program (e.g. Different databases)
But when any step fails, I want to skip the following steps only for that "branch" so to speak..
This is my example Code where Stage 1 is run first, with possible parallel steps (1.a, 1.b). The code does not work and is only some sort of example of how I would like it to work:
pipeline {
agent any
environment {
stageOneFailed = "false"
stageTwoFailed = "false"
}
stages {
stage ("Stage 1") {
parallel {
stage("Stage 1.a") {
// Something like this maybe?
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
// Do stuff here..
}
}
post {
unsuccessful {
// When stage did not succeed..
// Set stageOneFailed = "true"
}
}
}
stage("Stage 1.b") {
// Do Stuff..
// If Stage 1.b fails, set stageTwoFailed="true"
}
}
}
stage("Stage 2") {
parallel {
// Only run stages if earlier steps didn't fail
stage("Stage 2.a") {
when {
environment(name: "stageOneFailed", value: "false")
}
steps {
// Do stuff..
// If Stage 2.a fails, set stageOneFailed="true"
}
}
stage("Stage 2.b") {
when {
environment(name: "stageTwoFailed", value: "false")
}
steps {
// Do stuff..
// If Stage 2.b fails, set stageTwoFailed="true"
}
}
}
}
// stage()
}
}
Can anyone give any advice on how to do this the proper way?
Thanks in advance
EDIT: Changed code example. The example runs now!
pipeline {
agent any
environment {
stageOneFailed = "false"
stageTwoFailed = "false"
}
stages {
stage ("Stage 1") {
parallel {
stage("Stage 1.a") {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
bat "ech Stage 1.a" // Should fail because ech is no valid command
}
}
post {
failure {
script {
env.stageOneFailed = "true"
}
}
}
}
stage("Stage 1.b") {
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
bat "echo Stage 1.b" // Should not fail
}
}
post {
failure {
script {
env.stageTwoFailed = "true"
}
}
}
}
}
}
stage("Stage 2") {
parallel {
// Only run stages if earlier steps didn't fail
stage("Stage 2.a") {
when {
environment(name: "stageOneFailed", value: "false")
}
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
bat "echo Stage 2.a"
}
}
post {
failure {
script {
env.stageOneFailed = "true"
}
}
}
}
stage("Stage 2.b") {
when {
environment(name: "stageTwoFailed", value: "false")
}
steps {
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
bat "echo Stage 2.b"
}
}
post {
failure {
script {
env.stageTwoFailed = "true"
}
}
}
}
}
}
}
}
But when running the example, the Stage 1.a fails but Stage 2.a is still run, maybe anyone could help out here..
EDIT: I added output to see, what value stageNFailed is set to. Even after calling env.stageOneFailed, when going into next stage, it takes the old value false..
My assumption is, that when calling script env.stageNFailed = "true", the value is only set temporarily for that stage..
The example you have used is a perfectly acceptable way to do it. You have introduced 2 env variables that get used to determine if the previous step failed. You have used catchError to ensure that the pipeline doesn't fail when the stage fails. You have to use catchError in every stage to prevent the pipeline from failing (but I guess you already know that). In the post part of the stage, you have set the appropriate env variable to true, which is also correct.
post {
failure {
script {
env.stageOneFailed = true
}
}
}
Then when the next relevant stage starts, you have used the when condition to check if the stage should be run (you could also do something like this):
when {
expression { stageOneFailed == false }
}
So basically you have done everything right.

How to mark build success when one of the stages is aborted?

I have a pipeline with stages where one of the stages, intermittently takes longer than expected and hence using timeout to abort it. But if the stage is aborted, build also marked as aborted. Following is the code for the pipeline:
pipeline {
agent any
stages {
stage('First') {
options {
timeout(time: 10, unit: 'SECONDS')
}
steps {
script {
catchError(buildResult: 'SUCCESS') {
echo "Executing stage I"
sleep 12
}
}
}
}
stage('Second') {
steps {
script {
echo "Executing stage II"
}
}
}
}
}
Even though the stage is marked as Aborted, I want to mark build as Success. Can you please help how I can achieve this?
I would suggest one improvement to Michael's answer (which is correct btw). You can use catchError to mark stage ABORTED (or UNSTABLE) and mark the build SUCCESS, but you need to wrap the code that may timeout with try-catch block to control the error. Consider the following example:
pipeline {
agent any
stages {
stage('First') {
options {
timeout(time: 3, unit: 'SECONDS')
}
steps {
script {
catchError(buildResult: 'SUCCESS', stageResult: 'ABORTED') {
try {
echo "Executing stage I"
sleep 4
} catch(org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
error "Stage interrupted with ${e.toString()}"
}
}
}
}
}
stage('Second') {
steps {
script {
echo "Executing stage II"
}
}
}
}
}
When you run this pipeline, the stage that timed out is marked as ABORTED, but the pipeline continues and if there is no failure in the remaining stages, it is marked as SUCCESS.
And here is what the UNSTABLE stage status looks like.
Michael's solution works as well, but it produces a slightly different result - the stage that times out is marked as SUCCESS, and this might be less intuitive. You need to click on the stage to check if it timed out or not.
pipeline {
agent any
stages {
stage('First') {
options {
timeout(time: 3, unit: 'SECONDS')
}
steps {
script {
try {
echo "Executing stage I"
sleep 4
} catch(Exception e) {
currentBuild.result = "SUCCESS"
}
}
}
}
stage('Second') {
steps {
script {
echo "Executing stage II"
}
}
}
}
}
Your catchError() won't work in your case. The documantation (Source) tells the following:
buildResult (optional)
If an error is caught, the overall build result
will be set to this value. Note that the build result can only get
worse, so you cannot change the result to SUCCESS if the current
result is UNSTABLE or worse. Use SUCCESS or null to keep the build
result from being set when an error is caught.
The build status is set with currentBuild.currentResult which can have three values: SUCCESS, UNSTABLE, or FAILURE.
If you want to mark the build as SUCCESS on abortion the post-option (Source) aborted can be used:
pipeline {
agent any
stages {
stage('Example') {
steps {
echo 'Hello World'
}
}
}
post {
aborted {
// Executed only if stage is aborted
currentBuild.result = 'SUCCESS'
}
}
}

Jenkins Pipeline Get Current Stage Status After Using catchError

This is a follow-up to my earlier question:
Set a stage status in Jenkins Pipelines
It turns out I can keep a pipeline as SUCCESS but can mark an individual stage as UNSTABLE if I want via catchError like this:
node()
{
stage("Stage 1")
{
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE')
{
sh 'exit 1'
}
}
}
If I want to get the current status of the pipeline itself, I can use currentBuild.getCurrentResult() but I don't see a currentStage analog to this.
I'm interested in trying out a pattern that might look something like this in my stages:
stage("1") {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
// do stuff
}
// perhaps more catchError() blocks
if(currentStage.getCurrentResult() == "UNSTABLE") {
// do something special if we're unstable
}
}
but that would fail because there's no currentStage available.
So basically, catchError() is nice but I'd like to know how I can catch the status change to my stage if it gets changed... Does anyone know how you access the status of the current stage you're in from a pipeline?
I did it like this (to keep the catchError):
def boolean test_results = false
pipeline {
...
stage( 'x' ) {
steps{
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
<Do some dangerous stuff here>
//
// If we reached here the above step hasn't failed
//
script { test_results = true }
}
}
}
stage( 'y' ) {
steps{
script{
if( test_results == true ) {
} else {
}
}
}
}
}
Though there is no direct method for accessing the result of a stage in a pipeline as of now, you can work around it. This is considering you are only interested in either SUCCESS or UNSTABLE stage results as per the question and not in FAILURE.
The workaround is to initialize an empty map at the top of your pipeline to store the result of each stage. Now, instead of the catchError() method, use the unstable() method in combination with a try-catch block. This is because the latter not only lets you set the result as unstable but also perform other operations such as add the result to the map in the except block. Then you can read this stored result from the map in your if statement.
Example
stageResults = [:]
...
stage("1") {
try {
// do stuff
// Add to map as SUCCESS on successful execution
stageResults."{STAGE_NAME}" = "SUCCESS"
} catch (Exception e) {
// Set the result and add to map as UNSTABLE on failure
unstable("[ERROR]: ${STAGE_NAME} failed!")
currentBuild.result = "SUCCESS"
stageResult."{STAGE_NAME}" = "UNSTABLE"
}
if(stageResults.find{ it.key == "{STAGE_NAME}" }?.value == "UNSTABLE") {
// do something special if we're unstable
}
}
As for me the most elegant way to do it, it's a post section.
In your example, you mark the stage as UNSTABLE so after that you can catch it using post->unstable.
stage("1") {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
error 'Something goes wrong'
}
}
post {
always { echo 'Executed on every build'}
unstable { echo 'Executed only if build is unstable (marked by catchError)'}
}
}
As an alternative to add to Dibakar Aditya's answer, it is possible to wrap everything in a function which resembles regular steps. I.e.:
stage("1") {
def localSuccess = catchLocalError {
// do stuff
}
if(!localSuccess) {
// do something special if we're unstable
}
}
boolean catchLocalError(Closure c) {
try {
c()
return true
} catch (Exception e) {
return false
}
}

Post directive at pipeline level of jenkins declarative pipeline does not get executed on failure

Below is the skeleton of my Jenkinsfile. The post directive is executed on success but not in case of failure. Is this the expected behavior of jenkins?
Thanks
#!/usr/bin/env groovy
pipeline {
agent {
node { label 'ent_linux_node' }
}
stages {
stage('Prepare'){
steps {
//some steps
}
}
stage('Build') {
steps {
//Fails at this stage
}
}
stage('ArtifactoryUploads') {
steps {
//skips since previous stage failed
}
}
}
post {
always {
//Doesn't get executed but I am expecting it to execute
}
}
}

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

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.
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:
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.
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
}
}
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
}
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:
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')
}
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...
}
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 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
}
}
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
}
}
You can use the following code in your else statement:
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
error "some err msg"
}
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)
}
}
}

Resources