В пості 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
У нас є три типи данних, але з різними “рівнями”:
envcontext:- задається на рівні Workflow/Job/Step – в Reusable Workflow не передаються
varscontext:- задається або на рівні GitHub Actions Environments – в Reusable Workflow не передаються
- або на рівні Repository та Organization Variables – в Reusable Workflow доступні без додаткових дій
secretscontext:- задається або на рівні 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, але це вже зовсім інша історія.







