Программирование

GitHub Actions. Дополнительные возможности

Данная статья является продолжением описания возможностей платформы GitHub Actions. Здесь будут затронуты более продвинутые аспекты автоматизации в рамках CI/CD: перенос рутины в скрипты, передача данных между задачами, условное выполнение job/step, обработка данных в конфигурации workflow, а также способы хранения информации в процессе работы workflow.

Оглавление

Использование скриптов внутри workflow

Если в процессе работы необходимо исполнять множество команд в оболочке операционной системы, то имеет смысл создать отдельный скрипт для данной командной оболочки, сохранить его в репозитории и вызывать в одном из шагов:

jobs:
  example-job:
    steps:
      - name: Run build script
        run: ./.github/scripts/build.sh
        shell: bash

Обмен данными между задачами

Если одна из задач (job) генерирует какие-либо файлы, которые необходимы для работы другой задачи (job), то можно сохранить данные файлы в виде GitHub artifacts. Артефактами могут быть любые файлы в процессе сборки и тестирования кода: результаты тестов, бинарные файлы, логи и пр. Все артефакты принадлежат тому workflow, где были созданы и могут быть использованы любой задачей (job) в данном или следующих запусках текущего workflow.

Например, можно создать файл и сохранить его в виде артефакта:

jobs:
  upload-job:
    name: Save test
    steps:
      - run: echo TEST > test.file
      - name: Upload test file
        uses: actions/upload-artifact@v3
        with:
          name: test-file
          path: test.file

Чтобы загрузить файл из артефакта в job из другого запуска workflow:

jobs:
  download-job:
    steps:
      - name: Download test
        uses: actions/download-artifact@v3
        with:
          name: test-file

Внимание! Для загрузки файлов артефакта в том же запуске workflow, где файлы были сохранены, следует указать свойство needs для job по загрузке (needs: upload-job для примера выше).

Выражения

Для программной установки переменных окружения и доступа к контекстам (см. далее) workflow можно использовать выражения в рамках файла конфигурации workflow. Обычно выражения используются в значении свойства if для определения необходимости запуска того или иного step.

При использовании выражения внутри свойства if допустимо, а в остальных случаях необходимо оборачивать выражение специальным синтаксисом:
${{ <выражение> }}

Пример использования выражений внутри свойства if, а также для установки переменной окружения:

steps:
  - uses: actions/hello-world-javascript-action@v1.1
    if: ${{ <выражение> }}
    env:
      MY_ENV_VAR: ${{ <выражение> }}

В составе выражений могут быть использованы следующие типы данных:.

  • boolean true или false
  • null
  • number — любое число, поддерживаемое форматом JSON
  • string — любая строка, которую допускается оборачивать в одинарные (‘) кавычки. Для использования одинарной кавычки в составе строки следует использовать дополнительную кавычку (‘’)

Для комбинирования нескольких частей одного выражения используются стандартные операторы (<, >, <=, >=, ==, &&, || и пр.).

Внимание! GitHub игнорирует регистр при сравнении строк.

Внутри выражений можно использовать предопределенные функции:

  • contains( search, item ) – возвращает true если search содержит item. Если search является массивом, то данная функция вернет true если item является частью массива;
  • startsWith( searchString, searchValue ) – возвращает true если searchString начинается с searchValue;
  • endsWith( searchString, searchValue ) – возвращает true если searchString оканчивается на searchValue;
  • format( string, replaceValue0, replaceValue1, …, replaceValueN) – заменяет специальные значения в строке string переменными replaceValueN. Значения в строке string используют синтаксис {N}, где N – целое число;
  • join( array, optionalSeparator ) – все значения в array конкатенируются в строку. Если был добавлен опциональный разделитель optionalSeparator, то его значение будет добавлено между конкатенируемыми значениями. По умолчанию в качестве разделителя используется запятая;
  • toJSON(value) – возвращает JSON-представление value в формате pretty-print;
  • fromJSON(value) – возвращает JSON-объект или любой JSON совместимый тип данных для value;
  • hashFiles(path) – возвращает один хэш для набора файлов, совпадающих с паттерном path. Можно указать один паттерн или несколько, разделив их запятыми. Данная функция возвращает SHA-256 хэш для каждого файла, после чего использует полученные хэши для создания общего SHA-256 хэша для всего набора файлов. Если в наборе не будет ни одного файла, то возвращается пустая строка;
  • Функции по проверке статуса шагов:
    • success – возвращает true когда ни один из предыдущих step не был завершен с ошибкой или отменен;
    • always – приводит к тому, что step всегда выполняется и возвращает true даже если step был отменен;
    • cancelled – возвращает true если workflow был отменен;
    • failure – возвращает true если любой из предыдущих step завершился с ошибкой.

