Шаблони робочих процесів


Цей документ пояснює, як налаштувати робочі процеси безперервної інтеграції (Continuous Integration, CI) для мовного треку Exercism за допомогою GitHub Actions (GHA). У ньому зібрано найкращі практики та приклади, які допоможуть створити власні швидкі, надійні та стійкі робочі процеси CI. Робочі процеси GHA з цієї теки можна пристосувати до будь-якої системи CI, адже базова структура залишиться незмінною.

У цьому документі ми:

  • опишемо ідеальний робочий процес CI
  • обговоримо міркування та рекомендації
  • надамо кілька шаблонів для використання
  • залишимо посібник з переходу з Travis

Приклад реалізації цих файлів робочих процесів можна знайти в exercism/javascript.

ДОПОМОГА: а роботи тут дуже багато 😓

Решта документа пояснює, як працюють ці робочі процеси. Якщо часу обмаль і хочеться просто перейти з Travis або Circle на GHA, не оптимізуючи PR-скрипти, радимо зазирнути до нашого посібника на ~10 хвилин про перехід з Travis.

Дії CI для треку

Ось рекомендовані дії для перевірки цілісності вмісту репозиторію:

  1. Лінтинг configlet, щоб перевірити config.json
  2. перевірка на заглушки
  3. перевірка документації (v3 вимагає нових файлів; можливо, це перейде до configlet)
  4. лінтинг вправ із конфігурацією «maintainers»
  5. тестування вправ за допомогою файлів example/exemplar (може включати крок збірки)

Можуть бути й дії, специфічні для треку. Наприклад:

  1. перевірка цілісності конфігурацій вправ
  2. перевірка форматування файлів вправ

А можливо, захочеться й додаткових перевірок для зручності, як-от:

  1. переконатися, що існує CONTRIBUTING
  2. переконатися, що для залежностей є адекватний lockfile
  3. переконатися, що посилання всередині markdown-файлів робочі
  4. ...

Рекомендації

Як часто запускати перевірки

Для кожної дії подумаймо, як часто її слід запускати.

  • Лінтинг configlet настільки важливий (адже трек може зламатися, якщо зламається config.json), що його, ймовірно, варто запускати завжди, але достатньо робити це раз на коміт.
  • Перевірку наявності та цілісності файлів достатньо запускати раз на коміт.
  • Якщо трек має працювати з кількома версіями середовища виконання чи компілятора, збірку й тестування вправ слід запускати для кожної підтримуваної версії.
  • У PR, ймовірно, достатньо запускати дії лише для доданих або змінених файлів, але оскільки файл може впливати на вправу, безпечніше запускати дії для всієї вправи, якщо змінився хоч один її файл.

Дуже корисно зробити дії, які мають запускатися, доступними й локально. Це означає, що скрипти, які виконують усю роботу, можна запускати й вручну. Щоб досягти цього, не вбудовуйте дію безпосередньо у файли робочих процесів, а створіть окремий скрипт. Наприклад, перевірку на заглушки можна повністю вписати прямо у файл робочого процесу, але рекомендація тут - створити натомість новий виконуваний скрипт scripts/ci-check.

«Але ж команда дуже коротка, наприклад eslint . --ext ts --ext tsx».

Коли цю команду потрібно оновити, її доводиться оновлювати в усіх місцях: у документації, у файлах робочих процесів і в головах супровідників. Винесення цього в скрипт розвʼязує все це. Читати файл робочого процесу також може дуже лякати.

Перевірки в PR, де змінюються вправи

Скрипти scripts/pr і scripts/pr-check (див. шаблони) запускаються з кількома аргументами, по одному для кожного файлу, зміненого або доданого в цьому PR. Наприклад, якщо two-fer оновлено, виклик може мати такий вигляд:

scripts/pr exercises/two-fer/README.md exercises/two-fer/.meta/example.ext

Рекомендуємо запускати дії для зміненої вправи, а не для зміненого файлу. Річ у тім, що зміна одного файлу зазвичай тягне за собою зміни для всієї вправи (згадаймо конфігурацію, пакети).

Ще не на часі? / Складно?

Перш ніж упроваджувати цю оптимізацію, її можна спокійно проігнорувати! Посібник з переходу натякає, що її можна додати на пізнішому етапі. Якщо вхідні аргументи ігноруються, усі перевірки виконуватимуться для всіх вправ. Це цілком нормально. Просто це триватиме довше.

Перевірки цілісності

