DEV Community

Bertrand Paquet for Doctolib Engineering

Posted on

GitHub actions: how to push a GitHub status in addition of GitHub checks

GitHub actions are incredibly efficient to quickly deploy a CI on a project, especially because of the native parallelism system.

But we found some downsides along the way. One downside is the result of a GitHub actions is a Check, not a Status.
There are identical in the UI, but are not in the API. In the image below, the last line is a status in the API, but all the lines above are checks at API Level.

image

What is wrong with that? At Doctolib, we have a lot of automation relying on statuses, not on checks. To onboard some of our projects on Github action, we were looking for an elegant solution.

The first idea is to add a step at the end of the workflows, which push a status from here.
Something like:

      - name: Create status
        run: |
          curl --request POST \
          --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_commit.id }} \
          --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
          --header 'content-type: application/json' \
          --data '{
            "state": "${{ github.event.workflow_run.conclusion }}",
            "context": "unit test (kube)",
            "target_url": "${{ github.event.workflow_run.html_url }}"
            }' \
          --fail
Enter fullscreen mode Exit fullscreen mode

This solution is working, but only for green build (successful build). For red build (failed build), we can use a failure condition. If we have workflows with multiple jobs, we have to put this failure condition in every job. This can be painful, and add code duplication.

So we found a simpler solution: have an independent GitHub workflow which listen to the CI workflow (success AND failure), and set a status accordingly.

name: Set test status

on:
  workflow_run:
    workflows: ["Run tests"]
    types:
      - completed

jobs:
  set_status:
    runs-on: ubuntu-latest
    permissions:
      statuses: write 
    steps:
      - name: Create status
        run: |
          curl --request POST \
          --url https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_commit.id }} \
          --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
          --header 'content-type: application/json' \
          --data '{
            "state": "${{ github.event.workflow_run.conclusion }}",
            "context": "unit test (kube)",
            "target_url": "${{ github.event.workflow_run.html_url }}"
            }' \
          --fail
Enter fullscreen mode Exit fullscreen mode

Feel free to reuse it, you just have to change:

  • The name of your CI workflow in the on clause.
  • The name of the status you want to push on GitHub in the context params.

Discussion (1)

Collapse
twisterrob profile image
Róbert Papp • Edited on

There's a simpler way with more control, because
1) it's JavaScript, not a text-substituted curl command.
2) it uses GitHub API with auth built in.
3) first party GitHub Action
4) using checks.create rather then status.create for more control (output)

      - name: "Create a check run"
        uses: actions/github-script@v6
        env:
          parameter_url: '${{ github.event.workflow_run.html_url }}'
        with:
          debug: ${{ secrets.ACTIONS_STEP_DEBUG || false }}
          script: |
            // any JavaScript code can go here, you can use Node JS APIs too.
            // Docs: https://docs.github.com/en/rest/checks/runs#create-a-check-run
            // Rest: https://octokit.github.io/rest.js/v18#checks-create
            await github.rest.checks.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              head_sha: context.sha,
              name: "my-check-name",
              status: "completed",
              // Careful, code injection can happen.
              conclusion: "${{ github.event.workflow_run.conclusion }}",
              // This is safe, not string interpolation.
              details_url: process.env.parameter_url,
              output: {
                title: "my check title",
                summary: "my *check* summary",
                text: "my text",
              },
            });
Enter fullscreen mode Exit fullscreen mode

and how it looks: