Настройка результата сборки Jenkins multijob с помощью скрипта Groovy на основе дочерних заданий% pass/fail

У меня есть проект Jenkins Multijob с очень простой структурой:

  • Multijob
    • childjob 1
    • childjob 2
    • ребенок 3
    • childjob 4 и т.д....

Я хочу установить статус Multijob следующим образом:

  • Я хочу зеленый шар, если все детские задания пройдут
  • Я хочу желтый шар, если кто-то пропустит ИЛИ <25%
  • Я хочу красный шар, если> = 25%

Я знаю, что могу использовать Groovy post build action со сценарием, подобным ниже, но я не знаю, как установить требуемые пороговые уровни:

void log(msg) {
manager.listener.logger.println(msg)
}

threshold = Result.SUCCESS

void aggregate_results() {
    failed = false

    mainJob = manager.build.getProject().getName()
    job = hudson.model.Hudson.instance.getItem(mainJob)

    log '-------------------------------------------------------------------------------------'
    log 'Aggregated status report'
    log '-------------------------------------------------------------------------------------'

log('${mainJob}    #${manager.build.getNumber()} - ${manager.build.getResult()}')

job.getLastBuild().getSubBuilds().each { subBuild->
  subJob = subBuild.getJobName() 
  subJobNumber = subBuild.getBuildNumber()
  job = hudson.model.Hudson.instance.getItem(subBuild.getJobName())
  log '${subJob}   #${subJobNumber} - ${job.getLastCompletedBuild().getResult()}'
  log job.getLastCompletedBuild().getLog()

  //println subBuild
  dePhaseJob = hudson.model.Hudson.instance.getItem(subBuild.getJobName())
  dePhaseJobBuild = dePhaseJob.getBuildByNumber(subBuild.getBuildNumber())
  dePhaseJobBuild.getSubBuilds().each { childSubBuild ->
    try {
        log '   ${childSubBuild.jobName}'

        job = hudson.model.Hudson.instance.getItem(childSubBuild.getJobName())
        build = job.getBuildByNumber(childSubBuild.getBuildNumber())

        indent = '  '    
        log '${indent} #${build.getNumber()}  - ${build.getResult()}' 
        log build.getLog()

        if(!failed && build.getResult().isWorseThan(threshold) ) {
          failed = true
        }
    } catch (Exception e) {    
        log('ERROR: ${e.getMessage()}')
        failed = true
    }
  }
}

if(failed) {manager.build.setResult(hudson.model.Result.FAILURE)}
}

try {
  aggregate_results()
} catch(Exception e) {
  log('ERROR: ${e.message}')
  log('ERROR: Failed Status report aggregation')
  manager.build.setResult(hudson.model.Result.FAILURE)
}

Может ли кто-нибудь помочь настроить скрипт для достижения того, что мне нужно?

Ответы

Ответ 1

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

Как сказал в своем комментарии Викрам Палакурти, вы можете попробовать использовать следующие плагины:

  1. Плагин Multijob
  2. Pipeline Stage View Plugin (я лично использовал этот, и я настоятельно рекомендую его)
  3. Плагин трубопровода (должен также иметь надлежащую поддержку для агрегированного отображения результатов и может расширяться - вы можете создать свое собственное решение для отображения!)

В качестве полной заметки, во время исследования, которое я сделал для этого ответа, я столкнулся с этой очень хорошей практикой для Pipelines, и этот очень хороший сайт для плагинов Jenkins.

Ответ 2

Не уверен, действительно ли это квалифицируется как ответ. Это может быть больше комментарием, но комментарии на самом деле не подходят для длинных фрагментов кода, так что здесь.

Чтобы сделать ваш код более читабельным и легким в использовании, я сделал следующее:

  • заменил все экземпляры getXX и setYY на доступ к свойствам groovy, например, build.getResult() становится build.result
  • удалено ненужное использование паренов в вызовах функций, например, log('ERROR: ${e.getMessage()}') становится log 'ERROR: ${e.getMessage()}'
  • заменил использование строковой интерполяции в одинарных кавычках на двойные, так как интерполяция строк не работает в одинарных кавычках. Например, log 'ERROR: ${e.message}' становится log "ERROR: ${e.message}"
  • переключено с объявления всех переменных в глобальной области привязки скрипта на локальные, например, subJob =... становится def subJob =... Объявление всего в глобальной области приводит к трудным для поиска проблемам, особенно если вы повторно используете имена переменных, такие как job.

