詹金斯 - 如果开始新的构建,则中止运行构建
Posted
技术标签:
【中文标题】詹金斯 - 如果开始新的构建,则中止运行构建【英文标题】: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 分支的自动化测试等情况,如果分支已更新,则在分支上完成测试通常没有什么好处,因为更新也需要进行测试。显而易见的解决方案是中止之前的测试。可能仍需要进行清理,但不会浪费资源来完成不必要的测试。 【参考方案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().eachid,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 Script
或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()
//
【讨论】:
【参考方案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
【讨论】:
以上是关于詹金斯 - 如果开始新的构建,则中止运行构建的主要内容,如果未能解决你的问题,请参考以下文章
构建步骤“执行 Windows 批处理命令”在詹金斯中将构建标记为失败