Jenkins - прервать выполнение сборки, если запущена новая

Я использую Jenkins и Multibranch Pipeline. У меня есть работа для каждой активной ветки git. Новая сборка запускается нажатием на репозиторий git. Я хочу прервать выполнение сборок в текущей ветке, если в той же ветке появится новая.

Например: я фиксирую и нажимаю на ветку feature1. Затем BUILD_1 началось в Jenkins. Я делаю еще одну фиксацию и нажимаю на ветку feature1, пока BUILD_1 все еще работает. Я хочу, чтобы BUILD_1 было прервано и началось BUILD_2.

Я попытался использовать параметр stage concurrency=x и stage-lock-milestone функция, но решить мою проблему не удалось.

Также я прочитал эту ветку Остановка задания Jenkins в случае более новой запущен, но у моей проблемы нет решения.

Вы знаете какое-нибудь решение?


person kakty3    schedule 23.11.2016    source источник
comment
Мы позволяем текущему заданию завершиться, и у нас бывают случаи, когда мы позволяем очистить задания в очереди, если у нас их никогда не было (как предлагается в указанном вопросе). Не нравится идея прерывания уже запущенных заданий.   -  person MaTePe    schedule 23.11.2016
comment
@MaTePe Для таких ситуаций, как автоматическое тестирование веток git, часто мало пользы от выполнения теста в ветке, если ветка была обновлена, поскольку обновления также необходимо будет протестировать. Очевидное решение - отменить предыдущий тест. Возможно, еще потребуется очистка, но ресурсы не тратятся на ненужный тест.   -  person bschlueter    schedule 05.12.2017


Ответы (8)


С безопасностью сценария Jenkins многие решения здесь становятся сложными, поскольку они используют методы, не внесенные в белый список.

С этими этапами в начале Jenkinsfile это работает для меня:

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

Результат здесь будет:

  • Сборка 1 запускается и создает веху 1
  • Пока работает сборка 1, срабатывает сборка 2. У него есть этап 1 и этап 2. Он проходит этап 1, что приводит к прерыванию сборки №1.
person Brandon Squizzato    schedule 23.04.2019
comment
Вехи - определенно путь к многоотраслевому декларативному конвейерному проекту. - person Vladimir; 08.05.2019
comment
JENKINS-43353 предлагает сделать это официальным. - person Jesse Glick; 01.08.2019
comment
специфичны ли вехи для отрасли? - person David; 20.11.2019
comment
@David Я не могу предоставить вам какую-либо документацию по этому поводу, но, исходя из опыта и тестирования - да, они зависят от ветки (они не отменяют друг друга по веткам, по крайней мере, не в моей настройке) - person tymbark; 06.03.2020
comment
@LucasCarnevalli, это правда - убедитесь, что указанный выше код вехи является одним из первых, что определено в вашем Jenkinsfile. Для этого не требуется «узел», поэтому теоретически вы должны иметь возможность запустить этот код до того, как запустится что-либо еще. Если ваша работа терпит неудачу на этой ранней стадии из-за неудачного импорта или чего-то подобного, у вас, вероятно, есть более серьезные проблемы, которые нужно решить :) - person Brandon Squizzato; 01.07.2020
comment
@brandonsquizzato - где это находится в файле Jenkins? - person Alex G; 13.10.2020
comment
Супер крутая функция. Спасибо за это. Также работает со сценарием конвейера. - person Michael S; 11.12.2020

включить параллельный запуск заданий для вашего проекта с помощью 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 
person daggett    schedule 02.06.2017
comment
Звучит очень хорошо ! В настоящее время ищу способ перенести его в файл конвейера, созданный scm - person C4stor; 02.06.2017
comment
system groovy script работает внутри JVM мастера Jenkins, поэтому он может получить доступ ко всему в jenkins. Но конвейер работает в разветвленной JVM на ведомом устройстве, на котором выполняется сборка - я не нашел этого в документации, но совершенно уверен, что это так. - person daggett; 02.06.2017
comment
я нашел это: stackoverflow.com/questions/33531868/ / у меня сейчас нет конвейера, чтобы попробовать, но вы можете попытаться получить текущую сборку в конвейере, используя это выражение: def build = currentBuild.rawBuild - person daggett; 03.06.2017
comment
Код заработал, но, что любопытно, _getRuns отображает только текущую запущенную сборку: / - person C4stor; 05.06.2017
comment
каково значение currentBuild.rawBuild.getClass()? - person daggett; 05.06.2017
comment
класс org.jenkinsci.plugins.workflow.job.WorkflowRun - person C4stor; 06.06.2017
comment
Благодаря этому вопросу я посмотрел на правильный javadoc и получил рабочее решение ^^ - person C4stor; 06.06.2017
comment
Для тех, кто, как и я, получил этот ответ и не может запустить код - удалите идентификатор из закрытия. в основном измените строку: build.getProject()._getRuns().each{id,run-> на build.getProject()._getRuns().each{ run -> - person kars7e; 28.07.2017
comment
Это дает мне эту ошибку даже после определения build. groovy.lang.MissingPropertyException: нет такого свойства: build для класса: groovy.lang.Binding в groovy.lang.Binding.getVariable (Binding.java:63) в org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty (SandboxInterceptor.java:242) - person Taha Tariq; 05.12.2017
comment
в песочнице он работать не будет. execute system groovy script - person daggett; 05.12.2017
comment
Также такой подход может показаться рискованным в отношении основной ветки. Если вы работаете в команде, вы объединились с мастером, ваш товарищ по команде слился через 1 минуту. твоя работа будет отменена, а его побегут. - person JarJarrr; 06.03.2018
comment
система Groovy, кажется, отключена в последних выпусках jenkins ..... я не думаю, что это все еще возможно - person U.V.; 14.02.2019
comment
@daggett Что означает «не будет работать в песочнице»? - person Michael Kemmerzell; 06.03.2019
comment
Мне не удалось запустить его с безопасностью песочницы сценария: wiki.jenkins.io/ display / JENKINS / Script + Security + Plugin - person daggett; 06.03.2019
comment
Я не уверен, что это сработает, если сборка не выполняется на исполнителе. В моем случае мне нужно, чтобы это сработало, когда сборка находится в очереди. Это возможно? Это связано с тем, что у нас так много параллельных PR, которые перестраиваются после слияния с Develop, что многие из них ставятся в очередь и остаются в очереди, потому что строятся устаревшие ветки. - person Mark Han; 01.10.2019
comment
удалить очередь и заставить сборки запускаться параллельно, поэтому последняя сборка остановит предыдущую. - person daggett; 01.10.2019
comment
Отменит ли это и проекты в области переработки и сбыта продукции? - person user972014; 10.07.2020

Если кому-то это нужно в 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}")
    }
  }
}
person midN    schedule 20.08.2017
comment
Может стоит проверить, что номер сборки ниже текущего. В противном случае вы можете убить даже более новые сборки. - person danielMitD; 16.02.2018

