Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Senior: питання на співбесіді з теми «Хуки, CI й автоматизація»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

Ручний git bisect вимагає на кожному кроці перевірити код і сказати good чи bad. git bisect run робить це сам: на кожному кроці запускає скрипт і читає його код завершення.

git bisect start HEAD v2.3.0      # погана ревізія, потім добра
git bisect run php artisan test --compact --filter=VacancyExportTest
git bisect reset

На 1000 комітах це близько 10 запусків тесту замість ручного пошуку.

Як Git трактує код завершення:

Код Значення
0 коміт добрий
1-127, крім 125 коміт поганий
125 коміт неможливо перевірити - пропустити (skip)
інше (наприклад, 128 і більше) перервати bisect

Скрипт для реальних умов - стара версія коду може потребувати іншої підготовки:

#!/bin/sh
# bisect.sh
composer install --no-interaction --quiet || exit 125   # не збирається - пропустити
php artisan migrate:fresh --env=testing --force --quiet || exit 125

php artisan test --compact --filter=VacancyExportTest
  • exit 125 - коміт, на якому проєкт не збирається з не пов'язаних причин, не вважається «поганим» і не збиває пошук;
  • база - тестова (--env=testing), щоб не зачепити робочу.

Тест, якого ще не було в історії: часто баг виявили сьогодні, а тест написали теж сьогодні - у старих комітах його немає. Рішення - тримати тест поза робочим деревом і копіювати його на кожному кроці:

cp /tmp/RegressionTest.php tests/Feature/RegressionTest.php
php artisan test --compact tests/Feature/RegressionTest.php
status=$?
rm tests/Feature/RegressionTest.php
exit $status

Корисні деталі:

  • git bisect start --first-parent - іти лише по комітах злиття основної гілки: знайти, який PR зламав, без заходу в проміжні коміти гілок;
  • git bisect log і git bisect replay - зберегти й повторити сесію;
  • терміни good/bad можна замінити (--term-old=fast --term-new=slow) - bisect підходить і для пошуку регресії продуктивності, не лише помилки.

Докладніше в документації: git bisect run

Серверні хуки виконуються на сервері, що приймає push, і можуть відхилити його. На відміну від клієнтських, їх не можна пропустити через --no-verify - розробник їх не контролює.

Основні серверні хуки:

Хук Коли Що може
pre-receive один раз на весь push, до оновлення посилань відхилити весь push
update для кожного оновлюваного посилання окремо відхилити окрему гілку чи тег
post-receive після успішного оновлення сповіщення, запуск деплою; відхилити вже не може

pre-receive отримує на стандартний ввід рядки <старий SHA> <новий SHA> <посилання>:

#!/bin/sh
zero=0000000000000000000000000000000000000000
while read old new ref; do
    # заборонити force push у main: старий коміт має бути предком нового
    if [ "$ref" = "refs/heads/main" ] && [ "$old" != "$zero" ]; then
        if ! git merge-base --is-ancestor "$old" "$new"; then
            echo "Force push у main заборонено"
            exit 1
        fi
    fi
done

Вивід хука повертається клієнту з префіксом remote:.

