В пості GitHub Actions: деплой Dev/Prod оточень з Terraform я вже трохи торкався теми Reusable Workflows та Composite Actions – прийшов час трохи більше з нею ознайомитись.
Що треба зробити: зараз на проекті ми в кожному репозиторії пишемо Workflow-файли окремо. Втім, оскільки поступово всі процеси уніфікуються – управління інфраструктурою через Terraform, та запуск сервісів в Kubernetes і деплой з Helm – то вирішили, що пора навести лад в GitHub Actions, і перестати писати “кожен для себе”.
Натомість в окремому репозиторії створимо Shared Workflow з набором Jobs, які будуть виконувати потрібні дії, і потім будемо ці Workflow включати в Wokflow проектів.
Але у Reusable Workflows виявилось кілька цікавих деталей.
Тож спочатку глянемо в чому різниця між Reusable Workflows та Composite Actions та для чого вони призначаються., а потім поглянемо на роботу з Reusable Workflows.
Зміст
Порівняння Reusable Workflows та Composite Actions
Composite Actions
Composite Actions дозволяють скомбінувати кілька Steps в єдиний Action. Такі Step описуються в єдиному файлі, і можуть виконувати кілька різних runs
або викликати інші Actions.
Гарний приклад роботи з ними є в тому GitHub Actions: деплой Dev/Prod оточень з Terraform – Створення Composite Action “terraform-init”.
Ідеальне рішення, коли ви хочете використати послідовність Steps в кількох Jobs або Workflows.
- Composite Actions дозволяє комбінувати кілька steps в одному Action, щоб потім у Workflow викликати їх всі як один Step
- в Composite Actions не можна мати кілька Jobs
- Job, яка викликає Composite Actions може мати інші Steps
Reusable Workflows
Reusable Workflows дозволяють перевикористати цілий Workflow з усіма його Jobs та Steps. Дають більше можливостей, бо включають в себе контексти, змінні оточення та секрети.
Ідеальне рішення, коли ви хочете використати цілий CI/CD пайплайн в кількох репозиторіях.
Далі будемо використовувати такі назви:
- Reusable Workflow: workflow, який зберігається в окремому репозиторії та викликається для виконання іншим workflow
- Caller Workflow: workflow, який викликає Reusable Workflow
Особливості Reusable Workflows:
- Reusable Workflows не можуть викликати інші Reusable Workflows
- Reusable Workflows мають досить детальні логи виконання – кожна Job та Step логується окремо
- Reusable Workflows викликаються як Jobs, але така Job не може мати інших Steps
- через це ви не можете використати
$GITHUB_ENV
, щоб передати values до Jobs та Steps у Caller Workflow, який викликає Reusable Workflow
- через це ви не можете використати
- ви можете використовувати різні версії одного Reusable Workflow через анотацію
@REF
з іменем бранча або git-тегом
Див. також Limitations.
Reusable Workflows та Composite Actions: Key differences
Reusable workflows | Composite actions |
Can connect a maximum of four levels of workflows | Can be nested to have up to 10 composite actions in one workflow |
Can use secrets | Cannot use secrets |
Can use if: conditionals | Cannot use if: conditionals |
Can be stored as normal YAML files in your project | Requires individual folders for each composite action |
Can use multiple jobs | Cannot use multiple jobs |
Each step is logged in real-time | Logged as one step even if it contains multiple steps |
Створення Reusable Workflow
Зробимо тестові Workflow, щоб перевірити схему взагалі:
- в репозиторії
atlas-github-actions
буде Reusable Workflow - в репозиторії
atlas-test
буде Caller Workflow
Створюємо репозиторій для наших Reusable Workflows – atlas-github-actions
, і в ньому створюємо каталог .github/workflows
з файлом test-reusable-workflow.yml
:
name: Reusable Workflow # trigger from other workflows on: workflow_call: jobs: test: runs-on: ubuntu-latest steps: - name: "Test: print Hello" run: echo "Hello, World!"
Зберігаємо, пушимо в GitHub.
Далі нам потрібно дозволити використання Workflows з цього репозиторію.
Переходимо в Setting > Actions, і внизу сторінки дозволяємо доступ з інших репозиторіїв організації:
Переходимо до Caller-репозиторія – atlas-test
, також створюємо каталог .github/workflows
з файлом test-caller-workflow.yml
:
name: Caller Workflow on: # can be ran manually workflow_dispatch: jobs: test: # call the Reusable Workflow file uses: <ORG_NAME>/atlas-github-actions/.github/workflows/test-reusable-workflow.yml@master
Пушимо, і запускаємо:
Тепер трохи подивимось на деталі того, як працювати з Reusable Workflows.
Permissions
Чудовий пост на тему GitHub Actions Permisssions і Security взагалі – GitHub Actions Workflow Permissions.
Якщо в двох словах:
- при використанні Actions сторонніх девелоперів – перевіряйте їх код, та використовуйте SHA hash замість Git-тегу (ніколи так не робив, але для зовсім Security – має сенс)
- завжди налаштовуйте
permissions
для$GITHUB_TOKEN
явно на рівні Workflow або Job, щоб не використовувати дефолтні дозволи - Reusable Workflow наслідує
permissions
з Job або Workflow, яка викликає Reusable Workflow
Тобто якщо ми в Caller Workflow задамо permissions.pull-request: write
– то зможемо створювати коментарі в Pull Requests і з нашого Reusable Workflow.
GitHub Actions envs
, vars
, secrets
та Reusable Workflow
У нас є три типи данних, але з різними “рівнями”:
env
context:- задається на рівні Workflow/Job/Step – в Reusable Workflow не передаються
vars
context:- задається або на рівні GitHub Actions Environments – в Reusable Workflow не передаються
- або на рівні Repository та Organization Variables – в Reusable Workflow доступні без додаткових дій
secrets
context:- задається або на рівні GitHub Actions Environments – в Reusable Workflow не передаються
- або на рівні Repository та Organization Secrets – в Reusable Workflow доступні через
secrets: inherit
Ми взагалі не можемо використовувати Environments в Caller Workflow та Job, яка викликає Reusable Workflow – див. Supported keywords for jobs that call a reusable workflow, тож всі vars
та secrets
, які задані конкретному Evnironment – ми в Reusable Workflow не побачимо.
Тобто в Caller Workflow не можна зробити щось типу:
... jobs: test: # using 'environment' will fail environment: test uses: <ORG_NAME>/atlas-github-actions/.github/workflows/test-reusable-workflow.yml@master ...
Ну й давайте перевіримо що ми зможемо побачити в Caller Workflow, та в Reusable Workflow.
В репозиторії atlas-test
з Caller Workflow додаємо Environment, і в ньому Environment secrets та Environment variables:
В тому ж репозиторії додаємо звичайні Repository secrets:
Та Repository variables:
В цьому ж репозиторії оновлюємо файл Caller Workflow – test-caller-workflow.yml
:
- на рівні Workflow додаємо
env: CALLER_WORKFLOW_ENV
- до Job з нашою Reusable Workflow:
- додаємо передачу
test-input
в Reusable Workflow - додаємо передачу
secrets: inherit
- додаємо передачу
- на рівні Workflow додаємо Job
prints-envs
name: Caller Workflow on: # can be ran manually workflow_dispatch: env: CALLER_WORKFLOW_ENV: "Caller Env String" jobs: test: # call the Reusable Worfklow file uses: <ORG_NAME>/atlas-github-actions/.github/workflows/test-reusable-workflow.yml@master with: test-input: "Test Input String" secrets: inherit prints-envs: environment: test runs-on: ubuntu-latest steps: # Can use Envs from the Workflow level - name: "Test: print Caller Workflow Env" run: echo ${{ env.CALLER_WORKFLOW_ENV }} # can use Variables from the Workflow Environments level - name: "Test: print Caller Repository Env Variable" run: echo ${{ vars.CALLER_ENV_VAR }} # can use Variables from the Reposiotiry level - name: "Test: print Caller Repository Repo Variable" run: echo ${{ vars.CALLER_REPO_VAR }} # CAN'T use Secrets from the Workflow Environments level - name: "Test: print Caller Env Secret" run: echo ${{ secrets.CALLER_ENV_SECRET }} # can use Secrets from the Reposiotiry level - name: "Test: print Caller Repo Secret" run: echo ${{ secrets.CALLER_REPO_SECRET }}
В репозиторії atlas-github-actions
оновимо наш Reusable Workflow – файл test-reusable-workflow.yml
.
Додаємо inputs
та steps
, в яких спробуємо вивести env
, vars
та secrets
з Caller Workflow/Repository/Environment:
name: Reusable Workflow # trigger from other workflows on: workflow_call: inputs: test-input: required: true type: string jobs: test: runs-on: ubuntu-latest steps: - name: "Test: print Hello" run: echo "Hello, World!" # CAN'T use Envs from the Caller Workflow - name: "Test: print Caller Workflow Env" run: echo ${{ env.CALLER_WORKFLOW_ENV }} # CAN'T use Variables from the Caller Workflow Environments level - name: "Test: print Caller Repository Env Variable" run: echo ${{ vars.CALLER_ENV_VAR }} # can use Variables from the Caller Repository Variables - name: "Test: print Caller Repository Repo Variable" run: echo ${{ vars.CALLER_REPO_VAR }} # CAN'T use Secrets from the Caller Workflow Environments Secrets - name: "Test: print Caller Env Secret" run: echo ${{ secrets.CALLER_ENV_SECRET }} # can use Secrets from the Caller Reposiotiry - name: "Test: print Caller Repo Secret" run: echo ${{ secrets.CALLER_REPO_SECRET }} # can use Inputs from the Caller Workflow - name: "Test: print Caller Repo Input" run: echo ${{ inputs.test-input }}
Передача Secrets
Додам про передачу Secrets:
Перший варіант – використати secrtes: inherit
– тоді в Reusable Workflow будуть доступні всі змінні в Repository secrets та Orgznization secrets з Caller Workflow.
Крім того, в Reusable Workflow можна їх задати в env
:
... on: workflow_call: inputs: test-input: required: true type: string env: reusable_wf_local_secret: ${{ secrets.CALLER_REPO_INHERITED_SECRET }} jobs: test: ...
Другий варіант – замість використання secrets: inherit
передавати конкретний Secret:
... test: # call the Reusable Worfklow file uses: <ORG_NAME>/atlas-github-actions/.github/workflows/test-reusable-workflow.yml@master with: test-input: "Test Input String" secrets: REUSAVBLE_WF_SECRET_NAME: ${{ secrets.CALLER_WF_SECRET_NAME }} ...
В такому випадку в Reusable Workflow REUSAVBLE_WF_SECRET_NAME
має бути заданий в разом з inputs
:
... on: workflow_call: secrets: REUSABLE_WF_SECRET_NAME: required: false inputs: test-input: required: true type: string ...
Тоді далі в Reusable Workflow його можна використовувати як ${{ secrets.REUSABLE_WF_SECRET_NAME }}
.
Все пушимо в репозиторії, і запускаємо Workflow.
В Job, яка викликає Reusable Workflow частини даних нема:
В Job, яка викликається напряму в Caller Workflow всі дані є:
GitHub Context
Коли Reusable Workflows викликається з Caller Workflow, github контекст завжди буде мати дані з Caller Workflow.
Наприклад, в Reusable Workflow додамо відображення імені репозиторію:
name: Reusable Workflow # trigger from other workflows on: workflow_call: inputs: test-input: required: true type: string jobs: test: runs-on: ubuntu-latest steps: - name: "Test: print Hello" run: echo "Hello, World!" ... - name: "Test: print Repository Name from the github context" run: echo ${{ github.repository }}
І маємо ім’я atlas-test – репозиторій з Caller Workflow:
Тепер можна починати робити Worfklow для Terraform та Helm, але це вже зовсім інша історія.
Корисні посилання
- Github Not-So-Reusable Actions
- GitHub: Composite Actions vs Reusable Workflows [Updated 2023]
- How to start using reusable workflows with GitHub Actions
- Using inputs and secrets in a reusable workflow
- The Ultimate Guide to GitHub Reusable Workflows: Maximize Efficiency and Collaboration