Triggering GitHub Actions from a different repository
At work right now, I’m working on something that spans multiple GitHub repositories, and I need to trigger a job in one repository from a different repository.
Background
I’m working on a single project, broken across multiple repositories. I’ve got separate repositories for the network layer, for the message codecs, and so on. I’ve got another repository containing various integration tests that shouldn’t be included when the project is included in a production package. At some point, I’m going to add a repository with a fancy-pants web front-end that’ll look good in demos, but definitely should be opt-in for production.
For simplicity, let’s assume that I’ve got two repositories, the-tests
and the-app
. the-tests
depends on
the-app
. Whenever I make a change to the-app
(the “source”), I want to run a workflow in the-tests
(the “target”).
Repository Dispatch
Fortunately, GitHub provides a repository_dispatch
trigger that can be used to trigger a workflow. You add it to
the-tests
, like this:
# .github/workflows/integration-tests.yaml
name: "Integration Tests"
on:
repository_dispatch:
types:
- integration-tests
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Integration Tests
run: ./integration-tests.sh
The types:
value allows you to trigger different workflows. Here, we’re triggering on the integration-tests
event
type. We’ll see this again below.
I’ve shown it in a separate workflow file above, but if you want to use it to trigger an existing workflow, you can add it to that file along with the others. For example:
# .github/workflows/main.yaml
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
repository_dispatch:
types:
- integration-tests
# ...
Triggering it with curl
You can trigger this workflow by using curl
with the dispatches
REST endpoint. For more details, see Create a
repository dispatch
event in the
GitHub documentation. Here’s an example:
curl -qs \
--fail-with-body \
-L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $BEARER_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$TARGET_REPO_OWNER/$TARGET_REPO/dispatches \
-d '{"event_type":"integration-tests"}'
$BEARER_TOKEN
is a Fine-grained Personal Access Token, of which more below.$TARGET_REPO_OWNER
is the owner of the target repository. This is the GitHub organization or user name.$TARGET_REPO
is the name of the target repository. In this example, this would bethe-tests
.{"event_type":"integration-tests"}
matches thetypes:
value above.
The GitHub documentation doesn’t include the -q
(don’t load the config file), -s
(no progress reporting) or
--fail-with-body
options, but I think they’re a good idea.
Personal Access Token
In order to access the dispatches
endpoint, you’ll need a Personal Access Token (PAT) which grants programmatic access
to your repositories. Here’s how I generated a fine-grained one:
- In GitHub, click on your profile picture in the top-right and select “Settings”.
- Click “Developer settings”, at the bottom-left of the settings page.
- Click “Personal access tokens” / “Fine-grained tokens” on the left-hand side.
- Click “Generate new token”.
- Give it a name. Since it will have fine-grained permissions, give it a sensible name and description reflecting that.
- Under “Repository access”, choose “Only select repositories”. In the widget that appears, choose the target
repository (in our example, this is
tests
). This limits the damage that it can cause. - Under “Repository permissions”, you’ll need to choose “Contents: Read and write”, per the documentation here.
- Click “Generate token”.
- Important: you’re going to need the content of the secret later (it starts with
github_pat_
), so copy it to a scratch text file or somewhere. Do NOT check it in.
Triggering it from the source repository
Create a workflow in the-app
that includes the curl command from above.
# .github/workflows/trigger-integration-tests.yaml
name: "Trigger Integration Tests"
# Run this action when there's a push to 'main'.
on:
push:
branches: ["main"]
jobs:
trigger-integration-tests:
name: "Trigger Integration Tests"
runs-on: ubuntu-latest
steps:
- name: "Trigger Integration Tests"
run: |
target_repo=the-tests
curl -qs \
--fail-with-body \
-L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.THE_TESTS_PAT }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/$GITHUB_REPOSITORY_OWNER/$target_repo/dispatches \
-d '{"event_type":"integration-tests"}'
Notes:
- I used
$GITHUB_REPOSITORY_OWNER
, which evaluates to the owner of this repository (i.e.the-app
). This means that if both repositories are forked, it’ll trigger the tests in the correct fork. - If you want to use an environment variable in
event_type:
, bear in mind shell quoting rules. - You’ll need to configure the
THE_TESTS_PAT
secret; see below.
Secrets
Workflows can access secrets using the ${{ secrets.NAME }}
syntax. We’ll use this to access the Personal Access Token
that we created above.
- In the
the-app
repository (the “source”), click on “Settings” at the top. - Click on “Secrets and variables” on the left-hand side, under “Security”.
- Click on “Actions”.
- Click on “New repository secret”.
- Give it a name. In the above workflow step, it was called
THE_TESTS_PAT
, so use that. - Paste in the
github_pat_...
secret from earlier. - Click “Add secret”.
Conclusion
At this point, any push to main
in the-app
should trigger the “Run Integration Tests” workflow in the-tests
. You
can extend so that the-app
, the-lib
, the-other-lib
, etc. all trigger the integration tests when they’re pushed to.
What’s missing?
- Triggering
the-tests
for pull requests against dependencies. I’ll dig into that later – it’ll require messing with dependency versions. - Triggering the integration tests only if all of the actions in
the-app
succeed. If that’s all in a single workflow file, it’ll get ugly. “Reusable workflows” can probably help here.
What’s annoying?
- If you have multiple dependencies, you need to add the triggering workflow to each of them. It might be better to use a time-based polling mechanism instead.
- If you push to more than one dependency, it’ll trigger multiple times and might not wait until everything’s quieted down.