Отримуємо управління назад у Jenkins Pipeline

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 для цих дій.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.