diff --git a/CI-CD.md b/CI-CD.md index 0441181..2bad7b0 100644 --- a/CI-CD.md +++ b/CI-CD.md @@ -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:///api/packages +target = //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 `//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