Reusable Github Workflows
Posted on April 15, 2023 by Adrian Wyssmann ‐ 5 min read
Do you have multiple projects of the same topic, whicch use the same workflows? Then you might have a look into reusable workflows.
I have multiple ansible roles and I want to use the same workflows for all of these roles. This is when reusable workflows comes into play:
Rather than copying and pasting from one workflow to another, you can make workflows reusable. You and anyone with access to the reusable workflow can then call the reusable workflow from another workflow.
This consists of two elements:
- shared workflow: The actuall workflow which does all the work
- caller workflow: A workflow that uses another workflow
Creating a shared workflow
Firs we are starting to create our shared workflow in a dedicate repository “github-actions-workflows”. The workflow are filed under the default path for workflows: .github/workflows/
, e.g. .github/workflows/ansible-roles-release.yml
. In my case the workflow will do some linting, release preparation, creating and publishing the release (ansible role).
name: Create release
on:
workflow_call:
secrets:
gh_token:
required: true
galaxy_api_key:
required: true
jobs:
build:
name: Ansible linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Lint Ansible Playbook
uses: ansible/ansible-lint-action@main
- name: Install Dependencies
run: pip install ansible
- name: Create ansible.cfg with correct roles_path
run: printf '[defaults]\nroles_path=../:./' >ansible.cfg
# - name: Run ansible syntax-check
# run: ansible-playbook tests/test.yml -i tests/inventory --syntax-check
prepare-release:
name: Prepare Release
runs-on: ubuntu-latest
steps:
- name: Set RELEASE_VERSION
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Create release branch
run: git checkout -b release/$RELEASE_VERSION && git push --set-upstream origin release/$RELEASE_VERSION
- name: Install auto-changelog
run: sudo npm install -g auto-changelog
- name: Set current version
run: echo $RELEASE_VERSION > ./VERSION
- name: Create changelog
run: auto-changelog --ignore-commit-pattern "^\[?ci|docu|Merge|meta\]?|fixup" --release-summary
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "meta: Update changelog, bump version"
file_pattern: ./VERSION ./CHANGELOG.md
commit_user_name: GitHub Actions
commit_user_email: [email protected]
commit_author: Papanito <[email protected]>
push_options: --force
create-release:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v2
- name: Install auto-changelog
run: sudo npm install -g auto-changelog
- name: Create release notes
run: auto-changelog --ignore-commit-pattern "^\[?ci|docu|Merge|meta\]?|fixup" --starting-version $RELEASE_VERSION -o RELEASENOTES.md --release-summary
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.gh_token }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body_path: ./RELEASENOTES.md
draft: false
prerelease: false
needs:
- build
- prepare-release
import-role:
runs-on: ubuntu-latest
needs: create-release
steps:
- name: Publish Ansible role to Galaxy
uses: hspaans/ansible-galaxy-action@v1
with:
api_key: ${{ secrets.galaxy_api_key }}
As you can see there are two important things:
- the workflow will be triggered on [workflow_call]
- the workflow defines
secrets
(but also could defineinputs
) - the
secrets
are reference accordingly in the steps likeapi_key: ${{ secrets.galaxy_api_key }}
Workflows that call reusable workflows in the same organization or enterprise can use the inherit
keyword to implicitly pass the secrets.
Caller Workflow
In the repositroy where you want to run the reusable workflow, you have to add the calling workflow under .github/workflows/
, e.g. .github/workflows/ansible-roles-release.yml
.
name: Create release
on:
push:
tags:
- 'v*.*.*'
jobs:
call-workflow-passing-data:
uses: papanito/github-actions-workflows/.github/workflows/ansible-roles-release.yml@main
secrets:
gh_token: ${{ secrets.GITHUB_TOKEN }}
galaxy_api_key: ${{ secrets.ANSIBLE_GALAXY_APIKEY }}
As part of the jobs
you have to add call-workflow-passing-data.uses
, which refers to the workflow which as to run, which is my ansible-roles-release.yaml
on main
-branch. I also pass the required secrets under secrets
. Important is that these secrets (e.g GITHUB_TOKEN
) are defined in the repository where the caller workflow is.
That’s all, pretty simple isn’t it, and indeed very helpful. However, keep in mind, there are certain limitations:
- You can connect up to four levels of workflows. For more information, see “Nesting reusable workflows.”
- You can call a maximum of 20 reusable workflows from a single workflow file. This limit includes any trees of nested reusable workflows that may be called starting from your top-level caller workflow file.
- For example, top-level-caller-workflow.yml → called-workflow-1.yml → called-workflow-2.yml counts as 2 reusable workflows.
- Any environment variables set in an env context defined at the workflow level in the caller workflow are not propagated to the called workflow. For more information, see “Variables” and “Contexts.”
- To reuse variables in multiple workflows, set them at the organization, repository, or environment levels and reference them using the vars context. For more information see “Variables” and “Contexts.”
Workflow publisher
In order to deploy the caller workflow to all the repositories, I also use a workflow publisher, which is stored under .github/workflows/workflow-publisher.yml
of the repository “github-actions-workflows”. This job runs when changes are made to the calling workflow.
name: Workflow Publisher
on:
push:
branches: ['develop', 'main']
paths:
- 'ansible-roles/**'
workflow_dispatch:
jobs:
publish-workflow-template:
runs-on: ubuntu-latest
strategy:
matrix:
target_repo: [
'ansible-role-cloudflared',
'ansible-role-crowdsec',
'ansible-role-diskmounter',
'ansible-role-systemd_notifiers',
'ansible-role-ttrss',
'ansible-role-backup',
'ansible-role-git'
]
steps:
- name: Checkout source repo
uses: actions/checkout@v3
with:
path: main
- name: Checkout target repo
uses: actions/checkout@v3
with:
repository: ${{ github.repository_owner }}/${{ matrix.target_repo }}
path: ${{ matrix.target_repo }}
token: ${{ secrets.GH_TOKEN}}
ref: main
- name: Update workflows
shell: bash
run: |
mkdir -p .github/workflows/
cp ../main/ansible-roles/* .github/workflows/
working-directory: ./${{ matrix.target_repo }}
- name: Publish workflows
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "ci: update workflows"
commit_user_email: [email protected]
commit_user_name: GitHub Actions
repository: ./${{ matrix.target_repo }}
continue-on-error: false