We have lots of open source projects. I mean, hundreds of them.
Besides loving Open Source, we also love creating and perfecting our processes. We even have a templates repo to streamline some of them.
However, the READMEs tend to get outdated often. For example, lots of repositories had a broken link to an image, or page.
I saw some colleagues updating the image links, and I even did that myself. Twice.
No way I would do that for about 100 repositories – and worse: next time the image link was broken, do it all again.
I realized… there must be another way!
Automate development processes with GitHub Actions
I asked our CI specialists for help. That’s when this journey of exploring GitHub Actions (GHA) reusable workflows and triggering dispatch workflows started.
The idea was to:
- centralize the README template file(s);
- when they get updated, propagate the updates to all the repos using the template file(s).
Looks rad, right?
I have only been using GHA for basic needs such as dependabot, managing labels, etc. I did not know you could call workflows from different repositories, or even update an external repository using workflow dispatch actions 😎
Dynamic README GitHub Action
To get started, we used this Dynamic Readme GitHub Action (GHA), which is available in GitHub’s marketplace. The difference was that we wanted to make it reusable and future-proof for our > 100 repos.
The first step was to add this reusable action to our templates repo:
# templates/.github/workflows/dynamic-readme.yaml
name: Dynamic README reusable workflow
env:
VS_WORKFLOW_TYPE: "dynamic-readme"
on:
workflow_call:
secrets:
token:
required: true
jobs:
update_templates:
name: "Update Templates"
runs-on: ubuntu-latest
steps:
- name: "📥 Fetching Repository Contents"
uses: actions/checkout@main
- name: "💾 Github Repository Metadata"
uses: varunsridharan/action-repository-meta@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "💫 Dynamic Template Render"
uses: varunsridharan/action-dynamic-readme@main
with:
GLOBAL_TEMPLATE_REPOSITORY: thoughtbot/templates
files: |
README.md
committer_name: github-actions[bot]
committer_email: github-actions[bot]@users.noreply.github.com
commit_message: "docs: update readme file with markdown templates"
confirm_and_push: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: documentation files updated"
committer: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
signoff: false
branch: github-actions/repository-maintenance-${{ github.sha }}
delete-branch: true
title: "Automatically Generated: Update Dynamic Section in README"
body: |
This PR was automatically generated to update the dynamic section in the README file.
Whenever the README is updated, this workflow is triggered to dynamically render the snippet
used in the README.
What makes this action reusable is the workflow_call:
key. It means that external repos can access
this workflow.
Note that the workflow opens a Pull Request with the README changes. This was necessary because we have Open Source projects that had branch protection rules. If you don’t need that, you can edit the steps to push directly to main instead.
Let’s see how that works.
Reusable GitHub Actions Workflows
To call the reusable GHA workflow above in an external repo and generate the README dynamically, we need to:
- create a workflow calling the reusable workflow above;
- add the Dynamic README snippet to the README.
Calling a Reusable GitHub Workflow
In the new repository, create this workflow:
# new-repository/.github/workflows/dynamic-readme.yml
name: update-templates
on:
push:
branches:
- main
paths:
- README.md
workflow_dispatch:
jobs:
update-templates:
permissions:
contents: write
pull-requests: write
pages: write
uses: thoughtbot/templates/.github/workflows/dynamic-readme.yaml@main # <----- calls the reusable workflow
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
As you can see, this workflow is concise. It calls our reusable gha workflow and handles the necessary permissions.
Whenever we want to change the Dynamic README workflow itself, say change the commit message, we only do it in the original workflow.
You might have to make any edits depending on your repo/organization permissions. For our repos, the needed workflow permissions are satisfied with GitHub’s Automatic token authentication.
Add the Dynamic README snippet
In the new repository’s README, add the dynamic README snippet:
<!-- START /templates/footer.md -->
<!-- END /templates/footer.md -->
With these changes, the README for the new repository is rendered dynamically by using the content from our centralized template file.
Now, let’s go to the best part: keeping all of our Open Source READMEs up to date by triggering dispatch workflows.
Triggering GitHub Actions Dispatch Workflows
Besides avoiding duplication by using a reusable workflow, it would be perfect if, whenever the footer template was updated, the changes would get propagated to all the repos using the workflow.
It turns out…there is a way! One way is to use a workflow_dispatch GHA:
# templates/.github/workflows/trigger-dynamic-readme-update.yaml
name: trigger-dynamic-readme-update
on:
push:
branches:
- main
paths:
- templates/footer.md
jobs:
trigger-workflow-dispatch:
permissions:
actions: write
runs-on: ubuntu-latest
steps:
- name: Trigger Dynamic READMEs to be updated with templates
uses: benc-uk/workflow-dispatch@v1
with:
workflow: update-templates
repo: thoughtbot/high_voltage
token: ${{ secrets.PAT_TOKEN }}
ref: "main"
This workflow “listens” to updates on the templates/footer.md
file.
When there are any,
it triggers the update-templates
action on the thoughtbot/high_voltage
repo.
If you have a long list of repos (like we do) to include here, you can use a matrix to list them all:
# templates/.github/workflows/trigger-dynamic-readme-update.yaml
name: trigger-dynamic-readme-update
on:
push:
branches:
- main
paths:
- templates/footer.md
jobs:
trigger-workflow-dispatch:
permissions:
actions: write
runs-on: ubuntu-latest
strategy:
matrix:
repository:
- thoughtbot/high_voltage
- thoughtbot/guides
- thoughtbot/administrate
# - and so on
steps:
- name: Trigger Dynamic READMEs to be updated with templates
uses: benc-uk/workflow-dispatch@v1
with:
workflow: update-templates
repo: ${{ matrix.repository }} # iterates through each of the strategy matrix repository list item
token: ${{ secrets.PAT_TOKEN }}
ref: "main"
“Wait, but how does a repo using this workflow know there was an update?”
Good question! It knows because the workflow added to the repositories using the template
constains a workflow_dispatch
key.
Whenever we update the footer template, all of the repos listed in the repo matrix will
trigger their update-templates
, which will render the footer dynamically.
Workflow dispatch actions and security tokens
The workflow_dispatch action requires a Personal access token when using it on external repos.
In the templates repo, we
- created a personal GitHub access token for thoughtbot’s organization;
- pasted it in a Repository secret called
PAT_TOKEN
.
Debugging GitHub Reusable and Dispatch Workflows
Debugging GHA workflows is… annoying. What helped me during this process was to create a repo to get familiar with the flows. I messed up with the git history as much as I needed. Trust me, it took me a few hundred tries to get it working. Hopefully this post will save you from going through some of them ;)
Automating processes with Reusable workflows
Let’s recap. Putting it all together:
- templates repo:
- reusable and workflow dispatch actions
- footer README template
- repo using the workflows:
- calls the reusable workflow
- adds the dynamic README snippet to the README
The cool part of all of this journey is that even the templates repo itself is using the dynamic README to generate its footer.
What other processes could you explore automating a process like this one? The possibilities are endless. Have fun!
Thanks, immensely, to Akshith Yellapragada, Mina Slater, and Nick Charlton for the pairings, examples, and reviews.