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: trueon 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.secretEnvwith$$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 readhtmlcov/index.htmlthat 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.
Related
- Publish from CI — the general pipeline pattern
- AWS CodeBuild HTML reports — the same shape, the other cloud
- Share a pytest report · Share a coverage report