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."
}
}
}
}
Related
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.
I have a Jenkins pipeline with some parallel stages that should not fail the job if they fail.
Those stages start a build job.
I started from https://stackoverflow.com/a/56975220/1817610.
The original sample works, but not if my stage builds another pipeline.
pipeline {
agent any
stages {
stage('1') {
steps {
sh 'exit 0'
}
}
stage('2') {
parallel {
stage('2.1') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
build job: 'Failing pipeline'
}
}
}
stage('2.2') {
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
build job: 'Succesful pipeline'
}
}
}
}
}
stage('3') {
steps {
sh 'exit 0'
}
}
}
}
See build 7 in screenshot
If I changed the stage to
stage('2.1') {
steps {
build job: 'Failing pipeline', propagate: false
}
}
The job does not fail, but also the stage does not fail, see build 8.
I'd like to have the global state as successful but still showing that one of the builds failed.
you could make use of pure groovy try..catch block and control your SUCCESS and FAILURE with some condition.
Below is an example:
pipeline {
agent any;
stages {
stage('01') {
steps {
sh "echo Hello"
}
}
stage('02') {
parallel {
stage('02.1') {
steps {
script {
try {
def v = 10/0
println v
}catch(Exception e) {
println e
}
}
}
}
stage('02.2') {
steps {
script {
try {
def v = 10 % 2
println v
}catch(Exception e) {
println e
}
}
}
}
}
}
}
}
In my example, my parallel stage 02.1 will fail with java.lang.ArithmeticException: Division by zero but catch block will handle it by catching the exception.
if you want to fail the build with some condition, you can put if..else condition inside catch {} and fail by throwing back the exception to Jenkins like
...
stage('02.1') {
steps {
script {
try {
def v = 10/0
println v
}catch(Exception e) {
if(someCondition) {
println e
} else {
throw e;
}
}
}
}
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"
}
}
}
}
}
}
I'm creating a declarative Jenkins pipeline, that looks like this:
pipeline {
agent {
label 'mylabel'
}
stages {
stage('Install dependencies') {
milestone()
steps {
sh "yarn install"
}
}
stage('Lint files') {
steps {
sh "eslint src"
}
}
stage('Create bundle') {
steps {
sh "yarn run build:server"
sh "yarn run build:client"
}
}
stage('Publish') {
steps {
timeout(time: 15, unit: 'SECONDS') {
input(message: 'Deploy this build to QA?')
}
// deployment steps
}
}
}
}
It works great, however, if the timeout step fails (because we don't want to deploy this build, or nobody is there to push the button, and so on), the build is marked with status "aborted". Unfortunately this means that for example Github marks our pull requests as "checks failing".
Is there a way to declare the build with the status that it had before the timeout() step? Eg. if the build was a success up until the timeout step, it should be marked as success, even if the timeout happens.
Since all you want is to let the build abort without marking it as failed, you can just add a simple try/catch to your code.
stage('Publish') {
steps {
script {
def proceed = true
try {
timeout(time: 15, unit: 'SECONDS') {
input(message: 'Deploy this build to QA?')
}
} catch (err) {
proceed = false
}
if(proceed) {
// deployment steps
}
}
}
}
If a user aborts the build or it times out, the error is suppressed, the build is still a success, and the deployment steps won't execute.
We have a situation where we don't want to start a build if there are no user commits. Because of a bug in the scm trigger prevention on message or user.
What we then do is fail the build with a NOT_BUILT result.
Maybe this will also work for your situation
Try the following in your script
try {
stage ('wait') {
timeout(time: 15, unit: 'SECONDS') {
input(message: 'Deploy this build to QA?')
}
}
} catch (err) {
def user = err.getCauses()[0].getUser()
if('SYSTEM' == user.toString()) { //timeout
currentBuild.result = "SUCCESS"
}
}
If you don't want the job to be marked as "aborted" or "failed":
For a Declarative pipeline (not a scripted) you would do something like this
stage('Foo') {
steps {
script {
env.PROCEED_TO_DEPLOY = 1
try {
timeout(time: 2, unit: 'MINUTES') {
// ...
}
} catch (err) {
env.PROCEED_TO_DEPLOY = 0
}
}
}
}
stage('Bar') {
when {
expression {
env.PROCEED_TO_DEPLOY == 1
}
}
//rest of your pipeline
stage "Bar" would be skipped but as for the rest of the stages the job will be marked as passed (assuming nothing wrong happened before)
You could probably adjust currentBuild.result in post section of declarative pipeline.
E.g.
pipeline {
stages {
...
}
post {
aborted {
script {
currentBuild.result = 'SUCCESS'
}
}
}
}
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)
}
}
}