Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0705f35

Browse files
authored
Merge pull request #1621 from pdettori/worktree-cleanup-container-images
ci: add scheduled cleanup for SHA-tagged container images
2 parents 952031b + 61010b1 commit 0705f35

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
name: Cleanup Container Images
2+
3+
on:
4+
# Daily cleanup at 03:17 UTC (off-peak, avoids :00/:30 contention)
5+
schedule:
6+
- cron: '17 3 * * *'
7+
8+
# Manual trigger with configurable options
9+
workflow_dispatch:
10+
inputs:
11+
dry_run:
12+
description: 'Dry run (show what would be deleted without actually deleting)'
13+
required: false
14+
type: boolean
15+
default: false
16+
retention_days:
17+
description: 'Delete SHA-tagged images older than this many days'
18+
required: false
19+
type: number
20+
default: 7
21+
22+
permissions: {}
23+
24+
jobs:
25+
cleanup:
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: read
29+
packages: write
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
package:
34+
- ui-v2
35+
- backend
36+
- ui-oauth-secret
37+
- agent-oauth-secret
38+
- api-oauth-secret
39+
- mlflow-oauth-secret
40+
- spiffe-idp-setup
41+
42+
steps:
43+
- name: Delete stale SHA-tagged images for ${{ matrix.package }}
44+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
45+
with:
46+
script: |
47+
const package_name = `kagenti/${{ matrix.package }}`;
48+
const retentionDays = ${{ inputs.retention_days || 7 }};
49+
const dryRun = ${{ inputs.dry_run || false }};
50+
const cutoffDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
51+
52+
// Pattern: any branch prefix followed by a 7-12 char hex SHA
53+
const shaTagPattern = /^.+-[0-9a-f]{7,12}$/;
54+
// Tags to never delete
55+
const protectedTagPattern = /^(v\d|latest$)/;
56+
57+
console.log(`Package: ${package_name}`);
58+
console.log(`Retention: ${retentionDays} days (cutoff: ${cutoffDate.toISOString()})`);
59+
console.log(`Dry run: ${dryRun}`);
60+
console.log('---');
61+
62+
// Phase 1: collect all version IDs to delete (avoids pagination drift)
63+
const toDelete = [];
64+
let skipped = 0;
65+
let page = 1;
66+
const perPage = 100;
67+
68+
while (true) {
69+
const versions = await github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({
70+
package_type: 'container',
71+
package_name: package_name,
72+
org: 'kagenti',
73+
page: page,
74+
per_page: perPage,
75+
});
76+
77+
if (versions.data.length === 0) break;
78+
79+
for (const version of versions.data) {
80+
const tags = version.metadata?.container?.tags || [];
81+
const createdAt = new Date(version.created_at);
82+
83+
if (tags.length === 0) {
84+
if (createdAt < cutoffDate) {
85+
toDelete.push({ id: version.id, label: `untagged ${version.id}`, createdAt });
86+
}
87+
continue;
88+
}
89+
90+
if (tags.some(tag => protectedTagPattern.test(tag))) {
91+
skipped++;
92+
continue;
93+
}
94+
95+
if (!tags.every(tag => shaTagPattern.test(tag))) {
96+
skipped++;
97+
continue;
98+
}
99+
100+
if (createdAt >= cutoffDate) {
101+
skipped++;
102+
continue;
103+
}
104+
105+
toDelete.push({ id: version.id, label: tags.join(', '), createdAt });
106+
}
107+
108+
if (versions.data.length < perPage) break;
109+
page++;
110+
}
111+
112+
// Phase 2: delete collected versions
113+
for (const item of toDelete) {
114+
if (dryRun) {
115+
console.log(`[DRY RUN] Would delete ${item.label} (${item.createdAt.toISOString()})`);
116+
} else {
117+
await github.rest.packages.deletePackageVersionForOrg({
118+
package_type: 'container',
119+
package_name: package_name,
120+
org: 'kagenti',
121+
package_version_id: item.id,
122+
});
123+
console.log(`Deleted ${item.label} (${item.createdAt.toISOString()})`);
124+
}
125+
}
126+
127+
console.log('---');
128+
console.log(`Summary: ${toDelete.length} deleted, ${skipped} preserved`);

0 commit comments

Comments
 (0)