CI-CD: the publish target is composed from a base and the repository owner

charts-url in the registry vault item held the full registry URL and the
workflow appended /api/charts. It now holds only the base up to
/api/packages, and the owner segment comes from github.repository_owner,
which this Gitea instance does populate (measured).

Records what the composition buys, the guards that make a mismatch
between vault item and workflow a named error instead of a 404 against a
masked URL, the order of the hard cut, and why proving the new path
takes a manual dispatch.
jklein committed 2026-09-30 18:51:41 +00:00
1 parent 0f631e747f
commit d7ce2055a0
1 file changed
+30 -1
+30 -1
@@ -8,7 +8,36 @@ Two pipelines: one that **validates** a pull request, one that **publishes** a c
- **A push to a chart directory on `main` publishes that chart.** The chart version must be bumped in the same change; the PR pipeline enforces it.
- **`publish-helm-chart.yml`** — the reusable workflow: fetch dependencies, `helm package`, then a single HTTP `POST` of the `.tgz` to the registry.
The registry is a **classic, index-based** Helm registry (Gitea's `/api/charts` endpoint), not an OCI one: `helm push` speaks only OCI and is not used, there is no `helm registry login` session, and every request carries its own Basic auth. Credentials **and the target URL** come from 1Password Connect (item `jlab-registry-access` with `username`, `password`, `charts-url`); the workflow appends `/api/charts` to the URL. No registry address appears anywhere in the repository.
The registry is a **classic, index-based** Helm registry (Gitea's `/api/charts` endpoint), not an OCI one: `helm push` speaks only OCI and is not used, there is no `helm registry login` session, and every request carries its own Basic auth. Credentials **and the target URL** come from 1Password Connect (item `jlab-registry-access` with `username`, `password`, `charts-url`).
### The target URL is composed, not stored
`charts-url` holds only the **base up to `/api/packages`**. The workflow appends the rest:
```
op://GitHub-Actions/jlab-registry-access/charts-url = https://<host>/api/packages
target = <base>/<owner>/helm/api/charts
```
The owner segment comes from **`github.repository_owner`** — verified to be populated on this Gitea instance (measured as `jlab-cluster` on a temporary probe step, since removed). Consequences worth knowing:
- **No registry address appears anywhere in the repository**, and since this change not even the owner path segment: it is derived from the repository rather than stored. The 26 thin callers need no setting at all.
- The **second owner on the same instance** (`suer`, for `suer-configurator`) gets the right target from the same workflow, without a second value to keep in step.
- On the **github.com mirror** the owner is a different one — but Actions are switched off there, so the publish never runs. If it ever did, the derived owner would be one the instance does not know: HTTP 404, loud, not a silent upload to the wrong place.
Two guards make a mismatch between vault item and workflow loud rather than puzzling, because the only other signal is a 404 against a URL 1Password has masked:
| Guard | Rejects |
| --- | --- |
| `charts-url` must not end in `/api/charts` | the old, full URL; the error names `/api/packages` as the shape to store |
| `charts-url` must not contain a `/helm` segment | the old URL's package-type segment |
| `charts-url` must end in `/api/packages` | everything else, a base that already carries the owner included |
| derived owner non-empty | an empty `github.repository_owner`, which would build `<base>//helm/api/charts` |
| derived owner is a plain path segment (`[A-Za-z0-9._-]`) | a `/`, a space or a `?`, which would build a different URL entirely |
**Changing this is a hard cut:** the maintainer shortens the vault item to the base **first**, then the workflow change is merged. Both mismatches fail with a 404 and neither publishes to a wrong place.
One more thing to know about proving it: a change to `publish-helm-chart.yml` touches no chart directory, so **merging it triggers no publish** and leaves the new path unproven until the next chart change. The end-to-end check is a single manual `workflow_dispatch` of `publish-helm-chart.yml` for any chart — the registry replaces an already published version on re-upload (HTTP 201, see below), so it is a safe test of the whole path. Only the maintainer can trigger it; the bot account has no write permission on the repository.
### Three properties of the upload step, each paid for once