Files
ResInsight/.github/workflows/cleanup-rhel8-image.yml
T
Magne Sjaastad 4b4bf29dfc Store RHEL8 image layer cache in GHCR instead of the Actions cache
The nightly image build has failed since 2026-07-23 with "failed to
solve: not_found" while exporting the BuildKit layer cache, because the
repository's GitHub Actions cache is at the 10 GB limit and entries are
evicted as fast as they are written (0% cache hit rate on restore).

Use a registry cache (ci-rhel8:buildcache in GHCR) instead, which has no
size cap, and exclude the buildcache tag from the daily image cleanup.

Make the cache effective across nightly rebuilds: key the vcpkg stage on
its actual inputs (vcpkg.json, the vcpkg configuration and the vcpkg
tool) with a direct manifest install instead of copying the entire build
context, and exclude .git from the Docker build context since its
internals differ between checkouts of the same commit and invalidated
'COPY . /src' on every run. Nightly builds now reuse the compiled vcpkg
ports from the layer cache and only recompile the ResInsight warmup
stage.
2026-07-27 15:38:46 +02:00

132 lines
4.8 KiB
YAML

name: Cleanup RHEL8 CI Image Versions
# Daily cleanup of old dated tags pushed by build-rhel8-image.yml.
#
# Retention rules:
# * Versions tagged "latest" are always kept (the consumer workflow pulls
# `:latest`, so deleting it would break CI). Versions tagged "buildcache"
# are always kept too (BuildKit layer cache used by the image build).
# * The most recent MIN_KEEP versions by updated_at are kept regardless of
# age. This is a safety floor: if the last few nightly builds break, we
# can still roll back to a working older image.
# * Of what remains, anything older than RETENTION_DAYS is deleted.
#
# Owner type (User vs Organization) is auto-detected so the same workflow
# runs unchanged on the upstream (OPM/ResInsight, org) and on forks
# (e.g. magnesj/ResInsight, user).
on:
schedule:
# 03:00 UTC -- after the nightly image build (00:00) and the unit-test
# consumer (02:00) have both finished, so we never race them.
- cron: "0 3 * * *"
workflow_dispatch:
inputs:
dry_run:
description: "List versions that would be deleted without deleting"
type: boolean
default: false
retention_days:
description: "Delete versions older than this many days"
type: string
default: "5"
concurrency:
group: cleanup-rhel8-image
cancel-in-progress: false
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Delete old image versions
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ inputs.dry_run == true && 'true' || 'false' }}
RETENTION_DAYS: ${{ inputs.retention_days || '5' }}
MIN_KEEP: "3"
run: |
set -euo pipefail
owner="${GITHUB_REPOSITORY_OWNER}"
repo_name_lc="${GITHUB_REPOSITORY##*/}"
repo_name_lc="${repo_name_lc,,}"
pkg="${repo_name_lc}/ci-rhel8"
pkg_enc="${pkg/\//%2F}"
# Owner type drives both the list endpoint and the delete endpoint.
# User-owned packages delete via /user/... (authenticated user), org
# packages via /orgs/<org>/...
owner_type=$(gh api "/users/$owner" --jq '.type')
case "$owner_type" in
Organization)
list_path="/orgs/$owner/packages/container/$pkg_enc/versions"
del_prefix="/orgs/$owner/packages/container/$pkg_enc/versions"
;;
User)
list_path="/users/$owner/packages/container/$pkg_enc/versions"
del_prefix="/user/packages/container/$pkg_enc/versions"
;;
*)
echo "Unknown owner type: $owner_type" >&2
exit 1
;;
esac
echo "Owner : $owner ($owner_type)"
echo "Package : $pkg"
echo "Retention days : $RETENTION_DAYS"
echo "Safety floor : keep $MIN_KEEP most recent versions"
echo "Dry run : $DRY_RUN"
# Tolerate 404: the package legitimately does not exist on a fork
# that has never published an image yet.
if ! versions=$(gh api --paginate "$list_path" 2>/tmp/api.err); then
if grep -qiE 'not found|HTTP 404' /tmp/api.err; then
echo "Package $pkg not found on $owner -- nothing to clean up."
exit 0
fi
cat /tmp/api.err >&2
exit 1
fi
cutoff_epoch=$(date -u -d "$RETENTION_DAYS days ago" +%s)
echo "Cutoff (UTC) : $(date -u -d @"$cutoff_epoch" -Iseconds)"
# jq pipeline:
# 1. drop versions whose tags include "latest" or "buildcache"
# 2. sort newest-first
# 3. skip the first MIN_KEEP (safety floor)
# 4. keep only those older than the cutoff
to_delete=$(jq -c \
--argjson keep "$MIN_KEEP" \
--argjson cutoff "$cutoff_epoch" '
[ .[] | select((.metadata.container.tags // []) | (index("latest") or index("buildcache")) | not) ]
| sort_by(.updated_at) | reverse
| .[$keep:]
| [ .[] | select((.updated_at | fromdateiso8601) < $cutoff) ]
' <<<"$versions")
count=$(jq 'length' <<<"$to_delete")
echo "Versions to delete: $count"
jq -r '.[] | " - id=\(.id) updated=\(.updated_at) tags=\((.metadata.container.tags // []) | join(","))"' <<<"$to_delete"
if [[ "$count" -eq 0 ]]; then
echo "Nothing to do."
exit 0
fi
if [[ "$DRY_RUN" == "true" ]]; then
echo "Dry run -- not deleting."
exit 0
fi
jq -r '.[].id' <<<"$to_delete" | while read -r id; do
echo "DELETE $del_prefix/$id"
gh api -X DELETE "$del_prefix/$id"
done
echo "Cleanup complete."