Дженкинс параметризует задание, которое ставит только одну строчку

Представьте себе работу Дженкинса A, которая занимает 1 минуту, а работа B занимает 5 минут.

Если мы сконфигурируем задание A для запуска задания B, в то время как задание B работает, задание A может выполняться 5 раз до завершения B. Тем не менее, Дженкинс не добавляет 5 строчек к задаче B, что отлично, потому что иначе быстрая работа A создавала бы постоянно растущее отставание сборок для плохой работы B.

Однако теперь мы хотим иметь задание A trigger B как параметризованное задание, используя параметризованный триггерный плагин. Параметрированные задания помещают в очередь на отставание, что означает, что работа A с радостью создает огромную кучу сборок для задания B, которые не могут не отставать.

Имеет смысл добавлять новую параметризованную сборку в очередь каждый раз, когда она запускается, поскольку параметры могут быть разными. Дженкинс не должен всегда предполагать, что новая параметризованная сборка делает ненужные ненужные очереди.

Однако в нашем случае мы действительно хотели бы этого. Job A создает и обрабатывает наше приложение, затем Job B разворачивает его в производственную среду и запускает более тяжелый набор тестов интеграции. У нас также есть сборка C, которая развертывается в другой среде и делает еще больше тестирования, поэтому для нас это является эскалацией.

Нам нужна очередь для нашего параметризованного задания B, чтобы сохранить только добавленную к нему последнюю сборку; каждая новая сборка заменит любое задание, находящееся в очереди.

Есть ли хороший способ достичь этого?

Ответы

Ответ 1

Добавьте шаг предварительной настройки "System Groovy Script" к заданию B, который проверяет (более новые) задания с очередями с тем же именем и выдает их при обнаружении:

def name = build.properties.environment.JOB_NAME
def queue = jenkins.model.Jenkins.getInstance().getQueue().getItems()
if (queue.any{ it.task.getName() == name }) {
  println "Newer " + name + " job(s) in queue, aborting"
  build.doStop()
} else {
  println "No newer " + name + " job(s) in queue, proceeding"
}

Ответ 2

Вы можете избавиться от параметра Parameterized Trigger Plugin, а вместо этого использовать традиционный запуск. Как вы сказали, это предотвратит наложение очереди B.

Как передать параметры от A до B? Сделайте задание A, чтобы получить параметры в нем вывод консоли. В задании B, чтобы получить эти параметры сборки, просмотрите вывод консоли из последней сборки A (с Python script, возможно?).

Ответ 3

Здесь один способ:

Ответ 4

В случае, если вы используете Git, теперь это поддерживается с помощью хешей Combine Queued git в параметре Triggering/Parameters/Pass-through. Первая версия плагина git, которая должна фактически работать с этим: 1.1.27 (см. Jenkins-15160)

Ответ 5

Решение Рона работало для меня. Если вам не нравится иметь кучу отложенных сборок в истории сборки, вы можете добавить следующую систему groovy script к заданию A перед запуском задания B:

import hudson.model.*  
def q = jenkins.model.Jenkins.getInstance().getQueue()   
def items = q.getItems()  
for (i=0;i<items.length;i++){  
  if(items[i].task.getName() == "JobB"){  
    items[i].doCancelQueue()
  }   
}

Ответ 6

Здесь приведен более гибкий вариант, если вы заботитесь только о нескольких параметрах. Это особенно полезно, когда задание запускается извне (т.е. Из GitHub или Stash), а некоторым параметрам не требуется отдельная сборка.

Если проверенные параметры соответствуют как значениям, так и существующим как в текущей сборке, так и в очереди, текущая сборка будет прервана, и описание покажет, что будущая сборка содержит те же проверенные параметры (вместе с тем, что они были).

Он может быть изменен для отмены всех остальных заданий в очереди, кроме последнего, если вы не хотите, чтобы история сборки показывала прерванные задания.

    checkedParams = [ 
    "PARAM1",
    "PARAM2",
    "PARAM3",
    "PARAM4",
]

def buildParams = null
def name = build.project.name
def queuedItems = jenkins.model.Jenkins.getInstance().getQueue().getItems()

yieldToQueuedItem = false
for(hudson.model.Queue.Item item : queuedItems.findAll { it.task.getName() == name }) {
    if(buildParams == null) {    
        buildParams = [:]
        paramAction = build.getAction(hudson.model.ParametersAction.class)
        if(paramAction) {
            buildParams = paramAction.getParameters().collectEntries {
                [(it.getName()) : it.getValue()]
            }
        }
    }
    itemParams = [:]
    paramAction = item.getAction(hudson.model.ParametersAction.class)
    if(paramAction) {
        itemParams = paramAction.getParameters().collectEntries {
            [(it.getName()) : it.getValue()]
        }
    }

    equalParams = true
    for(String compareParam : checkedParams) {
        itemHasKey = itemParams.containsKey(compareParam)
        buildHasKey = buildParams.containsKey(compareParam)
        if(itemHasKey != buildHasKey || (itemHasKey && itemParams[compareParam] != buildParams[compareParam])) {
            equalParams = false
            break;
        }
    }
    if(equalParams) {
        yieldToQueuedItem = true
        break
    }
}

if (yieldToQueuedItem) {
    out.println "Newer " + name + " job(s) in queue with matching checked parameters, aborting"
    build.description = "Yielded to future build with:"
    checkedParams.each {
        build.description += "<br>" + it + " = " + build.buildVariables[it]
    }

    build.doStop()
    return
} else {
    out.println "No newer " + name + " job(s) in queue with matching checked parameters, proceeding"
}

Ответ 7

Ниже приведено решение Ron, но с некоторыми исправлениями для работы с моим Jenkins 2, включая удаление исключения java.io.NotSerializableException и обработку, что формат getName() несколько отличается от формата JOB_NAME

// Exception to distinguish abort due to newer jobs in queue
class NewerJobsException extends hudson.AbortException {
    public NewerJobsException(String message) { super(message); }
}

// Find jenkins job name from url name (which is the most consistently named
// field in the task object)
// Known forms:
//   job/NAME/
//   job/NAME/98/
@NonCPS
def name_from_url(url)
{
    url = url.substring(url.indexOf("/") + 1);
    url = url.substring(0, url.indexOf("/"));
    return url
}

// Depending on installed plugins multiple jobs may be queued. If that is the
// case skip this one.
// http://stackoverflow.com/info/26845003/how-to-execute-only-the-most-recent-queued-job-in-jenkins
// http://stackoverflow.com/info/8974170/jenkins-parameterized-job-that-only-queues-one-build
@NonCPS
def check_queue()
{
    def name = env.JOB_NAME
    def queue = jenkins.model.Jenkins.getInstance().getQueue().getItems()
    if (queue.any{ name_from_url(it.task.getUrl()) == name }) {
        print "Newer ${name} job(s) in queue, aborting"
        throw new NewerJobsException("Newer ${name} job(s) in queue, aborting")
    } else {
        print "No newer ${name} job(s) in queue, proceeding"
    }
}