GitHub Actions

A composite action you copy into your repository that reports a deployment as the job starts and as it ends, the workflow that uses it, the secrets and variables it needs, and the GitHub deployments alternative that needs no step at all.

By the end of this page a GitHub Actions workflow will report every deployment it makes: opened as running when the job starts, finished as succeeded, failed or cancelled when it ends, with the commit, the branch and a link back to the run. The action lives in your own repository, so there is nothing to install and nothing that changes under you.

Before you begin

You needWhy
An ingestion tokenA token with ci:deploy, confined to the project; see CI/CD ingestion.
The project and environment idsBoth are in the address bar of their pages; one environment id per environment the workflow deploys to.
jq on the runnerThe hosted runners carry it; a self-hosted runner needs it installed.

Add the action to your repository

Create .github/actions/zuse-bifrost-deployment/action.yml with this content:

.github/actions/zuse-bifrost-deployment/action.yml
name: Report a deployment to Zuse Bifrost
description: Opens a deployment as the job starts and finishes it as the job ends.

inputs:
  api:
    description: The API base address, without a trailing slash.
    required: true
  token:
    description: An ingestion token that carries ci:deploy.
    required: true
  project:
    description: The id of the project deployed.
    required: true
  environment:
    description: The id of the environment deployed into.
    required: true
  version:
    description: The version deployed.
    required: true
  phase:
    description: start, to open the deployment; finish, to end it.
    required: true
  deployment:
    description: The deployment id the start phase answered; needed by finish.
    required: false
  status:
    description: How the deployment ended; succeeded, failed or cancelled.
    required: false
    default: succeeded

outputs:
  deployment:
    description: The id of the deployment opened by the start phase.
    value: ${{ steps.report.outputs.deployment }}

runs:
  using: composite
  steps:
    - id: report
      shell: bash
      env:
        BIFROST_API: ${{ inputs.api }}
        BIFROST_TOKEN: ${{ inputs.token }}
        BIFROST_PROJECT: ${{ inputs.project }}
        BIFROST_ENVIRONMENT: ${{ inputs.environment }}
        BIFROST_VERSION: ${{ inputs.version }}
        BIFROST_PHASE: ${{ inputs.phase }}
        BIFROST_DEPLOYMENT: ${{ inputs.deployment }}
        BIFROST_STATUS: ${{ inputs.status }}
      run: |
        set -euo pipefail
        run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
        key="$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"

        if [ "$BIFROST_PHASE" = "start" ]; then
          body=$(jq -n \
            --arg environment "$BIFROST_ENVIRONMENT" \
            --arg version "$BIFROST_VERSION" \
            --arg sha "$GITHUB_SHA" \
            --arg branch "$GITHUB_REF_NAME" \
            --arg url "$run_url" \
            '{environment_id: $environment, version: $version, status: "running", commit_sha: $sha, branch: $branch, pipeline_url: $url}')

          id=$(curl -sS --fail-with-body -X POST "$BIFROST_API/v1/projects/$BIFROST_PROJECT/deployments" \
            -H "Authorization: Bearer $BIFROST_TOKEN" \
            -H "Content-Type: application/json" \
            -H "Idempotency-Key: $key-start" \
            -d "$body" | jq -r '.data.id')

          echo "deployment=$id" >> "$GITHUB_OUTPUT"
          echo "Opened deployment $id of $BIFROST_VERSION."
        else
          curl -sS --fail-with-body -X PATCH "$BIFROST_API/v1/deployments/$BIFROST_DEPLOYMENT" \
            -H "Authorization: Bearer $BIFROST_TOKEN" \
            -H "Content-Type: application/json" \
            -H "Idempotency-Key: $key-finish" \
            -d "{\"status\": \"$BIFROST_STATUS\"}" > /dev/null

          echo "Finished deployment $BIFROST_DEPLOYMENT as $BIFROST_STATUS."
        fi

The run id and the attempt make the idempotency key, so a re-run of the same attempt records nothing twice and a fresh attempt records a fresh deployment.

Use it in a workflow

Call the action once before the deployment and once after, with a finish step that always runs and reads the job's own outcome:

