![](https://habrastorage.org/getpro/habr/upload_files/b19/5b0/987/b195b0987889933a4a63772a07193adb.jpeg)
GitHub Actions — инструмент для автоматизации рутинных действий с вашего пакета на GitHub.
Из личного опыта расскажу, как без опыта и знаний о настройке CI, я научился автоматизировать рутину в своем Open Source проекте всего за день и что на самом деле это действительно не так страшно и сложно, как многие думают.
GitHub предоставляет действительно удобные и рабочие инструменты для этого.
План действий
настроим CI в GitHub Actions для небольшого проекта на PHP
научимся запускать тесты в матрице с покрытием (зачем это нужно также расскажу)
создадим ботов, которые будут назначать ревьюющих / исполнителей, выставлять метки для PR-s (на основе измененных файлов), а по окончании ревью и проверок в Check Suite будут автоматом мержить наши PR, а сами ветки будут удаляться автоматически.
подключим бота, который будет создавать релизы, которые автоматически будут пушиться в packagist.
В общем, мы постараемся минимизировать ручной труд так, чтобы от вас, как от автора вашего Open-Source пакета, оставалось только писать код, ревьюить и апрувить пулл-реквесты, а все остальное за вас делали боты. А если вы умеете делегировать, то и ревью и написание кода можно также возложить на плечи ваших соратников, проект будет и развиваться без вашего участия.
Настройка CI
Сильно углубляться в тонкости настройки CI для запуска тестов я не буду, на хабре достаточно постов об этом, но для небольшого проекта на PHP с базой данных Postgres примера моего CI вполне хватит. Лишнее можно удалить, названия и ключи можно менять на ваш вкус.
Создайте файл примерно с таким содержимым:
.github/workflows/ci.yml
name: CI
on:
push:
branches:
- master
pull_request:
types:
- opened
- reopened
- edited
- synchronize
env:
COVERAGE: '1'
php_extensions: 'pdo, pdo_pgsql, pcntl, pcov, ...'
key: cache-v0.1
DB_USER: 'postgres'
DB_NAME: 'testing'
DB_PASSWORD: 'postgres'
DB_HOST: '127.0.0.1'
jobs:
lint:
runs-on: '${{ matrix.operating_system }}'
timeout-minutes: 20
strategy:
matrix:
operating_system: ['ubuntu-latest']
php_versions: ['7.4']
fail-fast: false
env:
PHP_CS_FIXER_FUTURE_MODE: '0'
name: 'Lint PHP'
steps:
- name: 'Checkout'
uses: actions/checkout@v2
- name: 'Setup cache environment'
id: cache-env
uses: shivammathur/cache-extensions@v1
with:
php-version: '${{ matrix.php_versions }}'
extensions: '${{ env.php_extensions }}'
key: '${{ env.key }}'
- name: 'Cache extensions'
uses: actions/cache@v1
with:
path: '${{ steps.cache-env.outputs.dir }}'
key: '${{ steps.cache-env.outputs.key }}'
restore-keys: '${{ steps.cache-env.outputs.key }}'
- name: 'Setup PHP'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php_versions }}
extensions: '${{ env.php_extensions }}'
ini-values: memory_limit=-1
tools: pecl, composer
coverage: none
- name: 'Setup problem matchers for PHP (aka PHP error logs)'
run: 'echo "::add-matcher::${{ runner.tool_cache }}/php.json"'
- name: 'Setup problem matchers for PHPUnit'
run: 'echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"'
- name: 'Install PHP dependencies with Composer'
run: composer install --prefer-dist --no-progress --no-suggest --optimize-autoloader
working-directory: './'
- name: 'Linting PHP source files'
run: 'composer lint'
test:
strategy:
fail-fast: false
matrix:
operating_system: ['ubuntu-latest']
postgres: [11, 12]
php_versions: ['7.3', '7.4', '8.0']
experimental: false
include:
- operating_system: ubuntu-latest
postgres: '13'
php_versions: '8.0'
experimental: true
runs-on: '${{ matrix.operating_system }}'
services:
postgres:
image: 'postgres:${{ matrix.postgres }}'
env:
POSTGRES_USER: ${{ env.DB_USER }}
POSTGRES_PASSWORD: ${{ env.DB_PASSWORD }}
POSTGRES_DB: ${{ env.DB_NAME }}
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
name: 'Test / PHP ${{ matrix.php_versions }} / Postgres ${{ matrix.postgres }}'
needs:
- lint
steps:
- name: 'Checkout'
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 'Install postgres client'
run: |
sudo apt-get update -y
sudo apt-get install -y libpq-dev postgresql-client
- name: 'Setup cache environment'
id: cache-env
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.php_versions }}
extensions: ${{ env.php_extensions }}
key: '${{ env.key }}'
- name: 'Cache extensions'
uses: actions/cache@v1
with:
path: '${{ steps.cache-env.outputs.dir }}'
key: '${{ steps.cache-env.outputs.key }}'
restore-keys: '${{ steps.cache-env.outputs.key }}'
- name: 'Setup PHP'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php_versions }}
extensions: ${{ env.php_extensions }}
ini-values: 'pcov.directory=src, date.timezone=UTC, upload_max_filesize=20M, post_max_size=20M, memory_limit=512M, short_open_tag=Off'
coverage: pcov
tools: 'phpunit'
- name: 'Install PHP dependencies with Composer'
run: composer install --prefer-dist --no-progress --no-suggest --optimize-autoloader
working-directory: './'
- name: 'Run Unit Tests with PHPUnit'
continue-on-error: ${{ matrix.experimental }}
run: |
sed -e "s/\${USERNAME}/${{ env.DB_USER }}/" -e "s/\${PASSWORD}/${{ env.DB_PASSWORD }}/" -e "s/\${DATABASE}/${{ env.DB_NAME }}/" -e "s/\${HOST}/${{ env.DB_HOST }}/" phpunit.xml.dist > phpunit.xml
./vendor/bin/phpunit --verbose --stderr --coverage-clover build/logs/clover.xml
working-directory: './'
- name: 'Upload coverage results to Coveralls'
if: ${{ !matrix.experimental }}
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_PARALLEL: true
COVERALLS_FLAG_NAME: php-${{ matrix.php_versions }}-postgres-${{ matrix.postgres }}
run: |
./vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v
coverage:
needs: test
runs-on: ubuntu-latest
name: "Code coverage"
steps:
- name: 'Coveralls Finished'
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
Расскажу лишь в кратце, в этом конфиге 3 основных шага (lint, tests и coverage)
lint - через композер подключаем любой инструмент для линтирования, по сути это просто скрипт проверяющий, что ваш код написан с учетом современных тенденций и правил, оформлен как полагается, присутствуют нужные отступы и тд.
Если код не соответствует code style проекта, джобка падает и CI дальше не запускается. В данном примере, я использую линтер с правилами от umbrellio/code-style-php, а сами скрипты запуска описаны так (первый для проверки, второй для авто фиксов для локального использования):
"scripts": {
"lint": "ecs check --config=ecs.yml .",
"lint-fix": "ecs check --config=ecs.yml . --fix"
}
test - тестируем наше приложение в матрице следующего ПО (os, postgres и php), а через опцию include добавляем что-то дополнительное (важно сохранить структуру ключей матрицы).
В целом тут тоже ничего нет сложного, разве что два момента:
опция experimental (к слову назвать опцию можно как угодно) для матрицы нужна для того, чтобы падающие джобки в экспериментальном окружении не фейлили CI, например, когда вы добавляете поддержку новой версии PHP, и не ставите самоцелью решать упавшие тесты или покрытие прям щас. Такие джобки игнорируются (если падают).
строки с
sed -e "s/\${USERNAME}/${{ env.DB_USER }}/"...
нужны для того, чтобы переменные подключения к БД были записаны из файла phpunit.xml.dist с плейсхолдерами в phpunit.xml, это не панацея, вы можете использовать переменные окружения ENV, но на всякий случай файл доступен тут:
phpunit.xml.dist
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
<php>
<env name="APP_ENV" value="testing"/>
<ini name="error_reporting" value="-1" />
<var name="db_type" value="pdo_pgsql"/>
<var name="db_host" value="${HOST}" />
<var name="db_username" value="${USERNAME}" />
<var name="db_password" value="${PASSWORD}" />
<var name="db_database" value="${DATABASE}" />
<var name="db_port" value="5432"/>
</php>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
<exclude>
<file>./src/.meta.php</file>
</exclude>
</whitelist>
</filter>
<testsuites>
<testsuite name="Test suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>
coverage - т.к. тестирование и покрытие также происходит в матрице, т.к. часть кода может быть написана под одну версию Postgres, а другая под другую и оформлено в виде условий в вашем коде, то покрыть на 100% за одну итерацию может быть невозможно. К сожалению, composer, в отличие от Bandler-а от Ruby, так делать не умеет.
Но т.к. я перфекционист и мне нужен badge:100% coverage, в моем случае используется матрица покрытия и затем, отправленные отчеты о покрытии, мержатся в один. Например, coveralls.io поддерживает обьединенный кавераж.
Теперь когда у нас есть CI, мы попробуем подключить ботов для автоматизации нашей рутины.
Авто-назначение меток (labels)
Для подключения бота создайте два файла (конфиг и скрипт):
.github/labeler.config.yml
type:build:
- ".github/**/*"
- ".coveralls.yml"
- ".gitignore"
- "ecs.yml"
- "phpcs.xml"
dependencies:
- "composer.json"
- "composer.lock"
type:common
- "src/**/*"
type:tests:
- 'tests/**/*'
- 'phpunit.xml.dist'
- 'tests.sh'
theme:docs:
- "README.md"
- "LICENSE"
- "CONTRIBUTING.md"
- "CODE_OF_CONDUCT.md"
Тут по сути мы описываем маппинг меток к файлам и директориям вашего пакета, в зависимости от того, какие файлы будут изменены в рамках вашего PR, такие метки будут автоматически выставлены к PR.
Метки нужны для того, чтобы в последствии мы могли на их основании генерировать Summary для наших релизов и определять степень важности PR (будет ли это patch, minor или major). Вообще говоря, метки помогают визуально категоризировать пулл-реквесты, что очень удобно, когда их (pull-реквестов) много.
.github/workflows/labeler.yml
name: "Auto labeling for a pull request"
on:
- pull_request_target
jobs:
triage:
name: "Checking for labels"
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@main
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
configuration-path: ".github/labeler.config.yml"
Авто-назначение ревьюеров и исполнителей
Для подключения бота создайте два файла (конфиг и скрипт):
.github/assignee.config.yml
addReviewers: true
numberOfReviewers: 1
reviewers:
- pvsaintpe
addAssignees: true
assignees:
- pvsaintpe
numberOfAssignees: 1
skipKeywords:
- wip
- draft
Тут мы по сути описываем, кто и в каком кол-ве будет назначаться в качестве ревьюеров, и просто перечисляем логины пользователей с GitHub.
.github/workflows/assignee.yml
name: 'Auto assign assignees or reviewers'
on: pull_request
jobs:
add-reviews:
name: "Auto assignment of a assignee"
runs-on: ubuntu-latest
steps:
- uses: kentaro-m/auto-assign-action@v1.1.2
with:
configuration-path: ".github/assignee.config.yml"
Авто-мержирование проверенных PR
Для подключения бота создайте файл скрипта с содержимым:
.github/workflows/auto_merge.yml
name: 'Auto merge of approved pull requests with passed checks'
on:
pull_request:
types:
- labeled
- unlabeled
- synchronize
- opened
- edited
- ready_for_review
- reopened
- unlocked
pull_request_review:
types:
- submitted
check_suite:
types:
- completed
status: {}
jobs:
automerge:
runs-on: ubuntu-latest
steps:
- name: 'Automerge PR'
uses: "pascalgn/automerge-action@v0.12.0"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MERGE_METHOD: 'squash'
MERGE_LABELS: "approved,!work in progress"
MERGE_REMOVE_LABELS: "approved"
MERGE_COMMIT_MESSAGE: "pull-request-description"
MERGE_RETRIES: "6"
MERGE_RETRY_SLEEP: "10000"
UPDATE_LABELS: ""
UPDATE_METHOD: "rebase"
MERGE_DELETE_BRANCH: false
Из важного тут только то, что мержится будут только те PR, у которых будет выставлена метка approved, а также если все проверки в CheckSuite будут пройдены.
Мержить будем через Squash, чтобы была красивая история коммитов.
Авто-апрув отревьюенных PR
Когда ревьюющий ставит аппрув в PR, будем автоматом проставлять метку approved, создайте файл скрипта с содержимым:
.github/workflows/auto_approve.yml
on: pull_request_review
name: 'Label approved pull requests'
jobs:
labelWhenApproved:
name: 'Label when approved'
runs-on: ubuntu-latest
steps:
- name: 'Label when approved'
uses: pullreminders/label-when-approved-action@master
env:
APPROVALS: "1"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ADD_LABEL: "approved"
REMOVE_LABEL: "awaiting review"
Авто-выпуск релизов с ченджлогом
Для подключения бота создайте два файла (конфиг и скрипт) с содержимым:
.github/release-drafter.yml
template: |
## Changes
$CHANGES
change-template: '- **$TITLE** (#$NUMBER)'
version-template: "$MAJOR.$MINOR.$PATCH"
name-template: '$RESOLVED_VERSION'
tag-template: '$RESOLVED_VERSION'
categories:
- title: 'Features'
labels:
- 'feature'
- 'type:common'
- title: 'Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- 'hotfix'
- 'dependencies'
- title: 'Maintenance'
labels:
- 'type:build'
- 'refactoring'
- 'theme:docs'
- 'type:tests'
change-title-escapes: '\<*_&'
version-resolver:
major:
labels:
- major
- refactoring
minor:
labels:
- feature
- minor
- type:common
patch:
labels:
- patch
- type:build
- bug
- bugfix
- hotfix
- fix
- theme:docs
- type:tests
default: patch
В зависимости от меток, бот будет увеличивать либо MAJOR, либо MINOR, либо версию PATCH
.github/workflows/release_drafter.yml
name: Release Drafter
on:
push:
branches:
- master
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
with:
publish: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Теперь нужно провести некоторые настройки в GitHub Settings вашего проекта
Настройка Check Suite в GitHub
По умолчанию ветки в GitHub никак не ограничены, и пушить в них может каждый, кто имеет доступ на запись, но если вы хотите, чтобы код был красивый, чтобы код был покрыт на 100%, и у вас есть прочие хотелки, необходимо поставить ограничения и настроить Check Suite.
Пример, где настраиваются ограничения веток
![](https://habrastorage.org/getpro/habr/upload_files/7d2/fc5/075/7d2fc50754855cf7297b9b7f9e064c41.png)
Выберите основную ветку и создайте правило. Из того, на что следует обратить внимание, это следующие моменты:
Настройка approvals
По сути, тут мы настраиваем кол-во людей, которые должны посмотреть PR, будут ли сбрасываться апрувы, после появления новых коммитов, а также необходимо ли участие Code Owners в ревью.
Пример, как настраиваются approvalls
![](https://habrastorage.org/getpro/habr/upload_files/48c/ab0/8e4/48cab08e447532177a8dbda23e441b51.png)
Настройка обязательных проверок для Check Suite
Все наши проверки (в CI это джобки, в основном, но и другие интеграции тоже, например, Coveralls / Scrutinizer, и прочие анализаторы кода), могут быть как обязательными или необязательными.
Если проверка обязательная, то мержирование PR будет заблокировано пока все проверки не будут пройдены.
Пример, как настроить Check Suite для ветки
![](https://habrastorage.org/getpro/habr/upload_files/dc4/80d/a63/dc480da63540157f681e3edc51b0ce9f.png)
Автоматически удаляем ветки после мержа
Чтобы у нас была красивая история коммитов, а также чтобы не удалять вручную ветки после мержа, в Settings => Options нужно разрешить только Squash, если вы хотите красивую историю коммитов и включить опцию "Automatically delete head branches"
Пример настройки тут
![](https://habrastorage.org/getpro/habr/upload_files/8ef/e24/16c/8efe2416ce2aa67fb293478b3890af11.png)
Настройка веб-хука для packagist.org
Тут все стандартно, на сайте packagist есть инструкция, но для полноты поста выложу тоже.
Пример, как настроить webhook packagist
![](https://habrastorage.org/getpro/habr/upload_files/804/bd3/912/804bd39124843e8fd9d4d05a2d2974a8.png)
Секретный ключ можно взять на packagist в настройках вашего профиля (Show Api Token).
Таким образом, если вы поддерживаете достаточное кол-во OpenSource проектов, и в каждом из них есть некоторое количество активных Contributor-ов (с правами записи), вы можете настроить CI так, что сообщество будет само писать код, а ваши доверенные лица будут ревьюить, общий workflow будет соблюден.
Вы даже можете в coveralls / scrutinizer настроить правила, чтобы Check Suite падал если % покрытия кода меньше 100%, а в Readme напичкать баджиками для красоты, например так:
![](https://habrastorage.org/getpro/habr/upload_files/bdc/71a/64c/bdc71a64c5c311c760e5ae303170d347.png)
Буду рад, если мой туториал будет кому-то полезен, т.к. перед написанием данного поста я впервые столкнулся с GitHub Actions, я не DevOps и настройкой CI не занимаюсь, самому пришлось прогуглить не один сайт, чтобы настроить такой workflow, который был нужен мне.
TyLeRRR
Спасибо за статью. Кстати, auto-delete веток также можно включить через чекбокс в настройках репозитория.
pvsaintpe Автор
Да, все верно, в своем посте я об этом говорил, под спойлером. Правда, в интернете я видел кастомные решения через экшены, и изначально хотел об этом написать, но потом решил, что зачем городить лишнего бота, если есть рабочие решения из коробки GitHub.