forked from CyberDrain/CIPP
-
Notifications
You must be signed in to change notification settings - Fork 0
146 lines (133 loc) · 6.25 KB
/
Copy pathpreview-cleanup.yml
File metadata and controls
146 lines (133 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
name: "Cleanup: Preview Containers"
# Deletes the throwaway container versions built by preview-container.yml (tags prefixed with a
# branch type: preview-, fix-, feat-, chore- ...):
# - when the branch that produced them is deleted
# - weekly, for anything older than RETENTION_DAYS (covers one-off workflow_dispatch builds
# from branches that were never deleted)
#
# NOTE: `on: delete` only fires for workflow files present on the DEFAULT branch, so this has
# to reach main before branch-deletion cleanup starts working. The scheduled sweep is the
# backstop either way.
on:
delete:
schedule:
- cron: '0 4 * * 0' # Sundays 04:00 UTC
workflow_dispatch:
inputs:
dry_run:
description: List what would be deleted without deleting it
type: boolean
default: true
env:
PACKAGE_NAME: cipp
RETENTION_DAYS: 30
# Recognises a throwaway build image by its branch-type prefix. Must match the type list in
# preview-container.yml and $PreviewChannelPattern in Invoke-ExecContainerManagement.ps1.
# Nothing in this set can match latest / dev / nightly / a bare semver, which is what makes
# the sweep safe to run unattended.
BUILD_TAG_PATTERN: '^(preview|feat|fix|refactor|perf|chore|build|revert)-'
jobs:
cleanup:
# Any branch delete is worth a look now that fix/**, feat/** etc. also produce images via
# workflow_dispatch - a branch that never had one simply matches nothing. Tag deletions are
# skipped: they never produce build images.
if: ${{ github.event_name != 'delete' || github.event.ref_type == 'branch' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Delete preview image versions
env:
# GITHUB_TOKEN can delete versions of a package that inherits access from this repo.
# If ghcr.io/cyberdrain/cipp is org-owned without that link, set a GHCR_CLEANUP_TOKEN
# repo secret (PAT with delete:packages) and it will be preferred automatically.
GH_TOKEN: ${{ secrets.GHCR_CLEANUP_TOKEN || secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
DELETED_REF: ${{ github.event.ref }}
DRY_RUN: ${{ github.event.inputs.dry_run }}
OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail
# Dry run defaults to false for automatic triggers, true for manual dispatch.
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$DRY_RUN" != "false" ]; then
DRY=1
else
DRY=0
fi
if [ "$EVENT_NAME" = "delete" ]; then
# Must reproduce the slug rule in preview-container.yml exactly, including the
# branch-type prefix list - a mismatch here silently orphans images.
RAW=$(echo "$DELETED_REF" | tr '[:upper:]' '[:lower:]')
RAW="${RAW#refs/heads/}"
TYPE="${RAW%%/*}"
case "$TYPE" in
preview|feat|fix|refactor|perf|chore|build|revert)
if [ "$TYPE" = "$RAW" ]; then PREFIX="preview"; NAME="$RAW"; else PREFIX="$TYPE"; NAME="${RAW#*/}"; fi
;;
*)
PREFIX="preview"; NAME="$RAW"
;;
esac
NAME=$(echo "$NAME" \
| sed -E 's/[^a-z0-9._-]+/-/g; s/-{2,}/-/g; s/^[-.]+//; s/[-.]+$//' \
| cut -c1-45)
NAME="${NAME%-}"
if [ -z "$NAME" ]; then
echo "Could not derive a slug from '$DELETED_REF' - nothing to do."
exit 0
fi
SLUG="${PREFIX}-${NAME}"
echo "Branch '$DELETED_REF' deleted - removing $SLUG and its pinned builds."
# Exact moving tag, or that tag plus a -<shortsha> suffix.
MATCH="^${SLUG}(-[0-9a-f]{7})?$"
CUTOFF=""
else
echo "Sweeping build images older than ${RETENTION_DAYS} days."
MATCH="$BUILD_TAG_PATTERN"
CUTOFF=$(date -u -d "${RETENTION_DAYS} days ago" +%s)
fi
# Org-owned package. Falls back to the user endpoint if the org one 404s.
LIST_PATH="/orgs/${OWNER}/packages/container/${PACKAGE_NAME}/versions"
if ! gh api "$LIST_PATH?per_page=1" >/dev/null 2>&1; then
LIST_PATH="/users/${OWNER}/packages/container/${PACKAGE_NAME}/versions"
echo "Org package endpoint unavailable, using $LIST_PATH"
fi
gh api --paginate "$LIST_PATH" > versions.json
# Safety: a version can carry several tags. Delete only when EVERY tag on it matches
# both the requested match AND the build-tag pattern, so a version that also carries
# latest / dev / nightly / a bare semver can never be caught here even if $MATCH were
# wrong. Untagged versions are left alone (GHCR prunes those itself).
jq -r --arg match "$MATCH" --arg guard "$BUILD_TAG_PATTERN" --arg cutoff "${CUTOFF:-0}" '
.[]
| . as $v
| ($v.metadata.container.tags // []) as $tags
| select(($tags | length) > 0)
| select($tags | all(test($match)))
| select($tags | all(test($guard)))
| select(($cutoff | tonumber) == 0 or (($v.created_at | fromdateiso8601) < ($cutoff | tonumber)))
| "\($v.id)\t\($tags | join(","))"
' versions.json > targets.tsv
if [ ! -s targets.tsv ]; then
echo "Nothing to delete."
echo "Nothing to delete." >> $GITHUB_STEP_SUMMARY
exit 0
fi
{
echo "### Preview images ${DRY:+(dry run) }removed"
echo
echo '| Version | Tags |'
echo '|---|---|'
} >> $GITHUB_STEP_SUMMARY
while IFS=$'\t' read -r ID TAGS; do
echo "| \`$ID\` | $TAGS |" >> $GITHUB_STEP_SUMMARY
if [ "$DRY" = "1" ]; then
echo "[dry run] would delete $ID ($TAGS)"
continue
fi
echo "Deleting $ID ($TAGS)"
# Non-fatal: a version can vanish between listing and delete, and one failure
# should not abandon the rest of the sweep.
gh api -X DELETE "${LIST_PATH}/${ID}" \
|| echo "::warning::Could not delete package version $ID ($TAGS)"
done < targets.tsv