Skip to content

Commit ebceef4

Browse files
ci(retention): sweep packages named by the published nightly tree (#4826)
<!-- Thank you for making a contribution! Here are some tips for you: - Use Conventional Commits for the PR title: `type(scope): description` - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore - Scopes are not an exhaustive list — pick the most specific scope for the change and extend the list when a genuinely new area appears. Examples: - System components: dashboard, platform, operator, cilium, kube-ovn, linstor, fluxcd, cluster-api - Managed apps: postgres, mariadb, redis, kafka, clickhouse, virtual-machine, kubernetes - Development and maintenance: api, hack, tests, ci, docs, maintenance - Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or add a `BREAKING CHANGE:` footer - If it's a work in progress, consider creating this PR as a draft. - Don't hesistate to ask for opinion and review in the community chats, even if it's still a draft. - Add the label `kind/backport` if it's a bugfix that needs to be backported to a previous version. --> ## What this PR does The Artifact Retention workflow has failed every day since mid-August, and it never pruned anything. It lists the org's container packages with `GET /orgs/cozystack/packages?package_type=container`, and that endpoint answers `400 Invalid argument` to the app installation token the job uses. Others hit the same in actions/github-script#449 and cli/cli#9606. An old `|| true` used to hide it. This is item A19 in #3278. The per-package endpoints accept the same token: a probe run confirmed it, and the dry-run below read every package with it. So the job no longer lists the org. The new `hack/retention-packages.sh` takes the package names from the published `cozystack-packages:nightly` tree, reading refs through the shared `hack/lib/image-refs.sh`. It also adds `cozy-installer` and `cozystack-nocloud`, which the nightly publishes outside the tree. The committed tree can't be the source: it misses images whose digest only the build pins, and those still get dated nightly tags every day. Failures now turn the run red instead of passing silently: - if the name list is empty, the nightly sweep is skipped and the run fails, but abandoned promotion candidates are still handled, because their protection doesn't depend on that list - if a package's versions can't be read, that package is skipped and the run fails at the end - if a DELETE is refused, `hack/retention-delete.sh` logs an error with the HTTP status, goes on with the remaining versions and fails the run (before, this was only a warning) - if names resolve but none of the packages is linked to this repository, nothing gets swept, so the run fails too, after the promotion sweep has run (that sweep itself is skipped only when candidate protection can't be resolved) Promotion-candidate protection itself is unchanged. The contract test that pinned the old org listing now pins the new behaviour, and both new scripts have bats tests. Known gap: a component that disappears from the nightly tree drops off the list, so its old dated nightlies are never pruned. Today none of the packages outside the list has more dated nightlies than the keep window. Dry-run on this branch is green: https://github.com/cozystack/cozystack/actions/runs/38075159900. It resolves 59 packages, skips `cozystack/cozystack-ui` with the warning, and would delete 418 old nightly versions plus one abandoned promotion candidate. Deleting needs a different token. The app token lists versions, but a one-version apply run (https://github.com/cozystack/cozystack/actions/runs/37939905343) got `404 Package not found` on DELETE for a version it had just listed. The same DELETE with this repository's `GITHUB_TOKEN` and the job's `packages: write` worked in a second run (https://github.com/cozystack/cozystack/actions/runs/37941084456), with no change to package settings. So `hack/retention-delete.sh` now deletes with `GITHUB_TOKEN`, and listing stays on the app token. That delete was shown on a package linked to this repository. One of the swept packages, `cozystack/cozystack-ui`, is linked to another repository, and I didn't try deleting there. Such a package is skipped before its versions are read and named in a warning, so it can't turn every apply run red. The follow-up for it is in #3278. `RETENTION_APPLY` stays off, so the scheduled run is a green dry-run until someone turns it on. ### Screenshots <!-- REQUIRED for UI changes: attach screenshots or screen recordings demonstrating the visual impact of your changes. PRs with UI changes without screenshots will not be merged. --> N/A ### Downstream repositories <!-- Cozystack is upstream for repositories that are NOT kept in sync with it automatically, and nothing in CI compares the two sides, so a change here can break them silently. Which change forces what, and which file to touch, is the trigger map in docs/agents/contributing.md under "Downstream Repositories". Walk it before you tick anything. IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM TO FILL IN: 1. Walk the trigger map against the actual diff of this PR, file by file. Do not decide from the PR title or from your memory of what you changed. 2. For every repository you tick, open the follow-up PR there and link it on the same line. A ticked box with no link claims work that does not exist, which is worse than an empty box. Search that repository for an open PR or issue covering it first, and link that instead of filing a duplicate. 3. When the follow-up is out of scope, or needs a decision that is not yours to make, open an issue in that repository instead, link that, and say so in the PR body. Do not push a speculative PR into another repository just to fill a line here. 4. Do not tick "No downstream repository is affected" to make the checklist go away. If you are unsure, leave every box empty and say so in the PR body, so a human decides. --> - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note <!-- Write a release note: - Explain what has changed internally and for users. - Start with the same `type(scope):` prefix as in the PR title - Follow the guidelines at https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md. --> ```release-note ci(retention): the GHCR retention workflow takes package names from the published nightly tree instead of the org package listing, which rejects the app token, and fails the run whenever names, versions or deletions cannot be resolved. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Nightly package retention now targets packages referenced by the published nightly tree and keeps the 14 newest eligible versions per package. * Promotion candidates older than 30 days are eligible for pruning only when their digests are not protected by qualifying release pull requests or branch tips. * **Bug Fixes** * Packages linked to another repository are skipped, and sweep failures are reported without preventing other eligible packages from being processed. * **Documentation** * Clarified which nightly versions and promotion candidates qualify for retention. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 34488db + da1f1cc commit ebceef4

10 files changed

Lines changed: 497 additions & 105 deletions

‎.github/workflows/retention.yaml‎

Lines changed: 96 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
name: Artifact Retention
22

33
# Prune old nightly artifacts from GHCR: keep the newest KEEP dated versions of
4-
# each cozystack package, delete older ones. Nightlies are GHCR OCI tags
5-
# (0.0.0-nightly.<date>.<run-id>.<attempt> on every component image plus
6-
# cozystack-packages, cozy-installer and cozystack-nocloud) — not git tags or
7-
# releases — so this prunes container-package versions via the GitHub Packages
8-
# API.
4+
# each cozystack package the current nightly publishes, delete older ones.
5+
# Nightlies are GHCR OCI tags (0.0.0-nightly.<date>.<run-id>.<attempt> on every
6+
# component image plus cozystack-packages, cozy-installer and cozystack-nocloud)
7+
# — not git tags or releases — so this prunes container-package versions via
8+
# the GitHub Packages API.
99
#
1010
# Strictly scoped: only versions whose tags are ALL of the form
1111
# 0.0.0-nightly.<YYYYMMDD>.<run-id>.<attempt> are eligible. A version that also
@@ -18,15 +18,19 @@ name: Artifact Retention
1818
# tag, so a retry never moves one and leaves an untagged manifest behind. A
1919
# successful promotion adds a stable tag to the same manifest, which makes it
2020
# ineligible below. A candidate stays protected while an open bot-authored
21-
# release PR pins its digest; otherwise it becomes eligible after
22-
# PROMOTION_TTL_DAYS only when ALL its tags are still temporary.
21+
# release PR, or the tip of main or a release-X.Y branch, pins its digest;
22+
# otherwise it becomes eligible after PROMOTION_TTL_DAYS only when ALL its tags
23+
# are still temporary.
2324
#
2425
# Dry-run by default; flip the input or the RETENTION_APPLY repo variable to
2526
# actually delete. Deletion is irreversible.
2627
#
27-
# NOTE (admin): deleting org container-package versions requires the
28-
# cozystack-ci app installation to carry the `packages: write` permission. If
29-
# the delete step 403s, grant it there — this workflow cannot.
28+
# NOTE (admin): the cozystack-ci app installation token cannot delete here: it
29+
# lists package versions, yet DELETE of a listed version answered "404 Package
30+
# not found". This repository's GITHUB_TOKEN, with the job's packages: write
31+
# and no change to package settings, deleted the same version from a package
32+
# linked to this repository, so deletes use it. A package linked to another
33+
# repository is skipped with a warning.
3034

3135
on:
3236
schedule:
@@ -60,6 +64,16 @@ jobs:
6064
packages: write
6165
pull-requests: read
6266
steps:
67+
- name: Checkout
68+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
69+
with:
70+
persist-credentials: false
71+
72+
- name: Set up flux
73+
uses: fluxcd/flux2/action@04acaec6161ac4fb1a82ffafa88901c03271d34f # v2.8.6
74+
with:
75+
version: "2.8.6"
76+
6377
- name: Generate GitHub App token
6478
id: app-token
6579
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
@@ -73,6 +87,8 @@ jobs:
7387
GH_TOKEN: ${{ steps.app-token.outputs.token }}
7488
# workflow_dispatch input wins; otherwise the repo variable; else dry-run.
7589
APPLY: ${{ github.event.inputs.apply || vars.RETENTION_APPLY || 'false' }}
90+
# See the admin NOTE above for why deletes do not use the app token.
91+
DELETE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7692
run: |
7793
set -euo pipefail
7894
if [ "$APPLY" = "true" ]; then echo "MODE: APPLY (deleting)"; else echo "MODE: dry-run"; fi
@@ -187,42 +203,57 @@ jobs:
187203
protected_count="$(printf '%s\n' "$protected_promotion_digests" | awk '{print NF}')"
188204
echo "Protecting ${protected_count} promotion candidate digest(s) named by an open release PR or a base-branch tip."
189205
190-
# All container packages under the org that live in the cozystack/
191-
# registry namespace (ghcr.io/cozystack/cozystack/<name>).
206+
# The cozystack/* packages to sweep. GitHub refuses to list org
207+
# container packages to an app installation token (400 Invalid
208+
# argument), so the names come from the published nightly tree; see
209+
# hack/retention-packages.sh for why that tree and what it covers.
192210
#
193-
# Listed and filtered as two statements, because one `|| true` cannot
194-
# mean two things. `grep` legitimately exits 1 when the org has no
195-
# cozystack/* package, and a failed listing produces the same empty
196-
# output, so a trailing `|| true` covering both turned an API failure
197-
# into "no packages found" — which exits 0 BELOW the nightly sweep and
198-
# ABOVE the deferred guard, so an unresolvable protection set ended in
199-
# a green run that swept nothing. That bypasses the guard rather than
200-
# varying it.
201-
all_pkgs="$(gh api --paginate "/orgs/${ORG}/packages?package_type=container" \
202-
--jq '.[].name')" || {
203-
echo "::error::Could not list ${ORG} container packages; refusing retention of promotion candidates."
204-
promotion_retention_blocked=1
205-
all_pkgs=""
211+
# An empty list is never "nothing to do": the nightly publishes into
212+
# these packages every day, so empty means the names could not be
213+
# resolved. That skips the nightly sweep and fails the run, but not
214+
# before the promotion sweep below: it reads one fixed package, and
215+
# what keeps a candidate safe is the protected set, which no part of
216+
# this list feeds.
217+
#
218+
# A package whose versions cannot be read, or a version that cannot
219+
# be deleted, is likewise reported and skipped so the rest is still
220+
# swept, then fails the run at the end. The loop is fed by a
221+
# here-string rather than a pipe so the flag survives it.
222+
sweep_failed=0
223+
unlinked=""
224+
linked_count=0
225+
gh_err="$(mktemp)"
226+
tree="$(mktemp -d)"
227+
pkgs="$(flux pull artifact "oci://ghcr.io/${ORG}/cozystack/cozystack-packages:nightly" --output "$tree" >/dev/null \
228+
&& ./hack/retention-packages.sh "$tree")" || {
229+
echo "::error::Could not resolve the ${ORG} container packages to sweep."
230+
pkgs=""
206231
}
207-
# grep's exit 1 now means only what it says: nothing matched.
208-
pkgs="$(printf '%s\n' "$all_pkgs" | grep '^cozystack/' || true)"
209232
if [ -z "$pkgs" ]; then
210-
# Fail-closed before the early exit. The flag set here means this
211-
# listing or a protection source above went unresolved, and there is
212-
# no sweep left to run that could carry the run to the guard below —
213-
# so this is the last place the flag can still be honoured.
214-
[ "$promotion_retention_blocked" -eq 0 ] || {
215-
echo "::error::candidate protection could not be resolved and nothing was swept; refusing to report success."
216-
exit 1
217-
}
218-
echo "No cozystack/* container packages found."
219-
exit 0
233+
echo "::error::No package names resolved; skipping the nightly sweep."
234+
sweep_failed=1
235+
else
236+
echo "Sweeping $(printf '%s\n' "$pkgs" | wc -l | tr -d ' ') package(s):"
237+
printf '%s\n' "$pkgs" | sed 's/^/ /'
220238
fi
221-
222-
printf '%s\n' "$pkgs" | while IFS= read -r pkg; do
239+
while IFS= read -r pkg; do
223240
[ -n "$pkg" ] || continue
224241
enc="$(jq -rn --arg p "$pkg" '$p|@uri')"
225242
243+
# DELETE_TOKEN was shown to delete from a package linked to this
244+
# repository; one linked elsewhere is not attempted, only named in
245+
# a warning, so it cannot fail every apply run.
246+
linked="$(gh api "/orgs/${ORG}/packages/container/${enc}" --jq '.repository.full_name // ""' 2>"$gh_err")" || {
247+
echo "::error::Could not read ${pkg}; not swept: $(cat "$gh_err")"
248+
sweep_failed=1
249+
continue
250+
}
251+
if [ "$linked" != "$GITHUB_REPOSITORY" ]; then
252+
unlinked="${unlinked:+${unlinked} }${pkg}"
253+
continue
254+
fi
255+
linked_count=$((linked_count + 1))
256+
226257
# Version ids whose tags are non-empty AND every tag is a dated
227258
# nightly (so the floating `nightly` tag and any release tag exclude
228259
# the version). API returns versions newest-first, so the dated
@@ -231,28 +262,29 @@ jobs:
231262
# array per page, so length/slicing break once a package spans
232263
# multiple pages. Slurp the raw pages and merge with `jq -s add`
233264
# first, then count and slice the combined array.
234-
ids="$(gh api --paginate "/orgs/${ORG}/packages/container/${enc}/versions" \
265+
ids="$(gh api --paginate "/orgs/${ORG}/packages/container/${enc}/versions" 2>"$gh_err" \
235266
| jq -s '[add[]
236267
| select((.metadata.container.tags | length) > 0)
237268
| select(.metadata.container.tags | all(test("^0\\.0\\.0-nightly\\.[0-9]{8}(\\.[0-9]+)*$")))
238-
| .id]')"
269+
| .id]')" || {
270+
echo "::error::Could not read ${pkg} versions; not swept: $(cat "$gh_err")"
271+
sweep_failed=1
272+
continue
273+
}
239274
240275
total="$(printf '%s' "$ids" | jq 'length')"
241276
[ "$total" -gt "$KEEP" ] || { echo "── ${pkg}: ${total} nightlies (<= ${KEEP}); nothing to prune"; continue; }
242277
drop_ids="$(printf '%s' "$ids" | jq -r ".[${KEEP}:][]")"
243278
echo "── ${pkg}: ${total} nightlies, keep ${KEEP}, drop $((total - KEEP))"
244279
245-
printf '%s\n' "$drop_ids" | while IFS= read -r id; do
246-
[ -n "$id" ] || continue
247-
if [ "$APPLY" = "true" ]; then
248-
echo " ✗ deleting ${pkg} version ${id}"
249-
gh api -X DELETE "/orgs/${ORG}/packages/container/${enc}/versions/${id}" >/dev/null \
250-
|| echo "::warning::failed to delete ${pkg} version ${id} (permissions?)"
251-
else
252-
echo " • would delete ${pkg} version ${id}"
253-
fi
254-
done
255-
done
280+
printf '%s\n' "$drop_ids" | xargs ./hack/retention-delete.sh "$pkg" version || sweep_failed=1
281+
done <<< "$pkgs"
282+
[ -z "$unlinked" ] || echo "::warning::not linked to ${GITHUB_REPOSITORY}, skipped: ${unlinked}"
283+
# One unlinked package is expected; all of them means nothing was swept.
284+
if [ -n "$pkgs" ] && [ "$linked_count" -eq 0 ]; then
285+
echo "::error::None of the resolved packages is linked to ${GITHUB_REPOSITORY}; the nightly sweep pruned nothing."
286+
sweep_failed=1
287+
fi
256288
257289
# Anything unresolved above means the protected set is incomplete, so
258290
# no candidate can be called abandoned. Skip this sweep entirely and
@@ -273,24 +305,24 @@ jobs:
273305
pkg='cozystack/cozystack-packages'
274306
enc="$(jq -rn --arg p "$pkg" '$p|@uri')"
275307
cutoff="$(date -u -d "${PROMOTION_TTL_DAYS} days ago" '+%Y-%m-%dT%H:%M:%SZ')"
276-
promotion_ids="$(gh api --paginate "/orgs/${ORG}/packages/container/${enc}/versions" \
308+
promotion_ids="$(gh api --paginate "/orgs/${ORG}/packages/container/${enc}/versions" 2>"$gh_err" \
277309
| jq -s --arg cutoff "$cutoff" --arg protected "$protected_promotion_digests" '[add[]
278310
| .name as $digest
279311
| select((.metadata.container.tags | length) > 0)
280312
| select(.metadata.container.tags | all(test("^promotion-v[0-9]+\\.[0-9]+\\.[0-9]+-from-v[0-9]+\\.[0-9]+\\.[0-9]+-rc\\.[0-9]+-run-[0-9]+-[0-9]+$")))
281313
| select(.updated_at < $cutoff)
282314
| select(($protected | split(" ") | index($digest)) == null)
283-
| .id]')"
315+
| .id]')" || {
316+
echo "::error::Could not read ${pkg} versions; promotion candidates not swept: $(cat "$gh_err")"
317+
sweep_failed=1
318+
promotion_ids='[]'
319+
}
284320
promotion_total="$(printf '%s' "$promotion_ids" | jq 'length')"
285321
echo "── ${pkg}: ${promotion_total} abandoned promotion candidate(s) older than ${PROMOTION_TTL_DAYS} days"
286-
printf '%s' "$promotion_ids" | jq -r '.[]' | while IFS= read -r id; do
287-
[ -n "$id" ] || continue
288-
if [ "$APPLY" = "true" ]; then
289-
echo " ✗ deleting ${pkg} promotion candidate version ${id}"
290-
gh api -X DELETE "/orgs/${ORG}/packages/container/${enc}/versions/${id}" >/dev/null \
291-
|| echo "::warning::failed to delete ${pkg} version ${id} (permissions?)"
292-
else
293-
echo " • would delete ${pkg} promotion candidate version ${id}"
294-
fi
295-
done
322+
printf '%s' "$promotion_ids" | jq -r '.[]' \
323+
| xargs ./hack/retention-delete.sh "$pkg" "promotion candidate version" || sweep_failed=1
324+
if [ "$sweep_failed" -eq 1 ]; then
325+
echo "::error::some versions could not be read or deleted; see the errors above."
326+
exit 1
327+
fi
296328
echo "Done."

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This file provides structured guidance for AI coding assistants and agents worki
2020

