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

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

Related

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

Jenkinsfile fail a step and continue the rest

I have a Jenkinsfile like this:
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '7'))
disableConcurrentBuilds()
timeout(time: 10, unit: 'MINUTES')
timestamps()
}
stages {
stage('Admin') {
steps {
script {
try {
result = "FAIL"
} catch(error) {
result = "FAIL"
}
}
}
}
stage('Normal') {
steps {
script {
try {
sh("echo 'hi'")
} catch(error) {
}
}
}
}
}
}
How do I fail the first step and have it show red and have the pipeline continue for more steps?
I have looked at all the SO answers for this and can't make it work.
Setting result = "FAIL" does not cause the step to fail. How can I do this but continue the next step(s)?
You can mark a build status as failed by setting a new value for the global jenkins variable currentBuild. The variable has 3 states: SUCCESS (green), FAILURE (red) and UNSTABLE (yellow). The catch should prevent the build from exiting so you can set the status of the currentBuild and simply continue.
try {
//so something
} catch(error) {
//mark build as failed
currentBuild.result = 'FAILURE'
}
}
I think this should work. Sadly I don't have a way to validate this atm. If it does not work, please tell me.
As per my knowledge we can implement same as below.
Use script step and try-catch block
stage('someStage') {
steps {
script {
try {
//Do something
} catch (err) {
echo err
}
}
echo currentBuild.result = 'FAILURE'
}
}
You can use propagate: false which is available in build step. Somethng like below
stage("example") {
b = build(job: "example-job", propagate: false).result
if(b == 'FAILURE') {
echo "First job failed"
currentBuild.result = 'FAILURE' // of FAILURE
}
}
stage("test") {
build("test-job")
}
Links :
https://jenkins.io/doc/pipeline/steps/pipeline-build-step/
https://medium.com/#Lenkovits/jenkins-pipelines-and-their-dirty-secrets-1-9e535cd603f4
Do you want to show one stage "red" and all other (not failing) "green"?
As far as I know, that is not possible.
Do you want to know which stage is failing and which one runs correctly?
Well, it depends how you want it to be displayed
The easy way:
as suggested in a previous answer, you can use "echo" to print it in the console
The 'I want it full power' way:
capsulate a try-catch inside another try-catch and use the "error" signal:
stages {
try {
stage ('Normal') {
try {
/* Do your thing */
} catch (err) {
/* Mark this stage as "failure" using error
error 'Stage Normal failing'
}
} // stage 'Normal'
} catch (err) {
// here is the 'error' signal captured and omitted by now
}
Stage failing appears with the text "failed" shown in the box and in a darker red

Setting the build status to failed when failingFast

In a declarative jenkins pipeline, when failFast is used within a set of parallel stages. How do you set the build status to 'FAILED' instead of 'ABORTED'?
Assuming something fails, this will exit with 'ABORTED'
pipeline {
agent any
stages {
stage('Parallel') {
failFast true
parallel {
stage('Branch A') {
steps {
sh "foo"
}
}
stage('Branch B') {
steps {
sh "bar"
}
}
stage('Branch C') {
steps {
sh "baz"
}
}
}
}
}
}
If FailFast is removed the build will eventually fail with 'FAILED'. But I need the FailFast behaviour as well.
OK, after a bit of investigation this appears to an issue with Jenkins: https://issues.jenkins-ci.org/browse/JENKINS-55459
I was able to get by this by adding a script with a try/catch to those steps within the parallel stages.
steps {
script {
try {
MyCode()
}
catch (Exception err) {
if (currentBuild.result == null) {
error "The stage '${env.STAGE_NAME}' has failed."
} else {
echo "Exiting stage early."
}
}
}
}

How to exit from the Jenkins pipeline if a stage sets build fail/unstable status?

I have a declarative Jenkins pipeline with stage1, stage2, stage3 and so on. I want to stop stage2 from running if stage1 sets the build unstable/fail.
I know I can stop the steps in stage1 from running using return when the build is not success but couldn't find a way where I can just exit the pipeline without running the stages below stage1
Here is what I have:
stage('stage1') {
steps {
script{
//somesteps
if ("${stdout}" == "1"){
currentBuild.result = 'UNSTABLE'
return
} //if
//somesteps
} //script
} //steps
} //stage
// run only when stage1 is success
stage('stage2'){
when {
expression {
params.name ==~ /x|y/
}
}
steps {
script{
//stage2 steps
}
}
}
If params.name ==~ /z/ stage 3 will be executed skippping stage2
Note: I cannot include the steps in stage2/3/.. in stage1. It should be that way. Based on the build paramters stage2/3/4... will be called after stage1
The easiest way to skip remaining pipeline stages is to set up a variable which will control if following stages should be skipped or not. Something like this:
def skipRemainingStages = false
pipeline {
agent any
stages {
stage("Stage 1") {
steps {
script {
skipRemainingStages = true
println "skipRemainingStages = ${skipRemainingStages}"
}
}
}
stage("Stage 2") {
when {
expression {
!skipRemainingStages
}
}
steps {
script {
println "This text wont show up...."
}
}
}
stage("Stage 3") {
when {
expression {
!skipRemainingStages
}
}
steps {
script {
println "This text wont show up...."
}
}
}
}
}
This is very simple example that sets skipRemainingStages to true at Stage 1 and Stage 2 and Stage 3 get skipped because expression in the when block does not evaluates to true.
Alternatively you can call error(String message) step to stop the pipeline and set its status to FAILED. For example, if your stage 1 calls error(msg) step like:
stage("Stage 1") {
steps {
script {
error "This pipeline stops here!"
}
}
}
In this case pipeline stops whenever error(msg) step is found and all remaining stages are ignored (when blocks are not even checked).
Of course you can call error(msg) depending on some condition to make it FAILED only if specific conditions are met.
You can use post in a stage to exit as follows:
pipeline {
stages {
stage('stage 1') {
steps {
//step 1
}
}
stage('stage 2') {
steps {
script{
//step 2
}
}
post{
success {
}
failure {
script{
sh "exit 1"
//or
error "Failed, exiting now..."
}
}
aborted {
}
unstable {
script{
sh "exit 1"
//or
error "Unstable, exiting now..."
}
}
}
}
}
}
This will abort the build and job wouldn't run further.
You can also simply throw an Exception. That will abort the build.
In fact simply setting the build status in a catch clause works pretty well.
You can also then add custom logic in the finally block for sending notifications for build status changes (email, Slack message etc)
So perhaps something like the following. NOTE: I have copied some of this from an existing Jenkinsfile. So not 100% sure this is the same syntax as you were using:
pipeline {
try {
stages {
stage("stage1") {
if (something) {
throw new RuntimeException("Something went wrong")
}
}
stage("stage2") {
}
}
} catch (e) {
currentBuild.result = "FAILED"
throw e
}
}
You can try:
stage('Set skipRemainingStages variable which decides, whether to run next stages or not') {
steps {
script {
skipRemainingStages = true
try {
println("In if block")
skipRemainingStages = true
}
catch (Exception exc) {
println("Exception block: ${exc}")
skipRemainingStages = false
}
if (skipRemainingStages) {
currentBuild.result = 'FAILURE'
error("Stopping early!")
}
}
}
}
stage('This will not execute if skipRemainingStages=true')
{.
.
.
}
You can use mark the build as failed and then use sh "exit 1" to interrupt its execution in your Jenkins pipelines like below:
pipeline {
stages {
stage('stage 1') {
steps {
}
}
stage('stage 2') {
steps {
script{
if (something) {
currentBuild.result = "FAILURE"
sh "exit 1"
}
}
}
}
}
}

Abort a build immediately with success status in Jenkins pipeline

Is there any pipeline step available to abort a build on certain cases with success status?
There is an error step available to abort a build with failure status. But I don't know about the success status.
As was said in other replies, there isn't a step to abort in this way. As Christopher suggested you can use try-catch around the aborting code and use error(). I think you will need to track the abort status of your build - you could define an abort method globally in the pipeline to set this status and raise an error so it will abort other steps in your stage.
If you used the declarative pipeline you can use a 'when' declaration with an expression in later stages so they don't execute when the abort status is set.
I am interested in this problem myself so I worked out an example of a pipeline that does this here:
/**
* Tracking if the build was aborted
*/
Boolean buildAborted = false
/**
* Abort the build with a message
*/
def abortBuild = { String abortMessage ->
buildAborted = true
error(abortMessage)
}
pipeline {
agent any
parameters {
string(name: 'FailOrAbort', defaultValue: 'ok', description: "Enter 'fail','abort' or 'ok'")
}
stages {
stage('One') {
steps {
echo "FailOrAbort = ${params.FailOrAbort}"
script {
try {
echo 'Doing stage 1'
if(params.FailOrAbort == 'fail') {
echo "This build will fail"
error("Build has failed")
}
else if(params.FailOrAbort == 'abort') {
echo "This build will abort with SUCCESS status"
abortBuild("This build was aborted")
}
else {
echo "This build is a success"
}
echo "Stage one steps..."
}
catch(e) {
echo "Error in Stage 1: ${e.getMessage()}"
if(buildAborted) {
echo "It was aborted, ignoring error status"
}
else {
error(e.getMessage())
}
}
}
}
post {
failure {
echo "Stage 1 failed"
}
}
}
stage('Two') {
when {
expression {
return !buildAborted
}
}
steps {
echo "Doing stage 2"
}
}
stage('Three') {
when {
expression {
return !buildAborted
}
}
steps {
echo "Doing stage 3"
}
}
}
post {
always {
echo "Build completed. currentBuild.result = ${currentBuild.result}"
}
failure {
echo "Build failed"
}
success {
script {
if(buildAborted) {
echo "Build was aborted"
} else {
echo 'Build was a complete success'
}
}
}
unstable {
echo 'Build has gone unstable'
}
}
}
As a side note there is a property 'currentBuild.result' you can adjust in the pipeline but once set to 'FAILURE' it cannot be cleared back to 'SUCCESS' - the Jenkins model doesn't allow it AFAIK.
No, the normal process of a pipeline is to go from start to end.
What you could however do is to test for your success status and just not call the rest of your code, in an if or something like that. Functions could help you achieve that quite easily, e.g. :
node() {
// Part 1
def isSuccess = part1();
if(!isSuccess) {
part2()
}
}
// Part 2
def function part2() {
// Part 2 code
}
However, you should be careful with that kind of things, maybe it highlights the fact that your pipeline is not properly designed. If that is not what you want, please provide more details, like a use case.
First of all, I'm not aware of such a step.
But you could use the error step to abort the build with a certain message, if it should succeed. Catch this error in a try{}catch(){} block, check for the message and set the build status to success.

Resources