82

I have a Jenkins pipeline which has multiple stages, for example:

node("nodename") {
  stage("Checkout") {
    git ....
  }
  stage("Check Preconditions") {
    ...
    if(!continueBuild) {
      // What do I put here? currentBuild.xxx ?
    }
  }
  stage("Do a lot of work") {
    ....
  }
}

I want to be able to cancel (not fail) the build if certain preconditions are not met and there is no actual work to be done. How can I do this? I know the currentBuild variable is available, but I can't find the documentation for it.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
seg
  • 1,398
  • 1
  • 11
  • 18
  • A related question was asked on the devops stackexchange: https://devops.stackexchange.com/q/885/17761 – Ben Amos Jan 26 '23 at 21:11

11 Answers11

153

You can mark the build as ABORTED, and then use the error step to cause the build to stop:

if (!continueBuild) {
    currentBuild.result = 'ABORTED'
    error('Stopping early…')
}

In the Stage View, this will show that the build stopped at this stage, but the build overall will be marked as aborted, rather than failed (see the grey icon for build #9):

Pipeline Stage View

Christopher Orr
  • 110,418
  • 27
  • 198
  • 193
  • 20
    Great. Is there a way to early exit with success? – Lazyexpert Aug 10 '18 at 10:57
  • 4
    Sorry, already found. Just return on a `node-level` not a `stage-level` makes pipeline to early exit with success. – Lazyexpert Aug 10 '18 at 11:08
  • if (!continueBuild)...How we can set "ContinueBuild" value? – Nishant Kansal Aug 02 '19 at 12:10
  • @NishantKansal That's just the variable name the original poster mentioned. The syntax would be `def continueBuild = false` (or `true`), but it's up to you to decide when you want to abort the build, e.g. by calling a method `def continueBuild = makeSomeDecision()`. – Christopher Orr Aug 05 '19 at 16:03
  • FYI, [hudson/model/Result.java](https://github.com/jenkinsci/jenkins/blob/3cfb512909432ef8129233599592026336c8533d/core/src/main/java/hudson/model/Result.java#L81) – Lane May 31 '21 at 08:01
  • 1
    Can you give an example of where to place it on the node-level, when I place it outside of the stage- it fails ? @Lazyexpert – soBusted Aug 31 '21 at 13:17
  • The `node` that lazyexpert is referring to is only usable with a Scripted pipelines and (seemingly) not Declarative pipelines. – filoxo Apr 28 '22 at 17:50
21

After some testing I came up with the following solution:

def autoCancelled = false

try {
  stage('checkout') {
    ...
    if (your condition) {
      autoCancelled = true
      error('Aborting the build to prevent a loop.')
    }
  }
} catch (e) {
  if (autoCancelled) {
    currentBuild.result = 'ABORTED'
    echo('Skipping mail notification')
    // return here instead of throwing error to keep the build "green"
    return
  }
  // normal error handling
  throw e
}

This will result into following stage view:

enter image description here

failed stage

If you don't like the failed stage, you have to use return. But be aware you have to skip each stage or wrapper.

def autoCancelled = false

try {
  stage('checkout') {
    ...
    if (your condition) {
      autoCancelled = true
      return
    }
  }
  if (autoCancelled) {
    error('Aborting the build to prevent a loop.')
    // return would be also possible but you have to be sure to quit all stages and wrapper properly
    // return
  }
} catch (e) {
  if (autoCancelled) {
    currentBuild.result = 'ABORTED'
    echo('Skipping mail notification')
    // return here instead of throwing error to keep the build "green"
    return
  }
  // normal error handling
  throw e
}

The result:

enter image description here

custom error as indicator

You can also use a custom message instead of a local variable:

final autoCancelledError = 'autoCancelled'

try {
  stage('checkout') {
    ...
    if (your condition) {
      echo('Aborting the build to prevent a loop.')
      error(autoCancelledError)
    }
  }
} catch (e) {
  if (e.message == autoCancelledError) {
    currentBuild.result = 'ABORTED'
    echo('Skipping mail notification')
    // return here instead of throwing error to keep the build "green"
    return
  }
  // normal error handling
  throw e
}
CSchulz
  • 10,882
  • 11
  • 60
  • 114
  • I need a bit more clarity here: if (your condition)...I have a shell script that matches the previous and current commit id to decide whether the build should proceed or stop. Is it like I have to pass the exit status of that shell script to if (your condition)? If yes, then how? Please help. – Nishant Kansal Aug 02 '19 at 12:01
3

Following this documentation from Jenkins, you should be able to generate an error to stop the build and set the build result like this:

currentBuild.result = 'ABORTED'

Hope that helps.

segalaj
  • 1,181
  • 12
  • 23
  • 2
    I just tried this, with an echo afterwards and it did NOT stop the rest of the pipeline running. – Mihai Morcov Feb 16 '18 at 09:28
  • 3
    This command only set the build result. To stop the pipeline you have to generate a signal error: `error('error message')` or `throw new Exception()` – segalaj Feb 16 '18 at 12:50
  • 1
    Use `throw new Exception()` only if you want to see a stacktrace. – CSchulz Jul 10 '18 at 11:41
3

If you're able to approve the constructor for FlowInterruptedException, then you can do the following:

throw new FlowInterruptedException(Result.ABORTED, new UserInterruption(getCurrentUserId()))

You can add to your shared library repo a file var/abortError.groovy:

import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
import jenkins.model.CauseOfInterruption.UserInterruption

def call(message)
{
    currentBuild.displayName = "#${env.BUILD_NUMBER} $message"
    echo message
    currentBuild.result = 'ABORTED'
    throw new FlowInterruptedException(Result.ABORTED, new UserInterruption(env.BUILD_USER_ID))
}

Then you can use it this way (after importing library):

abortError("some message")

Note that if you se following error in console logs:

org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new org.jenkinsci.plugins.workflow.steps.FlowInterruptedException hudson.model.Result jenkins.model.CauseOfInterruption[]

You need follow the link form log and approve security exception.

Marek R
  • 32,568
  • 6
  • 55
  • 140
John Michelau
  • 1,001
  • 8
  • 12
  • Ok I get it working. Main disadvantage are security limitation. – Marek R Aug 17 '22 at 09:41
  • 1
    I've improve answer to make it easier to adopt. – Marek R Aug 17 '22 at 10:01
  • I discovered some strange behavior with this hack. Go to job which was canceled with this method and check console output. You will see `[Pipeline] End of Pipeline` and some spinning at the bottom. What is more interesting if canceled job was started by other job, starting job will never see that canceled job actually is completed. – Marek R Jun 30 '23 at 14:31
2

The thing that we use is:

try {
 input 'Do you want to abort?'
} catch (Exception err) {
 currentBuild.result = 'ABORTED';
 return;
}

The "return" at the end makes sure that no further code is executed.

Wim Rutgeerts
  • 109
  • 2
  • 5
2

I handled in a declarative way as shown below:

Based on catchError block it will execute post block. If post result falls under failure category, the error block will be executed to stop upcoming stages like Production, PreProd etc.

pipeline {

  agent any

  stages {
    stage('Build') {
      steps {
        catchError {
          sh '/bin/bash path/To/Filename.sh'
        }
      }
      post {
        success {
          echo 'Build stage successful'
        }
        failure {
          echo 'Compile stage failed'
          error('Build is aborted due to failure of build stage')

        }
      }
    }
    stage('Production') {
      steps {
        sh '/bin/bash path/To/Filename.sh'
      }
    }
  }
}
Meredith
  • 3,928
  • 4
  • 33
  • 58
Shree Prakash
  • 2,052
  • 2
  • 22
  • 33
  • I don't know your setup, but it would not be very secure to grant permissions to sudo bash anything. Better, grant sudo permission just to the script you need, and scrutinize it carefully before. – Raúl Salinas-Monteagudo Jun 26 '19 at 06:27
2

Inspired by all the answers I have put all the stuff together into one Scripted Pipeline. Keep in mind this is not a Declarative Pipeline.

To get this example working you will need:

The idea I had was to abort the pipeline if it is "replayed" vs started by "run button"(in branches tab of Jenskins BlueOcean):

def isBuildAReplay() {
  // https://stackoverflow.com/questions/51555910/how-to-know-inside-jenkinsfile-script-that-current-build-is-an-replay/52302879#52302879
  def replyClassName = "org.jenkinsci.plugins.workflow.cps.replay.ReplayCause"
  currentBuild.rawBuild.getCauses().any{ cause -> cause.toString().contains(replyClassName) }
}

node { 
        try {
                stage('check replay') {
                    if (isBuildAReplay()) {
                        currentBuild.result = 'ABORTED'
                        error 'Biuld REPLAYED going to EXIT (please use RUN button)'
                    } else {
                        echo 'NOT replay'
                    }
                }
                stage('simple stage') {
                    echo 'hello from simple stage'
                }
                stage('error stage') {
                    //error 'hello from simple error'
                }
                stage('unstable stage') {
                    unstable 'hello from simple unstable'
                }
                stage('Notify sucess') {
                    //Handle SUCCESS|UNSTABLE
                    discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}", footer: 'No-Code', unstable: true, link: env.BUILD_URL, result: "${currentBuild.currentResult}", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')

                }

        } catch (e) {
                echo 'This will run only if failed'

                if(currentBuild.result == 'ABORTED'){
                    //Handle ABORTED
                    discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}\n\nERROR.toString():\n"+e.toString()+"\nERROR.printStackTrace():\n"+e.printStackTrace()+" ", footer: 'No-Code', unstable: true, link: env.BUILD_URL, result: "ABORTED", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
                    throw e
                }else{
                    //Handle FAILURE
                    discordSend(description: "${currentBuild.currentResult}: Job ${env.JOB_NAME} \nBuild: ${env.BUILD_NUMBER} \nMore info at: \n${env.BUILD_URL}\n\nERROR.toString():\n"+e.toString()+"\nERROR.printStackTrace():\n"+e.printStackTrace()+" ", footer: 'No-Code', link: env.BUILD_URL, result: "FAILURE", title: "${JOB_NAME} << CLICK", webhookURL: 'https://discordapp.com/api/webhooks/')
                    throw e
                }
        } finally {
                echo 'I will always say Hello again!'

        }
}