2121
- **Image references, tags and digests** (e.g., "why is this image tagged rc", "add an image to a package", "change how an image is stamped", "promotion/nightly missed an image", "where do image refs live")
2222
- Read: [`image-refs.md`](./docs/agents/image-refs.md)
23-
- Action: Read the entire file. Image refs live in three storage shapes and carry three classes of tag; a tool that knows only one shape skips images silently rather than failing. `hack/lib/image-refs.sh` is the enumeration shared by the promote, retag, mirror and candidate-verify tooling — extend it there rather than teaching an individual script a new path. The one exception is `hack/overlay-main-images.sh`, which reads refs but walks the tree itself, so a newly declared file does not reach it; `hack/promote-packages-artifact.sh` sources nothing because it publishes the whole tree and consumes no refs at all — the doc carries both cases
23+
- Action: Read the entire file. Image refs live in three storage shapes and carry three classes of tag; a tool that knows only one shape skips images silently rather than failing. `hack/lib/image-refs.sh` is the enumeration shared by the promote, retag, mirror, candidate-verify, multi-arch stitch and verify, and retention tooling — extend it there rather than teaching an individual script a new path. The one exception is `hack/overlay-main-images.sh`, which reads refs but walks the tree itself, so a newly declared file does not reach it; `hack/promote-packages-artifact.sh` sources nothing because it publishes the whole tree and consumes no refs at all — the doc carries both cases
2424