Якщо в треку є один «верхньорівневий» файл залежностей та/або інші файли конфігурації, додайте крок перевірки цілісності (він існує поряд із scripts/sync або bin/sync, який копіює всі файли конфігурації до всіх вправ), що гарантує, що верхньорівневі/базові файли збігаються з тими, які скопійовано до тек вправ. Тоді залежності можна оновлювати й синхронізувати по всьому репозиторію, і ми можемо бути певні, що всі вправи мають однакову конфігурацію.

Поширений спосіб зробити це - скористатися контрольною сумою. В Ubuntu (і багатьох інших дистрибутивах Linux) є інструмент sha1sum, але підійде будь-який спосіб хешування чи зведення файлу конфігурації (md5, sha1, crc32) до значення контрольної суми:

$ sha1sum README.md
cd58091c5043bf21f00d39ff1740d8b2976deeff *README.md

Перевірки безпеки

Якщо трек використовує додаткові робочі процеси, яким потрібен доступ до токена GitHub чи інших секретів, найкраща практика - привʼязати усі дії, використані в робочому процесі, до конкретного коміту. Докладніше див. у посібнику GitHub із посилення безпеки.

Наприклад:

- uses: julia-actions/setup-julia@v1
+ uses: julia-actions/setup-julia@d26d1111976eae5f00db04f0515ab744ec9cd79e # 1.3.1

Якщо в інструментарії є lockfile-и для керування залежностями, варто додати їх до репозиторію й використовувати «заморожений lockfile» у файлах робочих процесів. Наприклад: npm ci, yarn install --frozen-lockfile і bundle install --frozen. Це гарантує, що lockfile актуальний під час зміни залежностей, і не дає шкідливим пакетам потрапити всередину.

Шаблони

У цій теці є принаймні такі шаблони:

  • configlet.yml: цей робочий процес завантажить найновіший бінарник configlet і виконає лінтинг цього репозиторію. Запускається на кожному коміті. Для PR запускається на актуальному коміті та на дереві «після злиття».
  • ci.yml: цей робочий процес запускається лише в головній гілці, раз на кожен коміт.
    1. Виконує команду «pre-check» (перевірка на заглушки, лінтинг, документація тощо) для всіх вправ
    2. Виконує команду «ci» (збірка й тестування) для кількох версій, для всіх вправ
  • pr.ci.yml: цей робочий процес запускається лише в PR, раз на кожен коміт.
    1. Виконує команду «pre-check» (перевірка на заглушки, лінтинг, документація тощо) для змінених файлів
    2. Виконує команду «ci» (збірка й тестування) для кількох версій, для змінених вправ

Робочі процеси, не повʼязані з PR, також можна запускати через workflow_dispatch.

Угорі кожного файлу зазначено, які «скрипти» мають бути доступні. Якщо потрібно, щоб це були бінарники, замініть scripts/xxx на bin/xxx. Деякий інструментарій вимагає, щоб бінарники лежали в теці bin.

  • scripts/ci: скрипт, який має збирати й тестувати всі вправи, запускаючи зразкові рішення проти тестів
  • scripts/ci-check: скрипт, який має виконувати лінтинг усіх вправ і, за бажанням, перевіряти на заглушки, цілісність конфігурації тощо
  • scripts/pr: те саме, що й scripts/ci, але має запускатися лише для вправ, визначених із переданих на вхід шляхів
  • scripts/pr-check: те саме, що й scripts/ci-check, але має запускатися лише для файлів або вправ, визначених із переданих на вхід шляхів

Розвʼязання проблем

Якщо виникнуть проблеми або захочеться, щоб хтось переглянув робочі процеси, зверніться до команди @exercism/github-actions.

Змінено верхньорівневий файл, який має запускати CI для всіх вправ

На момент написання pr.ci.yml дозволяє лише тестування «за розширенням». В ідеалі його варто оновити так, щоб він завжди запускався, коли змінюються певні файли (наприклад, бінарник для запуску тестів). Утім, такі зміни зазвичай рідкісні й робляться супровідниками, тож те, що ci.yml завжди запускається в головній гілці для всього, імовірно, достатньо безпечно.

Створено файл scripts/xxx у Windows, і тепер він не працює в {іншій ОС}

За замовчуванням файли, створені у Windows, не мають у git-index вбудованих метаданих про свою виконуваність, бо модель дозволів у Windows інша. Git за замовчуванням використовує метадані git-index, щоб визначити, чи має файл бути виконуваним у POSIX-системах, і таким чином робить файл scripts/xxx НЕ виконуваним.

git update-index --chmod=+x scripts/xxx
git commit -m "Make scripts/xxx executable"