Я также убрал некоторые reduncancies, пример:

job.getLastBuild().getSubBuilds().each { subBuild->
  subJob = subBuild.getJobName() 
  subJobNumber = subBuild.getBuildNumber()
  job = hudson.model.Hudson.instance.getItem(subBuild.getJobName())  // <---
  ...
  //println subBuild
  dePhaseJob = hudson.model.Hudson.instance.getItem(subBuild.getJobName()) // <---
  dePhaseJobBuild = dePhaseJob.getBuildByNumber(subBuild.getBuildNumber())

поэтому здесь мы устанавливаем для job и dePhaseJob одинаковое значение. Присвоение одного и того же значения двум отдельным переменным, как это, является избыточным и только затрудняет чтение кода.

Кроме того (и я не очень хорошо знаком с внутренним API-интерфейсом jenkins, поэтому я могу ошибаться здесь) следующий поток в приведенном выше коде кажется отключенным:

  • у нас есть экземпляр subBuild
  • затем мы извлекаем соответствующий экземпляр задания в job и dePhaseJob
  • затем мы получаем сборку в dePhaseJobBuild используя dePHaseJob.getBuildByNumber(subBuild.buildNumer)

но разве это не оставляет нас с subBuild == dePhaseJobBuild? Т.е. мы потратили весь этот код только для того, чтобы получить значение, которое у нас уже было. Мы идем от сборки к работе и обратно, чтобы построить. Если я не пропускаю что-то эзотерическое в api jenkins, это тоже выглядит избыточно.

Со всеми этими и некоторыми другими незначительными изменениями мы разбираемся со следующим кодом:

def job(name) {
  hudson.model.Hudson.instance.getItem(name) 
}

def aggregateResults() {
  def mainJobName = manager.build.project.name
  log '-------------------------------------------------------------------------------------'
  log 'Aggregated status report'
  log '-------------------------------------------------------------------------------------'
  log "${mainJobName}    #${manager.build.number} - ${manager.build.result}"

  def failed = false
  job(mainJobName).lastBuild.subBuilds.each { subBuild ->
    log "${subBuild.jobName}   #${subBuild.buildNumber} - ${subBuild.result}"
    log subBuild.log

    subBuild.subBuilds.each { subSubBuild ->
      try {
        log "   ${subSubBuild.jobName}   #${subSubBuild.buildNumber} - ${subSubBuild.result}"
        log "   " + subSubBuild.getLog(Integer.MAX_VALUE).join("\n   ") //indent the log lines

        if(!failed && subSubBuild.result.isWorseThan(threshold)) {
          failed = true
        }
      } catch (Exception e) {
        log "ERROR: ${e.message}"
        failed = true
      }
    }
  }

  if(failed) {
    manager.build.result = hudson.model.Result.FAILURE
  }
}

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

Проблемы в вашем коде (такие как интерполяция строк, которые я никогда не видел, когда-либо работали) заставляют меня думать, что исходный код не работал, а скорее пример шаблона.

Это заставляет меня задуматься о том, действительно ли вам нужно выполнять здесь два уровня вложенности, а именно:

job(mainJobName).lastBuild.subBuilds.each { subBuild ->
  subBuild.subBuilds.each { subSubBuild ->
    ...
  }
}

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

Если это так, вы можете избежать неприятностей с логикой:

def aggregateResults() {
  def mainJob = job(manager.build.project.name)
  def subs    = mainJob.lastBuild.subBuilds
  def total   = subs.size()
  def failed  = subs.findAll { sub -> sub.result.isWorseThan(threshold) }.size()

  if(failed > 0) {
    manager.build.result = hudson.model.Result.FAILURE
  }

  failed == 0 ? "green" : (failed/total < 0.25 ? "yellow" : "red")
}