GitHub Actions. Начало работы, часть 1 из 2
15.06.2022
Теги: CLI • Git • GitHub • Linux • Web-разработка • Конфигурация • Сервер
GitHub Actions — бесплатная для публичных репозиториев система непрерывной интеграции. Позволяет запустить проверку кода линтером и тестами, выполнить деплой проекта, опубликовать новую версию пакета, отправлять оповещения в мессенджер о событиях в репозитории и многое другое. Для начала создадим репозиторий и разместим в нем файлы проекта — хотя бы README.md
.
Workflow файл
Общий принцип работы Github Actions такой — в репозитории создается директория .github/workflows
, внутри которой размещаются yml-файлы, с описанием шагов, которые нужно выполнить на различные события. Давайте в репозитории перейдем на вкладку Actions
— там есть много образцов yml-файлов под разные задачи, которые можно использовать. Но мы создадим yml-файл сами, для этого переходим по ссылке «Set up a workflow yourself» — будет создана директория .github/workflows
и открыт на редактирование файл main.yml
.
# имя, которое будет показано в интерфейсе github.com name: learn-github-actions # список событий, при которых будут запускаться задания on: # на push и pull_request, только для ветки master push: branches: [master] pull_request: branches: [master] # позволяет запускать workflow вручную с вкладки actions в интерфейсе github.com workflow_dispatch: # одно или несколько заданий, которые могут быть запущены параллельно или последовательно jobs: # у этого workflow всего одна задача single single: # задание будет выполняться на последней версии Ubuntu runs-on: ubuntu-latest # шаги задания запускаются последовательно steps: # при выполнении задания будет доступен наш репозиторий - uses: actions/checkout@v3 # запуск одной shell-команды - name: Run step one run: echo "Single job, step one, single command" # запуск двух shell-команд - name: Run step two run: | echo "Single job, step two, command one" echo "Single job, step two, command two"
Workflow имеет имя name
, условия для запуска on
, одно или несколько заданий jobs
. У каждого задания есть имя, каждое задание содержит один или несколько шагов steps
. По умолчанию все задания выполняются параллельно, однако между ними можно определить зависимость, чтобы они выполнялись последовательно. Каждое задание выполняется на определённом раннере runs-on
— временном сервере на GitHub с выбранной операционной системой (Linux, MacOS или Windows).
Доступны следующие операционные системы:
- Windows Server 2019 — для этого нужно использовать
windows-2019
- Windows Server 2016 — для этого нужно использовать
windows-2016
- Ubuntu 20.04 — для этого нужно использовать
ubuntu-20.04
- Ubuntu 18.04 — для этого нужно использовать
ubuntu-18.04
- MacOS Big Sur 11 — для этого нужно использовать
macos-11
- MacOS Catalina 10.15 — для этого нужно использовать
macos-10.15
Понятно, что внутри сервера, когда запускается workflow
, нам нужны файлы нашего репозитория. Для этого предназначена директива uses
, где указывается готовый action
, который предлагает GitHub на Marketplace. По сути, директива выполняет команду git fetch
, чтобы внутри Ubuntu получить файлы нашего репозитория в состоянии последнего коммита (той ветки, на которой произошло событие, запустившее workflow
).
Чтобы получить внутри Ubuntu всю историю репозитория для всех существующих веток
steps: # при выполнении задания будет доступен наш репозиторий - uses: actions/checkout@v3 with: # количество последних коммитов при выполнении fetch, значение ноль # означает все коммиты, по умолчанию — 1 (только последний коммит) fetch-depth: 0
Чтобы получить внутри Ubuntu другую ветку вместо той, которая вызвала этот workflow
steps: # при выполнении задания будет доступен наш репозиторий - uses: actions/checkout@v3 with: # получаем другую ветку вместо той, которая вызвала этот workflow ref: other-branch
Переменные среды
В yml-файле можно использовать переменные среды, которые будут доступны внутри Ubuntu — некоторые из них предоставляет GitHub, а остальные можно создать самостоятельно. У пользовательских переменных среды разные области видимости — переменные уровня step
видны только на этом шаге, переменные уровня job
видны только для этого задания. То есть, область действия пользовательской переменной среды ограничена элементом, в котором она определена.
name: learn-github-actions on: push: branches: [master] pull_request: branches: [master] workflow_dispatch: # эта переменная на верхнем уровне (доступна везде) env: WORKFLOW_NAME: Learn github actions jobs: single: runs-on: ubuntu-latest # это переменная уровня задания single env: job_name: single job steps: - uses: actions/checkout@v3 - name: Run step one run: echo "Single job, step one, single command" - name: Run step two # это переменные уровня этого шага env: step_name: step two command1: command one command2: command two run: | # можем использовать переменные верхнего уровня, переменные уровня задания single и уровня этого шага echo "$WORKFLOW_NAME: $job_name, $step_name, $command1" echo "$WORKFLOW_NAME: $job_name, $step_name, $command2"
При запуске очередного шага, например Run step two
— запускается новый экземпляр оболочки bash
внутри ОС Ubuntu, которой будут доступны переменные WORKFLOW_NAME
, job_name
, step_name
, command1
и command2
.
Некоторые переменные среды, которые предоставляет GitHub (подробнее здесь):
GITHUB_WORKSPACE
— путь к директории с файлами проектаGITHUB_WORKFLOW
— имяworkflow
, который сейчас работаетGITHUB_REPOSITORY
— имя владельца репозитория + имя самого репозиторияGITHUB_EVENT_NAME
— имя события, которое запустило этотworkflow
GITHUB_JOB
— идентификатор задания, которое сейчас выполняетсяGITHUB_REF
— ветка (тег), где произошло событие, запустившееworkflow
run: | echo "GITHUB_WORKSPACE = $GITHUB_WORKSPACE" echo "GITHUB_WORKFLOW = $GITHUB_WORKFLOW" echo "GITHUB_REPOSITORY = $GITHUB_REPOSITORY" echo "GITHUB_EVENT_NAME = $GITHUB_EVENT_NAME" echo "GITHUB_JOB = $GITHUB_JOB" echo "GITHUB_REF = $GITHUB_REF"
GITHUB_WORKSPACE = /home/runner/work/learn-github-actions/learn-github-actions GITHUB_WORKFLOW = learn-github-actions GITHUB_REPOSITORY = tokmakov/learn-github-actions GITHUB_EVENT_NAME = push GITHUB_JOB = single GITHUB_REF = refs/heads/master
Контекст workflow
Переменные среды доступны только внутри Ubuntu, когда идет выполнение очередного step
. Контексты — это наборы переменных, которые доступны вне run
директив. Можно думать о них как о переменных, которые могут быть встроены в сам yml-файл рабочего процесса.
name: learn-github-actions on: push jobs: single: runs-on: ubuntu-latest steps: - name: Echo GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo $GITHUB_CONTEXT
{ "token": "***", "job": "single", "ref": "refs/heads/master", "sha": "3b612aebbc7a8ba9a461da6facf81e7e0cbe0b5e", "repository": "tokmakov/learn-github-actions", "repository_owner": "tokmakov", "repository_owner_id": "8893826", "repositoryUrl": "git://github.com/tokmakov/learn-github-actions.git", "run_id": "2508294682", "run_number": "15", "retention_days": "90", "run_attempt": "1", "artifact_cache_size_limit": "10", "repository_id": "503696545", "actor_id": "8893826", "actor": "tokmakov", "workflow": "learn-github-actions", "head_ref": "", "base_ref": "", "event_name": "push", .......... }
Теперь, когда мы знаем все ключи объекта github
— можем использовать их более осмысленно. Например, запускать то или иное задание в зависимости от выполнения того или иного условия.
name: learn-github-actions on: push jobs: prod-deploy: if: ${{ github.ref == 'refs/heads/master' }} runs-on: ubuntu-latest steps: - run: echo "Deploying to prod server on branch $GITHUB_REF" test-deploy: if: ${{ github.ref == 'refs/heads/test' }} runs-on: ubuntu-latest steps: - run: echo "Deploying to test server on branch $GITHUB_REF"
Кроме объекта контекста github
, доступны объекты env
, job
, steps
, runner
, secrets
, strategy
, matrix
, needs
и inputs
.
Особености контекста
GitHub Actions предоставляет набор переменных, называемых контекстами. И аналогичный набор переменных, называемых переменными среды по умолчанию. Эти переменные предназначены для использования в разных точках рабочего процесса:
- Переменные среды по умолчанию существуют только внутри операционной системы, которая выполняет задание
workflow
- Контекст можно использовать в любой момент
workflow
, в том числе, когда переменные среды по умолчанию недоступны
С помощью контекста можно выполнить проверку еще до того, как задание будет отправлено на выполнение. И, по результату это проверки — пропустить некоторые шаги.
name: learn-github-actions on: [push, pull_request] jobs: single: runs-on: ubuntu-latest steps: - name: Step one if: ${{ github.event_name == 'push' }} run: echo "Run only on push event" - name: Step two if: ${{ github.event_name == 'pull_request' }} run: echo "Run only on pull_request event"
Задания по порядку
По умолчанию все задания выполняются параллельно, однако между ними можно определить зависимость, чтобы они выполнялись в определенной последовательности.
jobs: job-one: runs-on: ubuntu-latest steps: - run: echo "Run job-one" job-two: runs-on: ubuntu-latest needs: job-one steps: - run: echo "Run job-two (after job-one)" job-three: runs-on: ubuntu-latest needs: [job-one, job-two] steps: - run: echo "Run job-three (after job-one and job-two)"
В этом примере задание job-one
должно успешно завершиться до начала job-two
, а job-three
ожидает успешного завершения job-one
и job-two
.
job-two
зависит от задания job-one
, то когда result
задания job-one
будет skipped
— то задание job-two
тоже будет пропущено (см. ниже, что такое результат job).
Директива matrix
Директива matrix
позволяет использовать переменные (например version
), чтобы запускать одно задание несколько раз с разными значениями этой переменной. Например можем установить несколько версий Node.js
и проверить работу приложения на каждой версии.
jobs: example: strategy: matrix: version: [12, 14, 16] runs-on: ubuntu-latest steps: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.version }}
Или, проверить работу приложения на разных версиях Node.js
и на разных версиях Ubuntu:
jobs: example: strategy: matrix: os: [ubuntu-18.04, ubuntu-20.04] version: [12, 14, 16] runs-on: ${{ matrix.os }} steps: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.version }}
Step outputs
Каждый шаг задания может иметь какое-то выходное значение (или несколько значений). Каждый шаг задания имеет доступ к выходным значениям всех предыдущих шагов.
jobs: example: runs-on: ubuntu-latest steps: - name: Step one id: step-one # задаем имя и значение output для этого шага run: echo "::set-output name=step-one-output::hello from step one" - name: Step two id: step-two # 1. задаем имя и значение output для этого шага # 2. получаем доступ к значению output первого шага run: | echo "::set-output name=step-two-output::hello from step two" echo "Step one output: ${{ steps.step-one.outputs.step-one-output }}" - name: Step three id: step-three # 1. задаем имя и значение output для этого шага # 2. получаем доступ к значению output первого шага # 3. получаем доступ к значению output второго шага run: | echo "::set-output name=step-three-output::hello from step three" echo "Step one output: ${{ steps.step-one.outputs.step-one-output }}" echo "Step two output: ${{ steps.step-two.outputs.step-two-output }}"
Job outputs
У заданий тоже могут быть выходные значения и их можно сделать доступными для зависимых заданий:
jobs: job-one: runs-on: ubuntu-latest # задаем имена и значения для выходных значений этого задания outputs: output-one: ${{ steps.step-one.outputs.step-one-output }} output-two: ${{ steps.step-two.outputs.step-two-output }} steps: - id: step-one run: echo "::set-output name=step-one-output::hello from step one" - id: step-two run: echo "::set-output name=step-two-output::hello from step two" job-two: runs-on: ubuntu-latest needs: job-one steps: # получаем доступ к выходным значениям задания job-one - run: echo "${{needs.job-one.outputs.output-one}}, ${{needs.job-one.outputs.output-two}}"
Условие для step
Для шагов можно задавать условия выполнения с помощью функций failure()
, always()
, cancelled()
и success()
. Последнюю функцию применять особого смысла нет, потому что она применяется неявно — следующий шаг запускается, если все предыдущие были успешны.
jobs: job1: runs-on: ubuntu-latest steps: - name: Step one id: step-one # steps.step-one.conclusion = skipped if: ${{ false }} run: echo "run step one" - name: Step two id: step-two run: | echo "run step two" echo "step one conclusion = ${{ steps.step-one.conclusion }}" job2: runs-on: ubuntu-latest steps: - name: Step one id: step-one # steps.step-one.conclusion = failure run: | echo "run step one" exit 1 - name: Step two id: step-two # этот шаг будем запускать всегда if: ${{ always() }} run: | echo "run step two" echo "step one conclusion = ${{ steps.step-one.conclusion }}" job3: runs-on: ubuntu-latest steps: - name: Step one id: step-one # steps.step-one.conclusion = failure run: | echo "run step one" exit 1 - name: Step two id: step-two # при неудаче любого шага этого job if: ${{ failure() }} run: | echo "run step two" echo "step one conclusion = ${{ steps.step-one.conclusion }}" job4: runs-on: ubuntu-latest steps: - name: Step one id: step-one # 30 секунд на отмену workflow run: | echo "run step one" sleep 30 - name: Step two id: step-two # при отмене рабочего процесса if: ${{ cancelled() }} run: | echo "run step two" echo "workflow cancelled"
Условие для job
Для заданий тоже можно задавать условия запуска, но только для тех заданий, которые зависят от других. При использовании директивы needs
задание запускается только в том случае, если все задания, от которых оно зависит, завершились успешно (функция success()
применяется неявно).
jobs: job-one: runs-on: ubuntu-latest steps: - run: exit 1 job-two: needs: job-one runs-on: ubuntu-latest steps: - run: echo "Function success()" job-three: needs: [job-one, job-two] if: ${{ failure() }} runs-on: ubuntu-latest steps: - run: echo "Function failure()" job-four: needs: [job-one, job-two, job-three] if: ${{ always() }} runs-on: ubuntu-latest steps: - run: | echo "Function always()" echo "Result job-one: ${{ needs.job-one.result }}" echo "Result job-two: ${{ needs.job-two.result }}" echo "Result job-three: ${{ needs.job-three.result }}"
Задание job-two
не будет запущено, потому что зависит от успешного выполнения задания job-one
. Задание job-three
будет запущено, потому что задание job-one
завершилось неудачей. Задание job-four
будет запущено при любом результате job-one
, job-two
, job-three
.
Результат step
У каждого шага есть результат (статус), с которым он завершился — это success
, failure
, skipped
или cancelled
. Каждый шаг может получить результат (статус) каждого предыдущего шага через outcome
.
jobs: example: runs-on: ubuntu-latest steps: - id: step-one run: echo "run step one" - id: step-two run: | echo " run step two" exit 1 - id: step-three run: | echo "run step three" echo "function success()" - id: step-four if: ${{ failure() }} run: | echo "run step four" echo "function failure()" - id: step-five if: ${{ always() }} run: | echo "run step five" echo "function always()" echo "outcome step one: ${{ steps.step-one.outcome }}" echo "outcome step two: ${{ steps.step-two.outcome }}" echo "outcome step three: ${{ steps.step-three.outcome }}" echo "outcome step four: ${{ steps.step-four.outcome }}"
Кроме outcome
есть еще conclusion
— значение совпадает с outcome
, если не используется директива continue-on-error
. Использование этой директивы изменяет значение conclusion
на success
, когда outcome
принимает значение failure
.
jobs: example: runs-on: ubuntu-latest steps: - id: step-one run: echo "run step one" - id: step-two continue-on-error: true run: | echo " run step two" exit 1 - id: step-three run: | echo "run step three" echo "outcome step one: ${{ steps.step-one.outcome }}" echo "outcome step two: ${{ steps.step-two.outcome }}" echo "conclusion step one: ${{ steps.step-one.conclusion }}" echo "conclusion step two: ${{ steps.step-two.conclusion }}"
Хотя шаг step-two
провалился — шаг step-three
был запущен и все задание example
считается успешным.
Результат job
У задания тоже может быть результат (статус) — success
, failure
, skipped
, cancelled
— и этот результат можно получить в зависимом задании.
jobs: job-one: runs-on: ubuntu-latest steps: - run: echo "run job-one" job-two: needs: job-one runs-on: ubuntu-latest steps: - run: | echo "run job-two" exit 1 job-three: needs: [job-one, job-two] if: ${{ false }} runs-on: ubuntu-latest steps: - run: echo "run job-three" job-four: needs: [job-one, job-two, job-three] if: ${{ always() }} runs-on: ubuntu-latest steps: - run: | echo "run job-four" echo "result job-one: ${{ needs.job-one.result }}" echo "result job-two: ${{ needs.job-two.result }}" echo "result job-three: ${{ needs.job-three.result }}"
run job-four result job-one: success result job-two: failure result job-three: skipped
Для заданий тоже можно использовать директиву continue-on-error
— например, если добавить ее для job-two
, то задание будет считаться успешным. А поскольку все задания workflow
были success
или skipped
— то и workflow
в целом будет считаться успешным (отмечен в интерфейсе GitHub зеленым кружком с галочкой).
run job-four result job-one: success result job-two: success result job-three: skipped
Временные файлы
Во время выполнения рабочего процесса внутри Ubuntu создаются временные файлы, которые можно использовать в своих целях. Путь к этим файлам можно получить через переменные среды. Для примера, можно сделать переменную среды доступной для всех следующих шагов в задании рабочего процесса, определив переменную среды и записав ее в файл GITHUB_ENV
.
steps: - name: Set the value id: step_one run: | echo "action_state=yellow" >> $GITHUB_ENV - name: Use the value id: step_two run: | echo "${{ env.action_state }}" # This will output 'yellow'
Поиск: CLI • Git • GitHub • Linux • Web-разработка • Конфигурация • Сервер