Контексты

Контексты – это способ доступа к информации о запущенном workflow, job, step и env. Каждый контекст – это объект, содержащий свойства, которые могут быть строками или другими объектами.

Для получения доступа к контексту используется синтаксис выражений:
${{ <контекст> }}

Для ознакомления со всем перечнем доступных контекстов, их областью видимости и примерами использования следует обратиться к документации.

Переменные

Один из способов задания переменных окружения для одного workflow был описан в первой части статей на данную тематику. Альтернативой может стать определение конфигурационных переменных для нескольких различных workflow на уровне организации, репозитория или окружения.

Внимание! По умолчанию, переменные никак не скрываются в потоке вывода поэтому не рекомендуется хранить в них такие данные, как пароли. Для этого лучше использовать шифруемые секреты (см. далее).

Подобный подход может использоваться, например, для задания конфигурационных переменных по умолчанию на уровне организации, которые будут передаваться в инструменты по сборке проекта. При этом сохраняется возможность (при необходимости) для переопределения данных переменных на уровне каждого репозитория. Отсюда можно сделать вывод о том, что чем ниже уровень определения переменной, тем выше ее приоритет при переопределении.

Имена определяемых конфигурационных переменных не чувствительны к регистру, должны быть уникальны для каждого из уровней, начинаться с буквы или символа подчеркивания и содержать только буквы, цифры или символ подчеркивания.

Внимание! Имя конфигурационной переменной не должно начинаться с GITHUB_.

Для создания конфигурационных переменных необходимо владеть репозиторием или обладать правами администратора на уровне организации. Процесс создания переменной, независимо от уровня, почти идентичен:

  • Перейти в настройки (Settings) репозитория или организации
  • Перейти в раздел Secrets and variables/Environments
  • На вкладке Variables нажать кнопку New repository (organization) variable
  • Ввести имя и значение добавляемой переменной
  • Для организации выбрать политику доступа из выпадающего списка Repository access
  • Нажать кнопку Add variable

Получить доступ к переменным окружения можно через контекст env, а к конфигурационным переменным через контекст vars. Если переменная не определена, то ее значение будет являться пустой строкой.

Пример доступа к конфигурационным переменным:

on:
  workflow_dispatch:
env:
  env_var: ${{ vars.ENV_CONTEXT_VAR }}

jobs:
  display-variables:
    name: ${{ vars.JOB_NAME }}
    if: ${{ vars.USE_VARIABLES == 'true' }}
    runs-on: ${{ vars.RUNNER }}
    environment: ${{ vars.ENVIRONMENT_STAGE }}
    steps:
    - name: Use variables
      run: echo "repository variable : ${{ vars.REPOSITORY_VAR }}"

Не смотря на возможность доступа к переменным окружения напрямую, иногда необходимо использовать соответствующий контекст. Так, часть процесса workflow обрабатывается непосредственно платформой GitHub Actions и не посылается в операционную систему runner. В таких случаях нет возможности использовать переменные окружения напрямую. Например, свойство if, которое определяет необходимость выполнения того или иного job, всегда обрабатывается платформой GitHub Actions, поэтому доступ к ней следует получать через соответствующий контекст.

Использование секретов

Аналогично конфигурационным переменным для тех же уровней можно создать секреты – переменные, которые хранят данные в зашифрованном виде. К секретам применяются те же требования по именованию, что и к конфигурационным переменным.

Доступ к секретам внутри workflow осуществляется через контекст secrets:

steps:
  - name: Hello world action
    with: # Установка секрета в качестве входного параметра
      super_secret: ${{ secrets.SuperSecret }}
    env: # Или в качестве переменной окружения
      super_secret: ${{ secrets.SuperSecret }}

Для использования секретов внутри командной оболочки необходимо установить переменную окружения со значением секрета:

steps:
  - shell: bash
    env:
      SUPER_SECRET: ${{ secrets.SuperSecret }}
    run: |
      example-command "$SUPER_SECRET"

Отдельно необходимо упомянуть секрет GITHUB_TOKEN, который автоматически создается при старте workflow и позволяет аутентифицироваться от имени платформы GitHub Actions. Для использования данного секрета можно обращаться все к тому же контексту secrets:

on: [ pull_request_target ]

permissions:
  contents: read
  pull-requests: write

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v4
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

В примере выше используется новое свойство permissions, которое указывает на доступные для секрета GITHUB_TOKEN права доступа (разрешения). Разрешение по умолчанию для данного секрета можно увидеть в документации здесь, а перечень всех возможных разрешений здесь.

Полезные ссылки

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *