Получаем управление обратно в Jenkins Pipeline

в 13:20, , рубрики: continuous delivery, Jenkins, Jenkins CI, open source, Программирование

Jenkins Pipeline Plugin очень удобная штука, чтобы организовать у себя непрерывную доставку ПО (Continuous Delivery). Плагин даёт возможность разбить доставку ПО до конечного потребителя на стадии (stage), каждой из которых можно управлять (на каком узле, что и как нужно сделать) и, в конечном счёте, визуализировать процесс доставки. Вкупе с Blueocean plugin всё это выглядит очень вкусно. В реальной же жизни подчас оказывается так, что кроме Jenkins-а есть ещё и другие системы, которые участвуют в этом процессе (workflow), и встаёт вопрос — как их интегрировать с имеющимися решениями. Примером тут может служить Jira, в которой есть некий issue падающий на тестировщика, прокликивающего интерфейс (ну или совершающего другую полезную работу), и только после его благословения, наш артефакт имеет право двигаться дальше в сторону ожидающего его клиента.

Так какие у нас есть варианты реализации?

Очевидно, что их не меньше двух:

  • "засыпать" на некоторое время и проверять состояние во внешней системе (polling)
  • использовать webhook для продолжения или отмены движения артефакта по пайплайну

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

Пошерстив документацию, фичи с названием веб-хук я не нашел, и это, на самом деле, не очень хорошо, потому что то, что есть — скорее некий workaround нежели целевое решение.

Ставить опыты будем на очень простой конфигурации сферического коня в сферическом вакууме (буквально берём пример из example-ов):

node {
   stage 'Stage 1'
   echo 'Hello World 1'

   stage 'Stage 2'
   echo 'Hello World 2'

   stage 'Stage 3'
   build job: 'hello-task', parameters: [[$class: 'StringParameterValue', name: 'CoolParam', value: 'hello']]
}

Для описания шагов последовательности действий, в плагине используется groovy-dsl. В приведённом примере у нас всё будет выполняться на одной ноде (причем это мастер, поэтому не делайте так ;)). Как видно, есть три стадии исполнения, две из которых просто пишут Hello World в консоль (какая неожиданность), а третья вызывает не менее простой job и передаёт в него параметр, который также нужно напечатать в консоли.

Если выполнить этот таск, то мы увидим в логах нечто подобное:

