【问题标题】:Jenkins - abort running build if new one is started詹金斯 - 如果开始新的构建,则中止运行构建
【发布时间】:2017-04-07 05:47:39
【问题描述】:

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

例如:我提交并推送到分支feature1。然后BUILD_1 在 Jenkins 中开始。我再次提交并推送到分支feature1,而BUILD_1 仍在运行。我希望BUILD_1 被中止并启动BUILD_2

我尝试使用stage concurrency=x 选项和stage-lock-milestone 功能,但未能解决我的问题。

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

你知道解决办法吗?

【问题讨论】:

  • 我们让当前的工作完成,在某些情况下,如果我们从来没有工作,我们会让队列中的工作被清理(如引用的问题中所建议的那样。)不喜欢中止已经开始的工作的想法。
  • @MaTePe 对于 git 分支的自动化测试等情况,如果分支已更新,则在分支上完成测试通常没有什么好处,因为更新也需要进行测试。显而易见的解决方案是中止之前的测试。可能仍需要进行清理,但不会浪费资源来完成不必要的测试。

标签: git jenkins groovy multibranch-pipeline


【解决方案1】:

有了 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 中止。

【讨论】:

  • 里程碑绝对是多分支声明性管道项目的必经之路。
  • JENKINS-43353 建议正式发布。
  • 里程碑分支是特定的吗?
  • @David 我不能给你任何关于这方面的文档,但从测试和经验来看——是的,它们是特定于分支的(至少在我的设置中不会跨分支相互取消)
  • @LucasCarnevalli 确实如此——确保上述里程碑代码是您的 Jenkinsfile 中定义的第一件事。它不需要“节点”,因此理论上您应该能够在运行其他任何代码之前运行此代码。如果您的工作由于导入失败或类似原因而在工作早期失败,您可能有更大的问题需要解决:)
【解决方案2】:

使用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 

【讨论】:

  • 听起来很不错!目前正在寻找一种将其移植到 scm 提交的管道文件的方法
  • 让代码工作,但奇怪的是,_getRuns 只列出当前正在运行的构建:/
  • 类 org.jenkinsci.plugins.workflow.job.WorkflowRun
  • 对于像我一样得到这个答案并且无法使代码运行的任何人 - 从闭包中删除 id。基本上将线路:build.getProject()._getRuns().each{id,run-> 改为 build.getProject()._getRuns().each{ run ->
  • 它在沙盒中不起作用。 execute system groovy script
【解决方案3】:

如果有人在 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}")
    }
  }
}

【讨论】:

  • 也许值得检查一下内部版本号是否低于当前版本号。否则,您可能会杀死更新的构建。
【解决方案4】:

根据@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()
    }
}

【讨论】:

  • 这解决了我的管道脚本中的问题。正在显示“Aborting old build”消息,但没有显示“Aborted by newer build”。也许是因为我的旧版本正在等待输入操作。
  • @neves 可能是。同样以防万一:“被较新的构建中止”消息显示在另一个(较旧的)构建上。
  • 这种方法是使用静态方法。所以我收到了这个错误: Scripts not allowed to use staticMethod hudson.model.Hudson getInstance
  • @DmitryKuzmenko 也许您正在沙箱中运行脚本?它在那里行不通。另外,这是从 2018 年开始的,可能新版本存在差异。
【解决方案5】:

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

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()!

【讨论】:

  • 有没有办法向终止的构建发送消息?它发送这个硬终止信号,但没有记录谁杀死它。
  • 我不知道,我们暂时生活在这条完整的灰色线条中,对我们来说已经足够了^^'
  • 从优雅到破坏的顺序是doStop() -> doTerm() -> doKill()
  • 这对您有什么帮助?这是错误的 :) 但是感谢您的想法...我有一个工作版本...查看我的答案
  • 嗯,它现在正在我们的生产堆栈中工作,所以我认为它没有错。您无法使用代码的事实可能来自很多因素,包括 jenkins 版本、java 版本、使用的操作系统、使用中的文件权限......
【解决方案6】:

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

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

def buildNumber = env.BUILD_NUMBER as int
for (int i = 1; i < buildNumber; i++)
{
    milestone(i)
}
milestone(buildNumber)

【讨论】:

  • 这样做的潜在问题是,当您有大量构建时,创建这么多里程碑可能会占用大量时间。我不知道事情在什么时候发生了变化——创造了许多过去对我来说很快的里程碑。然后最近,创建一个里程碑每次大约需要半秒——如果你在构建 #900 上显然不理想。所以我创建了不使用 for 循环的解决方案。
【解决方案7】:

基于@daggett 方法。如果您想在新推送到来时和获取更新之前中止正在运行的构建。
1.启用Execute concurrent builds if necessary
2.启用Prepare an environment for the run
3.在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()
    }
//}

【讨论】:

    【解决方案8】:

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

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

    【讨论】:

      【解决方案9】:

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

      在 cmets here 中找到解决方案 https://issues.jenkins.io/browse/JENKINS-43353

      【讨论】:

        猜你喜欢
        • 2016-07-16
        • 2014-07-18
        • 2012-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多