Основываясь на идее @ 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()
    }
}
person Stefanos Kalantzis    schedule 18.04.2018
comment
Это решило проблему в моем скрипте конвейера. Отображается сообщение «Прервать более старую сборку», а «Прервано более новой сборкой» - нет. Может быть, это потому, что моя старая сборка ждала действия ввода. - person neves; 16.01.2019
comment
@neves Может быть. Также на всякий случай это не очевидно: сообщение Прервано более новой сборкой отображается на другой (более старой) сборке. - person Stefanos Kalantzis; 22.01.2019
comment
В этом подходе используются статические методы. Итак, я получаю эту ошибку: скриптам не разрешено использовать staticMethod hudson.model.Hudson getInstance - person Dmitry Kuzmenko; 22.07.2021

Чтобы это заработало, в глобальной общей библиотеке есть следующий скрипт:

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

person C4stor    schedule 06.06.2017
comment
Есть ли способ отправить сообщение завершенной сборке? Он отправляет этот сигнал жесткого уничтожения, но не регистрируется, кто его убил. - person Taha Tariq; 04.12.2017
comment
Не знаю, сейчас мы живем с этими сплошными серыми линиями, и этого для нас достаточно ^^ ' - person C4stor; 05.12.2017
comment
Порядок от изящного к разрушительному идет doStop() - ›doTerm() -› doKill() - person Deiwin; 12.04.2018
comment
Как это вообще сработало для вас? это не так :) Но спасибо за идею ... у меня рабочая версия ... смотри мой ответ - person Stefanos Kalantzis; 18.04.2018
comment
Что ж, сейчас он работает в нашем производственном стеке, так что я не думаю, что это неправильно. Тот факт, что вы не смогли использовать код, так как это может быть связано с множеством факторов, включая версию jenkins, версию java, используемую ОС, используемые права доступа к файлам .... - person C4stor; 18.04.2018
comment
@TahaTariq решение stackoverflow.com/a/49901413/10335 печатает сообщение в старых заданиях. К сожалению, похоже, что это не работает, если другое ваше текущее задание ожидает ввода. - person neves; 16.01.2019

Добавление к ответу Брэндона Сквиццато. Если сборки иногда пропускаются, упомянутый механизм этапов не работает. Это решает установка более старых вех в цикле for.

Также убедитесь, что в ваших параметрах не указано disableConcurrentBuilds. В противном случае конвейер не дойдет до этапа, и это не сработает.

def buildNumber = env.BUILD_NUMBER as int
for (int i = 1; i < buildNumber; i++)
{
    milestone(i)
}
milestone(buildNumber)
person CodeMonkey    schedule 22.01.2020
comment
Потенциальная проблема заключается в том, что при большом количестве сборок создание такого количества этапов может занять значительное количество времени. Я не знаю точно, в какой момент все изменилось - раньше я быстро достигал многих вех. Совсем недавно создание вехи занимало около полсекунды каждая - очевидно, не идеально, если вы используете сборку # 900. Итак, я создал свое решение, в котором не используется цикл for. - person Brandon Squizzato; 03.07.2020

На основе метода @daggett. Если вы хотите прервать выполнение сборки при поступлении нового push-уведомления и до получения обновлений.
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()
    }
//}
person Ian Kwok    schedule 27.11.2019

Я также скомпилировал версию из ранее приведенных с несколькими незначительными изменениями:

  • цикл 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)
        }
    }
}
person Janis Peisenieks    schedule 01.04.2019