Jenkins - abort running build if new one is starte

2019-02-01 08:34发布

问题:

I use Jenkins and Multibranch Pipeline. I have a job for each active git branch. New build is triggered by push in git repository. What I want is to abort running builds in current branch if new one appears in same branch.

For example: I commit and push to branch feature1. Then BUILD_1 started in Jenkins. I make another commit and push to branch feature1 while BUILD_1 is still running. I want BUILD_1 to be aborted and to start BUILD_2.

I tried to use stage concurrency=x option, and stage-lock-milestone feature, but didn't manage to solve my problem.

Also I've read this thread Stopping Jenkins job in case newer one is started, but there is no solution for my problem.

Do you know any solution to this?

回答1:

enable job parallel run for your project with Execute concurrent builds if necessary

use execute system groovy script as a first build step:

import hudson.model.Result
import jenkins.model.CauseOfInterruption

//iterate through current project runs
build.getProject()._getRuns().iterator().each{ run ->
  def exec = run.getExecutor()
  //if the run is not a current build and it has executor (running) then stop it
  if( run!=build && exec!=null ){
    //prepare the cause of interruption
    def cause = { "interrupted by build #${build.getId()}" as String } as CauseOfInterruption 
    exec.interrupt(Result.ABORTED, cause)
  }
}

and in the interrupted job(s) there will be a log:

Build was aborted
interrupted by build #12
Finished: ABORTED 


回答2:

If anybody needs it in Jenkins Pipeline Multibranch, it can be done in Jenkinsfile like this:

def abortPreviousRunningBuilds() {
  def hi = Hudson.instance
  def pname = env.JOB_NAME.split('/')[0]

  hi.getItem(pname).getItem(env.JOB_BASE_NAME).getBuilds().each{ build ->
    def exec = build.getExecutor()

    if (build.number != currentBuild.number && exec != null) {
      exec.interrupt(
        Result.ABORTED,
        new CauseOfInterruption.UserInterruption(
          "Aborted by #${currentBuild.number}"
        )
      )
      println("Aborted previous running build #${build.number}")
    } else {
      println("Build is not running or is current build, not aborting - #${build.number}")
    }
  }
}


回答3:

Got it to work by having the following script in the Global Shared Library :

import hudson.model.Result
import jenkins.model.CauseOfInterruption.UserInterruption

def killOldBuilds() {
  while(currentBuild.rawBuild.getPreviousBuildInProgress() != null) {
    currentBuild.rawBuild.getPreviousBuildInProgress().doKill()
  }
}

And calling it in my pipeline :

@Library('librayName')
def pipeline = new killOldBuilds()
[...] 
stage 'purge'
pipeline.killOldBuilds()

Edit : Depending on how strongly you want to kill the oldBuild, you can use doStop(), doTerm() or doKill() !



回答4:

Based on the idea by @C4stor I have made this improved version... I find it more readable from @daggett 's version

import hudson.model.Result
import hudson.model.Run
import jenkins.model.CauseOfInterruption.UserInterruption

def abortPreviousBuilds() {
    Run previousBuild = currentBuild.rawBuild.getPreviousBuildInProgress()

    while (previousBuild != null) {
        if (previousBuild.isInProgress()) {
            def executor = previousBuild.getExecutor()
            if (executor != null) {
                echo ">> Aborting older build #${previousBuild.number}"
                executor.interrupt(Result.ABORTED, new UserInterruption(
                    "Aborted by newer build #${currentBuild.number}"
                ))
            }
        }

        previousBuild = previousBuild.getPreviousBuildInProgress()
    }
}