2525
- **Retiring an application** (e.g., "remove the X app", "deprecate X", "drop X from the catalog", "what happens to instances people already created")
2626
- Read: [`deprecating-applications.md`](./docs/deprecating-applications.md)

‎docs/agents/image-refs.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ If a package stamps a reference somewhere the two globs do not cover, add that f
5959
| `hack/nightly-mirror.sh` | all three shapes | copy each digest to the public registry, then rewrite hosts in the same file set |
6060
| `hack/overlay-main-images.sh` | `values.yaml` + `*.tag` | overlay current-main references onto packages a PR did not rebuild |
6161
| `hack/stitch-multiarch.sh` | all three shapes | join each amd64 image and its `-arm64` twin into one index, repin the tree on it, then fail if any old digest survives |
62+
| `hack/retention-packages.sh` | all three shapes, in the published nightly tree | name the GHCR packages the retention workflow prunes (those linked to another repository are then skipped); a ref it cannot see is a package whose nightlies are never pruned |
6263
| `hack/verify-multiarch.sh` | all three shapes, first- and third-party | fail a release unless every pinned image is an index with `linux/amd64` and `linux/arm64`, bar the repositories in `hack/multiarch-allowlist` |
6364

6465
`hack/multiarch-allowlist` is the only bypass for that gate, and it is for images that cannot exist for arm64, not for images nobody has rebuilt yet. An entry is a repository with no tag or digest, so bumping an allowed image needs no edit, and it must carry a reason after `#` on the same line: the script refuses an entry without one. Allowed refs are still printed, under their own "allowed single-arch" heading, and in tree mode an entry that matches no ref that failed the check is reported as stale without changing the exit status, so it gets removed once the image is multi-arch or gone. `--refs-file` reports no stale entries, since a list of pulled images omits everything the run did not pull.

