64

我使用 Jenkins 和多分支管道。我为每个活动的 git 分支都有一份工作。新构建由 git 存储库中的推送触发。如果新版本出现在同一分支中,我想要的是中止当前分支中正在运行的构建。

例如:我提交并推送到分支feature1。然后BUILD_1从詹金斯开始。我再次提交并feature1BUILD_1仍在运行时推送到分支。我想BUILD_1流产并开始BUILD_2

我尝试使用stage concurrency=x选项和阶段锁定里程碑功能,但未能解决我的问题。

我也读过这个线程Stopping Jenkins job in case new one is started,但我的问题没有解决方案。

你知道有什么解决办法吗?

4

9 回答 9

48

使用 Jenkins 脚本安全性,这里的许多解决方案变得困难,因为它们使用非白名单方法。

通过 Jenkinsfile 开始时的这些里程碑步骤,这对我有用:

def buildNumber = env.BUILD_NUMBER as int
if (buildNumber > 1) milestone(buildNumber - 1)
milestone(buildNumber)

这里的结果是:

  • Build 1 运行并创建里程碑 1
  • 在构建 1 运行时,构建 2 会触发。它具有里程碑 1 和里程碑 2。它通过了里程碑 1,这导致构建 #1 中止。
于 2019-04-23T19:40:04.257 回答
30

为您的项目启用作业并行运行Execute concurrent builds if necessary

用作execute system groovy script第一个构建步骤:

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)
  }
}

并且在中断的作业中会有一个日志:

Build was aborted
interrupted by build #12
Finished: ABORTED 
于 2017-06-02T10:04:24.840 回答
20

如果有人在 Jenkins Pipeline Multibranch 中需要它,可以像这样在 Jenkinsfile 中完成:

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}")
    }
  }
}
于 2017-08-20T09:01:19.227 回答
11

基于@C4stor 的想法,我制作了这个改进的版本......我发现它从@daggett 的版本中更具可读性

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()
    }
}
于 2018-04-18T13:47:51.277 回答
9

通过在全局共享库中使用以下脚本使其工作:

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

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

并在我的管道中调用它:

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

编辑:根据您想要杀死 oldBuild 的强度,您可以使用 doStop()、doTerm() 或 doKill() !

于 2017-06-06T07:45:37.597 回答
5

添加到 Brandon Squizzato 的答案。如果构建有时被跳过,上述里程碑机制将失败。在 for 循环中设置较旧的里程碑可以解决这个问题。

还要确保您的选项中没有disableConcurrentBuilds。否则,管道不会到达里程碑步骤,这将不起作用。

def buildNumber = env.BUILD_NUMBER as int
for (int i = 1; i < buildNumber; i++)
{
    milestone(i)
}
milestone(buildNumber)
于 2020-01-22T08:23:05.190 回答
3

从 Jenkins 2.42 你可以简单地做

// as a step in a scripted pipeline    
properties([disableConcurrentBuilds(abortPrevious: true)]) 
// as a directive in a declarative pipeline
options { disableConcurrentBuilds abortPrevious: true } 

在此处的评论中找到解决方案 https://issues.jenkins.io/browse/JENKINS-43353

于 2021-12-16T07:23:16.007 回答
1

基于@daggett 方法。如果您想在新推送到来时和获取更新之前中止正在运行的构建。
1. 启用Execute concurrent builds if necessary
2. 启用3. 在或Prepare an environment for the run
中运行波纹管代码Groovy ScriptEvaluated 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()
    }
//}
于 2019-11-27T03:40:40.520 回答
0

我还从之前给出的版本中编译了一个版本,并进行了一些小的调整:

  • 循环为while()每个构建生成多个输出
  • UserInterruption 当前需要一个 userId 而不是推理字符串,并且不会在任何地方显示推理字符串。因此,这只是提供了 userId
def 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)
        }
    }
}
于 2019-04-01T08:36:07.477 回答