feat(ci): auto-link pushed image to its repo via the Forgejo package API
All checks were successful
CI / preflight (push) Successful in 4s
CI / typecheck (push) Successful in 13s
ecosystem-selftest / semantic-release-bumptest (push) Successful in 11s
ecosystem-selftest / eslint-gate (push) Successful in 4s
ecosystem-selftest / yamllint-gate (push) Successful in 3s
pulumi-preview / preview (push) Successful in 15s
All checks were successful
CI / preflight (push) Successful in 4s
CI / typecheck (push) Successful in 13s
ecosystem-selftest / semantic-release-bumptest (push) Successful in 11s
ecosystem-selftest / eslint-gate (push) Successful in 4s
ecosystem-selftest / yamllint-gate (push) Successful in 3s
pulumi-preview / preview (push) Successful in 15s
Forgejo 11 does not auto-link container packages from org.opencontainers.image.source
(verified: label and manifest annotation both leave repo_id=0), so the reusable
workflow now POSTs to the package link API as ci-bot after push. Link persists across
future pushes. Also documents the Forgejo-11 reusable-workflow quirks surfaced this
session: secrets need 'secrets: inherit', and @master reusable refs are stale-parsed
(pin the SHA). Real repo-packages URL is /{owner}/-/packages?repo=<repo>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2fe9a4d43e
commit
110a199495
2 changed files with 65 additions and 14 deletions
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
## Forgejo 11 quirk (IMPORTANT)
|
||||
|
||||
Our forge runs **Forgejo 11.0.15**, where reusable-workflow support is the
|
||||
**pre-v15 "limited" implementation**. Two rules differ from GitHub / Forgejo ≥ v15:
|
||||
**pre-v15 "limited" implementation**. These rules differ from GitHub / Forgejo ≥ v15:
|
||||
|
||||
1. **The calling job MUST declare `runs-on`** (e.g. `runs-on: docker`). On standard
|
||||
GitHub you omit `runs-on` on a `uses:` job — do that here and Forgejo **silently
|
||||
|
|
@ -35,6 +35,21 @@ Our forge runs **Forgejo 11.0.15**, where reusable-workflow support is the
|
|||
e.g. `with: { image: ..., push: false }` on `reusable-docker-build` — rather than
|
||||
trusting the default.
|
||||
|
||||
4. **Secrets are NOT inherited by the called workflow — pass `secrets: inherit`.**
|
||||
Org-level Actions secrets are visible to the CALLER but `${{ secrets.X }}` is
|
||||
**empty inside the reusable workflow** unless the caller forwards them. Verified
|
||||
live: `reusable-docker-build`'s `docker login` failed with `username is empty`
|
||||
until the caller added `secrets: inherit` (which `runs-on`-jobs accept fine).
|
||||
|
||||
5. **`@master` reusable refs are stale-parsed — PIN to a commit SHA.** Forgejo 11
|
||||
caches the reusable-workflow body: after pushing an updated
|
||||
`reusable-docker-build.yml` to `foundation` master, callers at `...@master` kept
|
||||
running the OLD body (no error — caller `with:`/`push` inputs updated, but the
|
||||
steps didn't). Pin the caller to the commit SHA
|
||||
(`...reusable-docker-build.yml@<sha>`) to force the new body; bump the SHA when the
|
||||
reusable workflow changes. (A `foundation-forgejo` restart also clears the cache,
|
||||
but SHA-pinning is deterministic and the better practice.)
|
||||
|
||||
Also pre-v15: the called workflow's logs collapse into a single "Set up job" entry
|
||||
in the UI. **Forgejo v15.0** (LTS, Apr 2026) reworks this — omit `runs-on` and Forgejo
|
||||
expands the reusable workflow into its inner jobs with separate logs. On a future v15
|
||||
|
|
@ -70,21 +85,35 @@ proven by `ecosystem-selftest.yml` on the foundation's own runner.
|
|||
### Pushing images to the forge container registry
|
||||
|
||||
`reusable-docker-build` with `push: true` pushes the built image to Forgejo's
|
||||
built-in container registry and — when `source-url` is passed — links the package to
|
||||
its repo's `/packages` page (via the `org.opencontainers.image.source` OCI label). It
|
||||
authenticates as the org-scoped **`ci-bot`** user, reading org-level Actions secrets
|
||||
`FORGE_REGISTRY_USER` / `FORGE_REGISTRY_TOKEN`. Those secrets + the `ci-bot` identity
|
||||
are provisioned out-of-band as a Tier-1 step ([`ci-bot/`](../../ci-bot/), peer to
|
||||
`runners/`), **not** in `bootstrap` — so CI never depends on Vault being unsealed.
|
||||
A calling repo:
|
||||
built-in container registry and links the package to its repo. It authenticates as
|
||||
the org-scoped **`ci-bot`** user, reading org-level Actions secrets
|
||||
`FORGE_REGISTRY_USER` / `FORGE_REGISTRY_TOKEN` (forwarded via `secrets: inherit` —
|
||||
see quirk 4). Those secrets + the `ci-bot` identity are provisioned out-of-band as a
|
||||
Tier-1 step ([`ci-bot/`](../../ci-bot/), peer to `runners/`), **not** in `bootstrap`
|
||||
— so CI never depends on Vault being unsealed. A calling repo:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
image:
|
||||
runs-on: docker
|
||||
uses: olsitec/foundation/.forgejo/workflows/reusable-docker-build.yml@master
|
||||
runs-on: docker # quirk 1
|
||||
uses: olsitec/foundation/.forgejo/workflows/reusable-docker-build.yml@<sha> # quirk 5: PIN the SHA
|
||||
secrets: inherit # quirk 4
|
||||
with:
|
||||
image: "forge.olsitec.net/olsitec/<repo>:ci"
|
||||
source-url: "https://forge.olsitec.net/olsitec/<repo>"
|
||||
push: true
|
||||
```
|
||||
|
||||
Two Forgejo-11 container-registry facts the workflow already handles for you:
|
||||
|
||||
- **Single-manifest build.** The build runs `--provenance=false --sbom=false` so the
|
||||
push is one image manifest, not an OCI index with attestations. An index both hides
|
||||
the source label and litters the registry with junk `sha256:`-tagged versions.
|
||||
- **Explicit repo-link (no auto-link from the label).** Forgejo 11 does **not**
|
||||
auto-link a container package to a repo from `org.opencontainers.image.source`
|
||||
(verified: label *and* manifest annotation both leave `repo_id=0`). The "Link
|
||||
package to repo" step calls `POST /api/v1/packages/{owner}/container/{name}/-/link/{repo}`
|
||||
as ci-bot. The link **persists** across future pushes (Forgejo leaves `repo_id`
|
||||
untouched on version push). Linked packages appear at
|
||||
**`/{owner}/-/packages?repo=<repo>`** — Forgejo has no `/{owner}/{repo}/packages`
|
||||
route (that URL 404s regardless). A Forgejo **v15** upgrade may add auto-link — revisit then.
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@
|
|||
# PUSH + REPO-LINK: with push:true the image is pushed to Forgejo's built-in
|
||||
# container registry (forge.olsitec.net/olsitec/<name>) authenticating as the
|
||||
# org-level `ci-bot` (org Actions secrets FORGE_REGISTRY_USER/FORGE_REGISTRY_TOKEN,
|
||||
# provisioned out-of-band as a Tier-1 step — see HANDOVER / provision/ci-bot).
|
||||
# Passing `source-url` stamps the OCI label org.opencontainers.image.source so
|
||||
# Forgejo links the package to that repo's /packages page (without it the package
|
||||
# only shows on the org-level packages page).
|
||||
# provisioned out-of-band as a Tier-1 step — see HANDOVER / ci-bot/). The image
|
||||
# carries the OCI label org.opencontainers.image.source (from `source-url`), and —
|
||||
# because Forgejo 11 does NOT auto-link container packages from that label — the
|
||||
# "Link package to repo" step calls Forgejo's package link API so the package shows
|
||||
# under the repo (/{owner}/-/packages?repo=<repo>), not just the org page.
|
||||
name: reusable-docker-build
|
||||
on:
|
||||
workflow_call:
|
||||
|
|
@ -90,3 +91,24 @@ jobs:
|
|||
- name: Push
|
||||
if: ${{ inputs.push }}
|
||||
run: docker push "${{ inputs.image }}"
|
||||
|
||||
- name: Link package to repo
|
||||
if: ${{ inputs.push && inputs.source-url != '' }}
|
||||
# Forgejo 11 does not auto-link container packages from the OCI source
|
||||
# label, so link explicitly via the package API (owner/repo parsed from
|
||||
# source-url; package name from the image ref). Idempotent-ish: 201 = newly
|
||||
# linked, 400 = already linked. Never fails the job — the push already
|
||||
# succeeded; linking is secondary. The link persists across future pushes.
|
||||
run: |
|
||||
src="${{ inputs.source-url }}"; repo="${src##*/}"
|
||||
rest="${src%/*}"; owner="${rest##*/}"
|
||||
base="${{ inputs.image }}"; base="${base##*/}"; name="${base%%:*}"
|
||||
code=$(curl -s -o /tmp/link.out -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${{ secrets.FORGE_REGISTRY_TOKEN }}" \
|
||||
"https://forge.olsitec.net/api/v1/packages/$owner/container/$name/-/link/$repo")
|
||||
echo "link $owner/container/$name -> $repo : http=$code"
|
||||
case "$code" in
|
||||
201) echo "linked." ;;
|
||||
400) echo "already linked (400) — ok." ;;
|
||||
*) echo "WARNING: unexpected link status $code: $(cat /tmp/link.out)" ;;
|
||||
esac
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue