mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 10:37:21 +09:00
The zizmor "Audit workflows" gate (added in #3223) is red on main: five actions/* references in the release workflows from #3017 are pinned to a floating tag instead of a commit SHA (blanket unpinned-uses policy, High severity): - nightly.yaml (x3) actions/checkout@v4 - promote-rc.yaml actions/checkout@v4 - retention.yaml actions/create-github-app-token@v1 Pin each to the SHA the rest of the repo already standardizes on: - actions/checkout -> 34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - actions/create-github-app-token -> d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 `zizmor --offline .github/workflows/` now reports no findings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
111 lines
4.6 KiB
YAML
111 lines
4.6 KiB
YAML
name: Nightly Retention
|
|
|
|
# Prune old nightly artifacts from GHCR: keep the newest KEEP dated versions of
|
|
# each cozystack package, delete older ones. Nightlies are GHCR OCI tags
|
|
# (0.0.0-nightly.<date>.<run-id>.<attempt> on every component image plus
|
|
# cozystack-packages, cozy-installer and cozystack-nocloud) — not git tags or
|
|
# releases — so this prunes container-package versions via the GitHub Packages
|
|
# API.
|
|
#
|
|
# Strictly scoped: only versions whose tags are ALL of the form
|
|
# 0.0.0-nightly.<YYYYMMDD>.<run-id>.<attempt> are eligible. A version that also
|
|
# carries the floating `nightly` tag (the newest) or any release tag (vX.Y.Z,
|
|
# latest, rc, main) is never touched, so stable/rc/main artifacts can never be
|
|
# pruned.
|
|
#
|
|
# Dry-run by default; flip the input or the RETENTION_APPLY repo variable to
|
|
# actually delete. Deletion is irreversible.
|
|
#
|
|
# NOTE (admin): deleting org container-package versions requires the
|
|
# cozystack-ci app installation to carry the `packages: write` permission. If
|
|
# the delete step 403s, grant it there — this workflow cannot.
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '30 4 * * *' # daily 04:30 UTC, after the nightly has published
|
|
workflow_dispatch:
|
|
inputs:
|
|
apply:
|
|
description: "Actually delete (false = dry-run, the default)"
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
ORG: cozystack
|
|
KEEP: '14'
|
|
|
|
concurrency:
|
|
group: nightly-retention
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
prune:
|
|
name: Prune old GHCR nightlies
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
steps:
|
|
- name: Generate GitHub App token
|
|
id: app-token
|
|
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
|
|
with:
|
|
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
|
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
|
owner: cozystack
|
|
|
|
- name: Prune
|
|
env:
|
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
# workflow_dispatch input wins; otherwise the repo variable; else dry-run.
|
|
APPLY: ${{ github.event.inputs.apply || vars.RETENTION_APPLY || 'false' }}
|
|
run: |
|
|
set -eu
|
|
if [ "$APPLY" = "true" ]; then echo "MODE: APPLY (deleting)"; else echo "MODE: dry-run"; fi
|
|
|
|
# All container packages under the org that live in the cozystack/
|
|
# registry namespace (ghcr.io/cozystack/cozystack/<name>).
|
|
pkgs="$(gh api --paginate "/orgs/${ORG}/packages?package_type=container" \
|
|
--jq '.[].name' | grep '^cozystack/' || true)"
|
|
[ -n "$pkgs" ] || { echo "No cozystack/* container packages found."; exit 0; }
|
|
|
|
printf '%s\n' "$pkgs" | while IFS= read -r pkg; do
|
|
[ -n "$pkg" ] || continue
|
|
enc="$(jq -rn --arg p "$pkg" '$p|@uri')"
|
|
|
|
# Version ids whose tags are non-empty AND every tag is a dated
|
|
# nightly (so the floating `nightly` tag and any release tag exclude
|
|
# the version). API returns versions newest-first, so the dated
|
|
# nightlies are already in newest->oldest order.
|
|
# `gh api --paginate --jq` runs the filter per page and emits one
|
|
# array per page, so length/slicing break once a package spans
|
|
# multiple pages. Slurp the raw pages and merge with `jq -s add`
|
|
# first, then count and slice the combined array.
|
|
ids="$(gh api --paginate "/orgs/${ORG}/packages/container/${enc}/versions" \
|
|
| jq -s '[add[]
|
|
| select((.metadata.container.tags | length) > 0)
|
|
| select(.metadata.container.tags | all(test("^0\\.0\\.0-nightly\\.[0-9]{8}(\\.[0-9]+)*$")))
|
|
| .id]')"
|
|
|
|
total="$(printf '%s' "$ids" | jq 'length')"
|
|
[ "$total" -gt "$KEEP" ] || { echo "── ${pkg}: ${total} nightlies (<= ${KEEP}); nothing to prune"; continue; }
|
|
drop_ids="$(printf '%s' "$ids" | jq -r ".[${KEEP}:][]")"
|
|
echo "── ${pkg}: ${total} nightlies, keep ${KEEP}, drop $((total - KEEP))"
|
|
|
|
printf '%s\n' "$drop_ids" | while IFS= read -r id; do
|
|
[ -n "$id" ] || continue
|
|
if [ "$APPLY" = "true" ]; then
|
|
echo " ✗ deleting ${pkg} version ${id}"
|
|
gh api -X DELETE "/orgs/${ORG}/packages/container/${enc}/versions/${id}" >/dev/null \
|
|
|| echo "::warning::failed to delete ${pkg} version ${id} (permissions?)"
|
|
else
|
|
echo " • would delete ${pkg} version ${id}"
|
|
fi
|
|
done
|
|
done
|
|
echo "Done."
|