Main trick was the order of lines to achive abort state:

currentBuild.result = 'ABORTED'
error 'Biuld REPLAYED going to EXIT (please use RUN button)'

First set the state then throw an exception.

In the catch block both work:

currentBuild.result
currentBuild.currentResult
stawiu
  • 21
  • 3
1

You can go to the script console of Jenkins and run the following to abort a hung / any Jenkins job build/run:

Jenkins .instance.getItemByFullName("JobName")
        .getBuildByNumber(JobNumber)
        .finish(hudson.model.Result.ABORTED, new java.io.IOException("Aborting build"));
AKS
  • 16,482
  • 43
  • 166
  • 258
0

This is the way to abort the currently running build pipeline in Jenkins UI(in Build History there is a cancel button), for capture:

enter image description here

Hana Hasanah
  • 145
  • 1
  • 6
0

The Executor.interrupt(Result) method is the cleanest, most direct way I could find to stop a build prematurely and choose the result.

script {
    currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT)
    sleep(1)   // Interrupt is not blocking and does not take effect immediately.
}

You can use any Result constant for this, but based on your desire to "cancel" because "there is no actual work to be done," I feel the NOT_BUILT result is the best fit. This has the added advantage of signaling to connected integrations (e.g. Bitbucket) that the build should be ignored and not counted as a failure.

Pros:

  • Works in a declarative pipeline just as well as a scripted one.
  • No try/catch or exceptions to handle.
  • Marks the calling stage and any successive stages as green/passing in the UI.

Cons:

  • Requires a number of in-process script approvals, including one that is considered insecure. Approve and use with caution.

Taken from my answer on devops.stackexchange.com.

As for currentBuild, have a look at the docs for the RunWrapper class.

Ben Amos
  • 1,750
  • 15
  • 18
0

The most elegant way I found: Notice, it requires to approve a method signature in the Manage Jenkins -> In process script approval page

currentBuild.result = "ABORTED"
throw new org.jenkinsci.plugins.workflow.steps.FlowInterruptedException(hudson.model.Result.ABORTED)

enter image description here

If you just want to mark the build/stage result but continue to the next steps & stages you can use:

steps{
    script{
        echo "Hello"
        catchError(buildResult: 'ABORTED', stageResult: 'ABORTED') {
            error("Your abortion reason goes here")
        }
    }
}
Barel elbaz
  • 426
  • 3
  • 8