Started by user admin
[Pipeline] node
Running on master in /var/jenkins_home/jobs/pipeline-test/workspace
[Pipeline] {
[Pipeline] stage (Stage 1)
Entering stage Stage 1
Proceeding
[Pipeline] echo
Hello World 1
[Pipeline] stage (Stage 2)
Entering stage Stage 2
Proceeding
[Pipeline] echo
Hello World 2
[Pipeline] stage (Stage 3)
Entering stage Stage 3
Proceeding
[Pipeline] build (Building hello-task)
Scheduling project: hello-task
Starting building: hello-task #2
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

Ура, у нас выполнились и наши команды из скрипта, и наш дочерний таск, который мы определили отдельно.

Теперь представим, что между первой и второй стадией, нам нужно одобрение от внешней системы на продолжение исполнения работы. Для того, чтобы его реализовать, будем использовать механизм ожидания ввода данных пользователем через конструкцию input. В простейшем виде это будет выглядеть так:

input 'Ready to go?'

Запустив наш job ещё раз, мы увидим, что с нас теперь требуют выполнить подтверждающее действие в интерфейсе:
alt

Но интерфейс это круто для любителей щелкать мышью, но он никак не решает нашей задачи, поэтому идём курить API. И тут с документацией не всё в порядке. Чтобы понять что и как вызвать, нужно спросить совета у знающих в чатике людей, и исследовать код.

Так как в нашем примере нет никаких параметров, то можно использовать метод proceedEmpty, для подтверждения действия. Чтобы это сделать, нужно кинуть POST-запрос на урл:

JENKINS_ROOT_URL/job/JOB_NAME/BUILD_NUMBER/input/INPUT_ID/proceedEmpty?token=YOUR_TOKEN

Основная сложность тут именно в получении INPUT_ID, потому что через API его у меня достать не получилось, а понять какой он, можно только распарсив страницу или просмотрев трафик сабмита формы. Хорошая новость в том, что INPUT_ID всегда постоянный. Плохая — по-умолчанию он генерируется рандомно и представляет собой строку символов. Ходить и каждый раз её узнавать не самое веселое занятие, поэтому надо задать этот ID вручную через свойство id:

input message: 'Ready to go?', id: 'go'

Тут стоит обратить внимание, что реальный ID всегда будет начинаться с заглавной буквы. В итоге в моём случае запрос оказался следующим:

http://localhost:8080/job/pipeline-test/16/input/Go/proceedEmpty?token=f7614a8510b59569347714f53ab1e764

Дополнительной плюшкой механизма input-ов является возможность задавать дополнительные параметры, которые потом можно использовать:

def testPassParamInput = input(
     id: 'testPassParam', message: 'Pass param?', parameters: [
     [$class: 'StringParameterDefinition', defaultValue: 'hello', description: 'Test parameter', name: 'testParam']
    ])

Для этого мы можем определить некоторый параметр, который хотим передавать в дочерний job, в нашем случае testParam. Соответственно мы можем переписать вызов дочернего job-а для того, чтобы он принимал этот параметр:

build job: 'hello-task', parameters: [[$class: 'StringParameterValue', name: 'CoolParam', value: testPassParamInput]]

Обратите внимание, что в value передаётся весь объект целиком. В случае если параметров будет несколько, необходимо указывать явно какой параметр нужно взять:

testPassParamInput['testParam']

В интерфейсе у нас теперь будет как-то так:

alt

Но нам опять таки GUI малоинтересен и идём дальше изучать API. Чтобы пробросить параметр через обычный HTTP, нужно использовать другой метод: proceed:

JENKINS_ROOT_URL/job/JOB_NAME/BUILD_NUMBER/input/INPUT_ID/proceed?token=YOUR_TOKEN

При этом нам нужно передать форму с параметрами и их значениями. Для этого в первую очередь сформируем правильный JSON:

{
    "parameter" : [
        {
            "name" : "testParam",
            "value" : "new cool value"
        }
    ]
}

Здесь name — имя параметра, а value соответственно его значение.

Теперь встаёт вопрос как его правильно передать, и тут у непосвященных начинаются проблемы. Так как Jenkins реализует у себя JSONP, то этот контент не передать непосредственно в теле запроса. Вместо этого, его необходимо обернуть в форму и запихнуть в поле json. Если делать это через Postman, то итоговый запрос будет выглядеть следующим образом:

----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="json"

{ "parameter": [ { "name" : "testParam", "value" : "new cool value" } ] }
----WebKitFormBoundaryE19zNvXGzXaLvS5C

Не слишком красиво, но это работает. Теперь в логах мы сможем наблюдать, что действительно действия были подтверждены пользователем (в нашем случае администратором):

Hello World 2
[Pipeline] input
Ready to go?
Proceed or Abort
Approved by admin
[Pipeline] input
Input requested
Approved by admin
[Pipeline] stage (Stage 3)
Entering stage Stage 3
Proceeding
[Pipeline] build (Building hello-task)
Scheduling project: hello-task
Starting building: hello-task #11

В случае, когда внешняя система добро не даёт, ей нужно дёрнуть метод abort:

JENKINS_ROOT_URL/job/JOB_NAME/BUILD_NUMBER/input/INPUT_ID/abort?token=YOUR_TOKEN

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

Rejected by admin
Finished: ABORTED

Ну и напоследок. Не забывайте, что все эти запросы требуют basic-авторизации, токена и crumbs. Последние можно получить по адресу: JENKINS_ROOT_URL/crumbIssuer/api/json:

{
  "_class":"hudson.security.csrf.DefaultCrumbIssuer",
  "crumb":"f4c1a2dc6a67c70e66c35c807e542f4e",
  "crumbRequestField":"Jenkins-Crumb"
}

После этого нужно вставить в заголовки http-запроса новый заголовок Jenkins-Crumb и его значение из поля crumb.

Резюме

В текущем виде Pipeline Plugin даёт возможности по встраиванию управляющих воздействий со стороны внешних систем, что открывает массу возможностей для автоматизации доставки ПО при сложных и переходных процессах внедрения. В то же время, хочется всё-таки более очевидного и красивого API для этих действий.

Автор: aatarasoff

Источник

Поделиться новостью

* - обязательные к заполнению поля