Skip to content

Commit 83996f2

Browse files
authored
fix: prune old docker images (#2813)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. Ex: Closes #<issue number> --> prune old docker images
1 parent 98124bb commit 83996f2

2 files changed

Lines changed: 124 additions & 1 deletion

File tree

.github/workflows/ghcr-prune.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Cleanup workflow for pruning old commit-hash Docker tags from GHCR.
2+
name: GHCR Tag Prune
3+
on:
4+
schedule:
5+
- cron: "0 6 * * *" # daily at 06:00 UTC
6+
workflow_dispatch:
7+
inputs:
8+
retention-days:
9+
description: "Override retention window (days)"
10+
required: false
11+
type: number
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
env:
18+
DEFAULT_RETENTION_DAYS: 14
19+
20+
jobs:
21+
prune:
22+
name: Remove aged commit-hash tags
23+
runs-on: ubuntu-latest
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
package:
28+
- ev-node
29+
- ev-node-evm-single
30+
- local-da
31+
steps:
32+
- name: Delete stale tags
33+
uses: actions/github-script@v7
34+
env:
35+
PACKAGE_NAME: ${{ matrix.package }}
36+
OVERRIDE_RETENTION: ${{ github.event.inputs.retention-days }}
37+
with:
38+
script: |
39+
const packageName = process.env.PACKAGE_NAME;
40+
if (!packageName) {
41+
core.setFailed('PACKAGE_NAME env not provided');
42+
return;
43+
}
44+
45+
const retentionDaysInput = process.env.OVERRIDE_RETENTION;
46+
const retentionDays = retentionDaysInput ? Number(retentionDaysInput) : Number(process.env.DEFAULT_RETENTION_DAYS);
47+
if (Number.isNaN(retentionDays) || retentionDays <= 0) {
48+
core.setFailed(`Invalid retention window: ${retentionDaysInput}`);
49+
return;
50+
}
51+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
52+
const owner = context.repo.owner;
53+
const ownerType = context.payload.repository?.owner?.type === 'User' ? 'User' : 'Organization';
54+
55+
core.info(`Processing ${packageName} for ${ownerType.toLowerCase()} ${owner}; removing commit-hash tags older than ${retentionDays} days`);
56+
57+
const listParams = {
58+
package_type: 'container',
59+
package_name: packageName,
60+
per_page: 100,
61+
};
62+
if (ownerType === 'Organization') {
63+
listParams.org = owner;
64+
} else {
65+
listParams.username = owner;
66+
}
67+
68+
const listFn = ownerType === 'Organization'
69+
? github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg
70+
: github.rest.packages.getAllPackageVersionsForPackageOwnedByUser;
71+
72+
const deleteFn = ownerType === 'Organization'
73+
? github.rest.packages.deletePackageVersionForOrg
74+
: github.rest.packages.deletePackageVersionForUser;
75+
76+
const versions = await github.paginate(listFn, listParams);
77+
core.info(`Found ${versions.length} versions`);
78+
79+
const hashRegex = /^(?:sha256:)?[0-9a-f]{7,64}$/i;
80+
const prRegex = /^pr-\d+$/i;
81+
const maxDeletes = 100;
82+
let deleted = 0;
83+
for (const version of versions) {
84+
if (deleted >= maxDeletes) {
85+
core.info(`Hit per-run deletion cap (${maxDeletes}); stopping early for ${packageName}`);
86+
break;
87+
}
88+
const created = new Date(version.created_at).getTime();
89+
if (Number.isNaN(created) || created >= cutoff) {
90+
continue;
91+
}
92+
93+
const tags = version.metadata?.container?.tags ?? [];
94+
const digest = version.metadata?.container?.digest;
95+
const identifiers = [...tags];
96+
if (digest) {
97+
identifiers.push(digest);
98+
}
99+
if (version.name) {
100+
identifiers.push(version.name);
101+
}
102+
103+
const hasReleaseTag = tags.some(tag => tag.startsWith('v'));
104+
if (hasReleaseTag) {
105+
continue;
106+
}
107+
108+
const hasCommitTag = identifiers.some(id => hashRegex.test(id));
109+
const hasPrTag = tags.some(tag => prRegex.test(tag));
110+
if (!hasCommitTag && !hasPrTag) {
111+
continue;
112+
}
113+
114+
core.info(`Deleting version ${version.id} (${tags.join(', ')}) created ${version.created_at}`);
115+
await deleteFn({
116+
package_type: 'container',
117+
package_name: packageName,
118+
package_version_id: version.id,
119+
...(ownerType === 'Organization' ? { org: owner } : { username: owner }),
120+
});
121+
deleted += 1;
122+
}
123+
124+
core.info(`Deleted ${deleted} old commit-hash tags for ${packageName}`);

.github/workflows/test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ jobs:
130130
EVM_SINGLE_IMAGE_REPO: ghcr.io/${{ github.repository_owner }}/ev-node-evm-single
131131
EVM_SINGLE_NODE_IMAGE_TAG: ${{ inputs.image-tag }}
132132

133-
134133
build_all-apps:
135134
name: Build All ev-node Binaries
136135
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)