‎docs/release.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ A nightly is an **installable copy of `main` on GHCR — not a rebuild and not a
335335
2. **build-disk** — assemble the Talos `nocloud` disk from the upstream siderolabs imager (the profile references only `ghcr.io/siderolabs/*`, so this needs no cozystack image and no rebuild) and publish it as a GHCR OCI artifact (`cozystack-nocloud`) so a nightly is installable on real hardware. The nightly e2e does not boot it.
336336
3. **e2e** — stage the published GHCR closure (rewritten tree + the `cozy-installer` chart pinned to the GHCR packages artifact) and run the **full** app suite on Talos containers, which do not boot the disk. A nightly has no diff, so Test Impact Analysis does not apply.
337337

338-
No GitHub release and no `api/apps/v1alpha1/*` Go-module tag are created for a nightly. [`retention.yaml`](../.github/workflows/retention.yaml) prunes old GHCR nightly versions (keeps the newest 14 per package; the floating `nightly`, release tags and untagged versions are never touched) and abandoned packages candidates. A candidate version is eligible only when all of these hold: it carries at least one tag and **every** tag on that manifest matches the strict `promotion-vX.Y.Z-from-vX.Y.Z-rc.N-run-<run-id>-<attempt>` form, so a manifest that has been retagged stable is excluded; it was last updated more than 30 days ago; its digest is not pinned by `packages/core/installer/values.yaml` at the head of an **open** pull request that is same-repository, authored by `cozystack-ci[bot]`, on a `release-X.Y.Z` branch and carrying the `release` label; and its digest is not pinned by that same file at the tip of `main` or of any `release-X.Y` branch — which is what keeps a candidate safe between the promote PR merging and finalize retagging it. Any such PR head whose pin is not exactly one `digest=sha256:<64 hex>` stops the candidate sweep for that run and fails the job, without affecting the nightly sweep.
338+
No GitHub release and no `api/apps/v1alpha1/*` Go-module tag are created for a nightly. [`retention.yaml`](../.github/workflows/retention.yaml) prunes old GHCR nightly versions (keeps the newest 14 per package the current nightly publishes, named from the published `cozystack-packages:nightly` tree by [`hack/retention-packages.sh`](../hack/retention-packages.sh), except packages linked to another repository, which are skipped with a warning; the floating `nightly`, release tags and untagged versions are never touched) and abandoned packages candidates. A candidate version is eligible only when all of these hold: it carries at least one tag and **every** tag on that manifest matches the strict `promotion-vX.Y.Z-from-vX.Y.Z-rc.N-run-<run-id>-<attempt>` form, so a manifest that has been retagged stable is excluded; it was last updated more than 30 days ago; its digest is not pinned by `packages/core/installer/values.yaml` at the head of an **open** pull request that is same-repository, authored by `cozystack-ci[bot]`, on a `release-X.Y.Z` branch and carrying the `release` label; and its digest is not pinned by that same file at the tip of `main` or of any `release-X.Y` branch — which is what keeps a candidate safe between the promote PR merging and finalize retagging it. Any such PR head whose pin is not exactly one `digest=sha256:<64 hex>` stops the candidate sweep for that run and fails the job, without affecting the nightly sweep.
339339

340340
Install a nightly:
341341

0 commit comments

Comments
 (0)