GitHub Actions. Начало работы, часть 1 из 2

15.06.2022

Теги: CLIGitGitHubLinuxWeb-разработкаКонфигурацияСервер

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-разработка • Конфигурация • Сервер

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.