8

In a declarative pipeline, using a multibranch job and a Jenkinsfile, can the job be aborted with a success exit code rather than a failure exit code?

In the below stage, I basically check if the commit that started the job contains "ci skip", and if it does I want to abort the job.

Using error aborts the job but also marks it as such (red row). I'd like this job to be marked with a green row.

stage ("Checkout SCM") {
   steps {
      script {
         checkout scm
         result = sh (script: "git log -1 | grep '.*\\[ci skip\\].*'", returnStatus: true) 
         if (result == 0) {
            error ("'ci skip' spotted in git commit. Aborting.")
         }
      }
   }
}

Edit:

Instead of the above, I am now trying to simply skip all stages in case the git commit contains "ci skip". My understanding is that if the result in expression is false, it should skip the stage...

pipeline {
    environment {
        shouldBuild = "true"
    }
    ...
    ...
    stage ("Checkout SCM") {
        steps {
            script {
                checkout scm
                result = sh (script: "git log -1 | grep '.*\\[ci skip\\].*'", returnStatus: true) 
                if (result == 0) {
                    echo ("'ci skip' spotted in git commit. Aborting.")
                    shouldBuild = "false"
                }
            }
        }
    }

    stage ("Unit tests") {
        when {
            expression {
                return shouldBuild
            }
        }
        steps {
            ...
        }
    }
    ...
    ...
}
Idan Adar
  • 44,156
  • 13
  • 50
  • 89

4 Answers4

8

Okay, so the way to get this to work is not with the environment directive but rather to use the parametersdirective.

That is, adding parameters at the top of the pipeline:

parameters {
      booleanParam(defaultValue: true, description: 'Execute pipeline?', name: 'shouldBuild')
   }

When checking for the git commit, if it contains "ci skip" I change the value of shouldBuild:

env.shouldBuild = "false"

Then in expression:

expression {
    return env.shouldBuild != "false" 
}

And that's it. In case the git commit contains "ci skip" the stages are skipped and the job finishes with SUCCESS.

Idan Adar
  • 44,156
  • 13
  • 50
  • 89
  • You should be able to access parameters with the `params` global so you also have the type available. You can then just do `return parameters.shouldBuild` – mkobit Mar 27 '17 at 04:31
  • Thanks @mkobit, so it's `params.shouldBuild` or `parameters.shouldBuild`? – Idan Adar Mar 27 '17 at 04:52
  • Also, should `env.shouldBuild` also change then to `params.shouldBuild` when I change its value? – Idan Adar Mar 27 '17 at 04:55
  • So it's definitely not `parameters.shouldBuild`. "parameters" doesn't exist. But, after changing the return to what you suggested using `params`, I guess it thinks the value of `shouldBuild` is still "true" because it did not skip the stages... – Idan Adar Mar 27 '17 at 05:33
  • Good catch with the `parameters`, that was my fault when posting from my phone. If I look at the `/pipeline-syntax/globals` on my Jenkins, I see the global `params` variable which says _Exposes all parameters defined in the build as a read-only map with variously typed values._. All `env` values get coerced into `String` type. – mkobit Mar 27 '17 at 16:22
  • So, I think you should be abel to do `return params.shouldBuild`. `env.shouldBuild` will also be the `String` value of your parameter, so that will be `"true"` or `"false"` instead of the `boolean` typed value of `true` or `false`. I'm fairly confident with this, but I discover new and interesting strangeness with Jenkins pipelines daily. – mkobit Mar 27 '17 at 16:24
  • Weird. Maybe I'll play with it again some time later... I'm. Not sure why it expression was true when it shouldn't have been, following your suggestion. Hmpf... – Idan Adar Mar 27 '17 at 16:31
  • Awesome! I've been searching the internet for hours and this is, without a doubt, the simplest (and functional) way to do what I want. You, sir, are going places! – rmccabe3701 May 09 '22 at 02:58
  • Why is the parameter even needed -- you're just setting an env var based on the commit message. – Josh M. Jan 27 '23 at 16:05
3

If you are launching an error, it's not possible to change the status to SUCCESS because a build result can only get worse.

Anyways, I implemented the ci-skip functionality by using a shared library to mark the build result as NOT_BUILT. I think that's a better solution, since you won't need to add the when expression on each stage.

Also, the code of the pipeline looks cleaner:

pipeline {
  stages {
    stage('prepare') { steps { ciSkip action: 'check' } }
    // other stages here ...
  }
  post { always { ciSkip action: 'postProcess' } }
}

See this answer for the code of the library.

csalazar
  • 748
  • 1
  • 7
  • 13
1

You can also make a skip-ci check in one stage using when directive. For example:

stages {
    stage("Check Preconditions") {
        when {
            expression {
                result = sh (script: "git log -1 | grep '.*\\[ci skip\\].*'", returnStatus: true) # Check if commit message contains skip ci label
                result == 0 # Evaluate the result
            }
        }
        steps {
            script {
                echo 'Got skip=ci, aborting build' # Just an info message
                currentBuild.result = 'ABORTED' # Mark the current build as aborted
                error('Skip-CI') # Here you actually stop the build
            }
        }
    }
    stage("Test") {
        ...
    }

The idea is that if you meet skip-ci condition the code under when block will be executed and the build will be aborted.

A few notes here:

  • pipeline still will be marked in red color, but in the Build History you will see grey (aborted) build
  • If you're using a Groovy sandbox, you need to approve signature staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object java.lang.String via In-process script approval in order to get currentBuild.result = 'ABORTED' work

P.S This works well if you need to mark the build as aborted. Basically, Jenkins do abort the build on [ci skip] condition, so this status looks more appropriate. I assume, this will work with currentBuild.result = 'SUCCESS' as well. Otherwise, you can try to use catchError directive

Yurii Rochniak
  • 304
  • 2
  • 5
0

The declarative method to do this is as follows:

    stage ("Unit tests") {
        when {
            not {
                changelog '.*\\[ci skip\\].*'
            }
        }
        steps {
            ...
        }
    }
Vetsin
  • 2,245
  • 1
  • 20
  • 24