Reputation: 1239
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?
Upvotes: 83
Views: 59204
Reputation: 321
From Jenkins workflow-job plugin version 2.42 you can simply do
// as a step in a scripted pipeline
properties([disableConcurrentBuilds(abortPrevious: true)])
// as a directive in a declarative pipeline
options { disableConcurrentBuilds abortPrevious: true }
Found solution in comments here https://issues.jenkins.io/browse/JENKINS-43353
Upvotes: 32
Reputation: 11
Before each build task, first determine whether all the tasks currently under construction are the same as the branch of this build. If they are the same, keep the latest task.
stage('Setup') {
steps {
script {
JOB_NAME = env.JOB_NAME
branch_name = "${env.gitlabTargetBranch}"
def job = Jenkins.instance.getItemByFullName( JOB_NAME )
def builds = job.getBuilds()
for( build in job.getBuilds()) {
if (build.isBuilding()) {
String parameters = build?.actions.find{ it instanceof ParametersAction }?.parameters?.collectEntries {
[ it.name, it.value ]
}.collect { k, v -> "${v}" }.join('\n')
if (branch_name == "${parameters}") {
if (env.BUILD_NUMBER > "${build.getId()}") {
build.doKill()
}
}
}
}
}
......
Upvotes: 1
Reputation: 4738
Adding to Brandon Squizzato's answer. If builds are sometimes skipped the milestone mechanism as mentioned will fail. Setting older milestones in a for-loop solves this.
Also make sure you don't have disableConcurrentBuilds in your options. Otherwise the pipeline doesn't get to the milestone step and this won't work.
def buildNumber = env.BUILD_NUMBER as int
for (int i = 1; i < buildNumber; i++)
{
milestone(i)
}
milestone(buildNumber)
Upvotes: 5
Reputation: 11
Based on @daggett method. If you want to abort running build when the new push is coming and before fetch updates.
1. Enable Execute concurrent builds if necessary
2. Enable Prepare an environment for the run
3. Running bellow code in Groovy Script
or Evaluated Groovy script
import hudson.model.Result
import hudson.model.Run
import jenkins.model.CauseOfInterruption
//def abortPreviousBuilds() {
Run previousBuild = currentBuild.getPreviousBuildInProgress()
while (previousBuild != null) {
if (previousBuild.isInProgress()) {
def executor = previousBuild.getExecutor()
if (executor != null) {
println ">> Aborting older build #${previousBuild.number}"
def cause = { "interrupted by build #${currentBuild.getId()}" as String } as CauseOfInterruption
executor.interrupt(Result.ABORTED, cause)
}
}
previousBuild = previousBuild.getPreviousBuildInProgress()
}
//}
Upvotes: 1
Reputation: 645
With Jenkins script security many of the solutions here become difficult since they are using non-whitelisted methods.
With these milestone steps at the start of the Jenkinsfile, this is working for me:
def buildNumber = env.BUILD_NUMBER as int
if (buildNumber > 1) milestone(buildNumber - 1)
milestone(buildNumber)
The result here would be:
Upvotes: 53
Reputation: 4988
I also compiled a version from the previously given ones with a few minor tweaks:
while()
loop generated multiple outputs for each builddef killOldBuilds(userAborting) {
def killedBuilds = []
while(currentBuild.rawBuild.getPreviousBuildInProgress() != null) {
def build = currentBuild.rawBuild.getPreviousBuildInProgress()
def exec = build.getExecutor()
if (build.number != currentBuild.number && exec != null && !killedBuilds.contains(build.number)) {
exec.interrupt(
Result.ABORTED,
// The line below actually requires a userId, and doesn't output this text anywhere
new CauseOfInterruption.UserInterruption(
"${userAborting}"
)
)
println("Aborted previous running build #${build.number}")
killedBuilds.add(build.number)
}
}
}
Upvotes: 0
Reputation: 28564
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
Upvotes: 31
Reputation: 1609
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()
}
}
Upvotes: 14
Reputation: 8026
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() !
Upvotes: 9
Reputation: 607
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}")
}
}
}
Upvotes: 22