Skip to content

CI/CD examples

These examples run a Helena test from a pipeline and fail the job unless the run passes. Each one needs:

  • A service account with the Write scope and access to the test's environment.
  • The environment UUID and test UUID, stored as pipeline variables (HELENA_ENV_UUID, HELENA_TEST_UUID).
  • curl and jq on the runner.

Prefer OIDC workload identity where your CI platform supports it. Then there is no Helena secret to store, leak or rotate.

GitHub Actions (OIDC workload identity)

On the service account, add an OIDC trust:

Field Value
Issuer URL https://token.actions.githubusercontent.com
Subject (sub claim) repo:acme/app:ref:refs/heads/main
Audience (aud claim) https://app.helena.dev/api/v1/service-account/auth (the default)
Additional claim conditions repository_owner = acme (optional)

Store the trust's client ID as the repository variable HELENA_CLIENT_ID.

name: Helena checks
on:
  push:
    branches: [main]
  schedule:
    - cron: "0 6 * * *"

permissions:
  id-token: write   # required to request the OIDC token
  contents: read

jobs:
  helena:
    runs-on: ubuntu-latest
    steps:
      - name: Get Helena token
        run: |
          ID_TOKEN=$(curl -sf -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://app.helena.dev/api/v1/service-account/auth" | jq -r .value)
          TOKEN=$(curl -sf -X POST https://app.helena.dev/api/v1/service-account/auth \
            -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
            -d client_id=${{ vars.HELENA_CLIENT_ID }} -d assertion="$ID_TOKEN" | jq -r .access_token)
          echo "::add-mask::$TOKEN"
          echo "HELENA_TOKEN=$TOKEN" >> "$GITHUB_ENV"

      - name: Run Helena test
        run: |
          curl -sf -X POST -H "Authorization: Bearer $HELENA_TOKEN" -H 'Content-Type: application/json' \
            -d '{"wait": true}' \
            "https://app.helena.dev/api/v1/environments/${{ vars.HELENA_ENV_UUID }}/tests/${{ vars.HELENA_TEST_UUID }}/runs" \
            | tee result.json | jq -e '.data.status == "passed"'

      - name: Show failed assertions
        if: failure()
        run: jq '.data.assertions[] | select(.passed == false)' result.json

For pull requests the subject is repo:acme/app:pull_request. For a GitHub environment, it is repo:acme/app:environment:production. Add one trust per subject you want to allow.

GitLab CI (OIDC workload identity)

On the service account, add an OIDC trust:

Field Value
Issuer URL https://gitlab.com (or your self-managed GitLab URL)
Subject (sub claim) project_path:acme/app:ref_type:branch:ref:main
Audience (aud claim) https://app.helena.dev/api/v1/service-account/auth
helena:
  image: alpine:3.20
  id_tokens:
    HELENA_ID_TOKEN:
      aud: https://app.helena.dev/api/v1/service-account/auth
  before_script:
    - apk add --no-cache curl jq
  script:
    - >
      TOKEN=$(curl -sf -X POST https://app.helena.dev/api/v1/service-account/auth
      -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
      -d client_id=$HELENA_CLIENT_ID -d assertion=$HELENA_ID_TOKEN | jq -r .access_token)
    - >
      curl -sf -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json'
      -d '{"wait": true}'
      "https://app.helena.dev/api/v1/environments/$HELENA_ENV_UUID/tests/$HELENA_TEST_UUID/runs"
      | tee result.json | jq -e '.data.status == "passed"'
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

A self-managed GitLab instance must be reachable from the internet over HTTPS, so Helena can fetch its OIDC discovery document and keys.

Any CI (client secret)

Store the client ID and secret as masked secrets (HELENA_CLIENT_ID, HELENA_CLIENT_SECRET).

#!/usr/bin/env bash
set -euo pipefail

TOKEN=$(curl -sf -X POST https://app.helena.dev/api/v1/service-account/auth \
  -d grant_type=client_credentials \
  -d client_id="$HELENA_CLIENT_ID" \
  -d client_secret="$HELENA_CLIENT_SECRET" | jq -r .access_token)

curl -sf -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"wait": true}' \
  "https://app.helena.dev/api/v1/environments/$HELENA_ENV_UUID/tests/$HELENA_TEST_UUID/runs" \
  | tee result.json | jq -e '.data.status == "passed"'

# Revoke the token when done
curl -sf -X POST -H "Authorization: Bearer $TOKEN" https://app.helena.dev/api/v1/service-account/auth/revoke >/dev/null

Running every test in an environment

for TEST in $(curl -sf -H "Authorization: Bearer $TOKEN" \
    "https://app.helena.dev/api/v1/environments/$HELENA_ENV_UUID/tests" \
    | jq -r '.data[] | select(.enabled) | .uuid'); do
  STATUS=$(curl -sf -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
    -d '{"wait": true}' \
    "https://app.helena.dev/api/v1/environments/$HELENA_ENV_UUID/tests/$TEST/runs" | jq -r .data.status)
  echo "$TEST: $STATUS"
  [ "$STATUS" = "passed" ] || FAILED=1
done
exit ${FAILED:-0}

Triggering is limited to 30 runs per minute per service account. Add a short sleep for large environments.

Queue and poll instead of waiting

If your runner has a short HTTP timeout, queue the run and poll its Location:

LOCATION=$(curl -s -D - -o /dev/null -X POST -H "Authorization: Bearer $TOKEN" \
  "https://app.helena.dev/api/v1/environments/$HELENA_ENV_UUID/tests/$HELENA_TEST_UUID/runs" \
  | awk 'tolower($1)=="location:" {print $2}' | tr -d '\r')

until curl -sf -H "Authorization: Bearer $TOKEN" "$LOCATION" \
    | jq -e '.data.status | IN("passed","failed","error")' >/dev/null; do
  sleep 2
done

curl -sf -H "Authorization: Bearer $TOKEN" "$LOCATION" | jq -e '.data.status == "passed"'