Де це доступно:

  • власний сервер Git, GitLab self-managed, GitHub Enterprise Server - так;
  • github.com - власні серверні хуки встановити не можна. Їхню роль виконують rulesets (заборона force push, обов'язкові підписи, обмеження на шляхи й розмір файлів) і push protection для секретів.

Серверні хуки проти CI:

Серверний хук CI
коли синхронно, під час push асинхронно, після push
результат push відхилено, коду на сервері немає код уже на сервері, PR позначено червоним
час секунди - інакше push «зависає» хвилини, без обмежень
що перевіряти політики: формат, заборонені файли, права, підписи збирання, тести, аналіз

Правило: у хуки - швидкі й безумовні політики, порушення яких не має з'явитися на сервері навіть у гілці (секрети, величезні бінарні файли, заборонені операції). Усе, що довге чи потребує середовища, - в CI. Хук, що запускає тести, робить кожен push хвилинним і ламає роботу всієї команди при збоях.

Докладніше в документації: githooks: pre-receive

Якщо повідомлення комітів дотримуються Conventional Commits, з історії можна автоматично визначити, яким має бути наступний номер версії і що писати в змінлог.

Правила семантичного версіонування з комітів:

Коміти з останнього релізу Нова версія
лише fix: патч: 1.4.2 → 1.4.3
є feat: мінорна: 1.4.2 → 1.5.0
feat!: чи футер BREAKING CHANGE: мажорна: 1.4.2 → 2.0.0
chore:, docs:, test: релізу не потребують

release-please (від Google) працює через релізний pull request:

  1. після кожного злиття в main дія аналізує нові коміти;
  2. відкриває чи оновлює PR «chore(main): release 1.5.0» зі зміненим CHANGELOG.md і номером версії;
  3. команда зливає цей PR, коли хоче випустити реліз;
  4. дія створює тег v1.5.0 і GitHub Release з нотатками.
on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v5
        with:
          release-type: php

semantic-release - альтернатива без проміжного PR: реліз публікується одразу після злиття. Більше автоматизації, менше контролю над моментом релізу.

Що потрібно для роботи:

  • дисципліна повідомлень - перевірка в commit-msg і в CI; при злитті через squash - перевірка заголовка PR, бо він стає повідомленням коміту;
  • осмислені коміти: fix: typo в історії main потрапить у змінлог;
  • права боту на запис і створення PR; якщо релізний PR має запускати CI, потрібен токен GitHub App чи PAT - події від стандартного GITHUB_TOKEN не запускають інші workflow.

Обмеження: змінлог з комітів - для розробників. Для користувачів продукту часто потрібні людські нотатки до релізу, і автоматичний текст - лише чернетка для них.

Докладніше в документації: release-please

У великому проєкті з десятками залежностей бот, що відкриває окремий PR на кожне оновлення, швидко стає шумом, який команда ігнорує. Мета - щоб рутинні оновлення проходили без людей, а увагу отримували лише ризиковані.

Renovate - гнучкіша альтернатива Dependabot: підтримує більше екосистем, складні правила, групування, автозлиття й «панель залежностей» (Dependency Dashboard) - задачу зі списком усіх очікуваних оновлень.

{
    "extends": ["config:recommended"],
    "schedule": ["before 6am on monday"],
    "minimumReleaseAge": "3 days",
    "lockFileMaintenance": { "enabled": true },
    "packageRules": [
        {
            "matchUpdateTypes": ["patch", "minor"],
            "matchCurrentVersion": "!/^0/",
            "automerge": true
        },
        {
            "matchPackageNames": ["laravel/**", "livewire/**", "filament/**"],
            "groupName": "Laravel ecosystem"
        },
        {
            "matchDepTypes": ["require-dev"],
            "groupName": "dev dependencies",
            "automerge": true
        },
        {
            "matchUpdateTypes": ["major"],
            "dependencyDashboardApproval": true
        }
    ]
}

Що тут відбувається:

  • автозлиття патчів і мінорних версій стабільних пакетів - якщо CI зелений, PR вливається сам;
  • групи - пов'язані пакети оновлюються разом (екосистема Laravel), а не десятком окремих PR;
  • мажорні версії - лише після схвалення на панелі, бо вони потребують міграції коду;
  • minimumReleaseAge - не брати версію, опубліковану щойно: зламані й скомпрометовані релізи зазвичай відкликають за перші дні;
  • lockFileMaintenance - періодично оновлює транзитивні залежності в composer.lock, які інакше застарівають непомітно;
  • розклад - PR з'являються в певний час, а не протягом усього тижня.

Передумови для автозлиття:

  • тести, яким можна довіряти - інакше автозлиття постачає баги в main;
  • обов'язкові перевірки в захисті гілки - бот не повинен мати змоги злити червоний PR;
  • моніторинг після деплою - швидко помітити регресію й відкотити.

Dependabot чи Renovate: Dependabot вбудований у GitHub і простіший у налаштуванні (групи, cooldown, ігнорування є й у ньому); Renovate виграє на складних правилах, монорепозиторіях і поза GitHub (GitLab, Bitbucket).

Докладніше в документації: Renovate: параметри конфігурації