Triggering Reusable Github Actions Workflows

Stefanni Brasil

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.

OSS READMEs with broken image logos

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

Adding secrets to a GitHub actions on a repository

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:

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.