Share an HTML report from Google Cloud Build

Cloud Build's UI is a log stream. That is an honest design — it is a build service, not a report host — and it means your coverage/index.html has exactly one destination: a GCS bucket, as an object, unrendered.

From there every path costs something:

  • Public bucket. Now the report that lists your failing tests, internal hostnames and dependency versions is world-readable, permanently, for every build you ever ran.
  • Signed URLs. Private, and they expire. The reviewer opens it on Thursday and gets ExpiredToken.
  • IAM on the bucket. Correct, and it means the customer, the auditor and the PM all need Google Cloud identities and a role binding before they can read a test report.

None of the three gives anyone a way to respond to what they read.

One step in cloudbuild.yaml

Put a scoped token (reports:write only) in Secret Manager, create the report once, and PATCH that id from every build:

availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/comma-api-token/versions/latest
      env: COMMA_API_TOKEN

substitutions:
  _COMMA_REPORT_ID: rep_xxxxxxxx

steps:
  - name: python:3.12
    id: test
    allowFailure: true
    entrypoint: bash
    args:
      - -c
      - |
        pip install -r requirements.txt
        pytest --cov --cov-report=html

  - name: gcr.io/cloud-builders/curl
    id: publish
    secretEnv: ["COMMA_API_TOKEN"]
    entrypoint: bash
    args:
      - -c
      - |
        apt-get -qq update && apt-get -qq install -y jq
        curl -fsS -X PATCH \
          "https://commareports.com/api/v1/reports/${_COMMA_REPORT_ID}" \
          -H "Authorization: Bearer $$COMMA_API_TOKEN" \
          -H "Content-Type: application/json" \
          -d "$(jq -n --rawfile html htmlcov/index.html \
                --arg title "Coverage — $SHORT_SHA" \
                '{title: $title, html: $html}')"
        echo "Report → https://commareports.com/p/${_COMMA_REPORT_ID}"

Three details are load-bearing:

  • allowFailure: true on the test step. Without it, Cloud Build halts at the failing step and the publish never runs — so the only builds that produce a report are the ones nobody needed one for. If you want the build to end red anyway, check $? in a final step and exit non-zero there.
  • secretEnv with $$COMMA_API_TOKEN. The doubled $ escapes the substitution so the shell, not Cloud Build, expands it — the token stays out of the build log.
  • The steps share /workspace, which is why the publish step can read htmlcov/index.html that the test step wrote.

What the published copy adds

  • A URL that renders, with no bucket configuration. The HTML is stored verbatim and served inside a sandboxed iframe with scripts enabled, so an interactive report stays interactive.
  • One address across every build. Revisions accumulate at the same URL and any two can be diffed.
  • Readers without a Google Cloud identity. Visibility is private, team, domain-gated, or link, decided per report — not by an IAM binding that outlives the reason for it.
  • Comments anchored to the content. A reviewer highlights the line that regressed and pins a thread there — see commenting on HTML.

Limits

  • HTML body: 5 MB. Larger files go in as assets at 25 MB per file, 250 MB per report.
  • A report that fetches sibling data files at view time can't, from the sandbox — publish a static digest with the archive attached, as in sharing an Allure report.
  • Rate limit: 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Create your first report →

Related