.github/workflows/deploy-staging.yml
name: Deploy to staging

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - id: report
        uses: ./.github/actions/zuse-bifrost-deployment
        with:
          api: ${{ vars.BIFROST_API }}
          token: ${{ secrets.BIFROST_TOKEN }}
          project: ${{ vars.BIFROST_PROJECT }}
          environment: ${{ vars.BIFROST_STAGING }}
          version: ${{ github.sha }}
          phase: start

      - name: Deploy
        run: ./scripts/deploy.sh staging

      - if: always()
        uses: ./.github/actions/zuse-bifrost-deployment
        with:
          api: ${{ vars.BIFROST_API }}
          token: ${{ secrets.BIFROST_TOKEN }}
          project: ${{ vars.BIFROST_PROJECT }}
          environment: ${{ vars.BIFROST_STAGING }}
          version: ${{ github.sha }}
          phase: finish
          deployment: ${{ steps.report.outputs.deployment }}
          status: ${{ job.status == 'success' && 'succeeded' || (job.status == 'cancelled' && 'cancelled' || 'failed') }}

Use a tag or a version file as version on a release workflow (${{ github.ref_name }} on a tag push); the commit is reported beside it either way. A workflow that deploys to several environments calls the pair once per environment with that environment's id.

Secrets and variables

Under the repository's settings, add the token as a secret and the rest as variables, so the workflow file carries no ids of its own:

NameKindValue
BIFROST_TOKENsecretThe ingestion token, shown once when it was minted.
BIFROST_APIvariableThe API base address your workspace answers on.
BIFROST_PROJECTvariableThe project's id.
BIFROST_STAGINGvariableThe staging environment's id; one variable per environment.

An organisation that deploys many repositories into the same workspace keeps the token and the address as organisation-level secrets and variables, and the ids per repository.

Report in one step instead

A workflow that only wants the outcome on record reports once, after the deployment, with the outcome as the status:

One report after the deployment
- if: always()
  env:
    BIFROST_API: ${{ vars.BIFROST_API }}
    BIFROST_TOKEN: ${{ secrets.BIFROST_TOKEN }}
    BIFROST_PROJECT: ${{ vars.BIFROST_PROJECT }}
    BIFROST_STAGING: ${{ vars.BIFROST_STAGING }}
    OUTCOME: ${{ job.status == 'success' && 'succeeded' || 'failed' }}
  run: |
    curl -sS --fail-with-body -X POST "$BIFROST_API/v1/projects/$BIFROST_PROJECT/deployments" \
      -H "Authorization: Bearer $BIFROST_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" \
      -d "{\"environment_id\": \"$BIFROST_STAGING\", \"version\": \"$GITHUB_SHA\", \"status\": \"$OUTCOME\", \"commit_sha\": \"$GITHUB_SHA\", \"branch\": \"$GITHUB_REF_NAME\", \"pipeline_url\": \"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\"}"

Or use GitHub deployments

A workflow that creates a GitHub deployment, through the Deployments API or an action built on it, needs none of the above when the repository was picked from the team's App installation: the App hears the deployment and each status the workflow gives it, and records them on the environment whose name matches the one the workflow used. Name the environments in Zuse Bifrost the way the workflow names them, and set a version field in the deployment's payload when you want the record to carry a version rather than the branch or the tag; see CI and deployments.

One or the other

Use the action, or GitHub deployments, not both for the same job: each writes its own record, and a job that does both shows twice.

Frequently asked

The start step fails with 403. The token lacks ci:deploy, is confined to another project, or belongs to someone whose role does not carry deployment:record. Mint it again as described on CI/CD ingestion.

The finish step fails with 422. The deployment ended already, which happens when a re-run of the same attempt reaches the finish step twice; the record is right and the job may ignore it.

The start step fails with 404. The environment id is not one of the project's. Copy it from the environment's page address, and keep one variable per environment.

Can I report the test results too? The test management module brings ci:test-results and a step that uploads a JUnit report; the deployment steps stay as they are.

  • CI/CD ingestion: the token and the calls the action makes.
  • GitLab CI: the same from GitLab, and from any other pipeline.
  • CI and deployments: what the record looks like inside the application.
  • GitHub: the App, whose deployment events are the alternative.

How is this guide?

Last updated on

On this page

What changed recentlyEvery release of Zuse Bifrost, with the pages it touched.

For AI agents: llms.txt ยท llms-full.txt