From 5a59f054a2c613cd5a721472f79abee048276212 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Wed, 28 Jan 2026 22:53:17 +0000 Subject: [PATCH] Add workflow cleanup pruning --- .github/workflows/docker.yml | 147 +++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c31d784..0206916 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -111,3 +111,150 @@ jobs: run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) + + prune-releases: + name: Prune old releases + runs-on: ubuntu-latest + needs: merge + permissions: + contents: write + steps: + - name: Prune releases and tags + uses: actions/github-script@v7 + with: + script: | + const keep = 5; + const { owner, repo } = context.repo; + const releases = await github.paginate(github.rest.repos.listReleases, { + owner, + repo, + per_page: 100, + }); + core.info(`Found ${releases.length} releases; keeping latest ${keep}.`); + const toDelete = releases.slice(keep); + for (const release of toDelete) { + core.info(`Deleting release ${release.tag_name} (id ${release.id})`); + await github.rest.repos.deleteRelease({ + owner, + repo, + release_id: release.id, + }); + if (!release.tag_name) { + core.info(`Release ${release.id} has no tag.`); + continue; + } + const refs = await github.rest.git.listMatchingRefs({ + owner, + repo, + ref: `tags/${release.tag_name}`, + }); + if (refs.data.length === 0) { + core.info(`Tag ${release.tag_name} not found; skipping.`); + continue; + } + await github.rest.git.deleteRef({ + owner, + repo, + ref: `tags/${release.tag_name}`, + }); + } + + cleanup: + name: Cleanup old runs, tags, and artifacts + runs-on: ubuntu-latest + needs: merge + permissions: + actions: write + contents: write + steps: + - name: Cleanup old workflow runs, tags, and artifacts + uses: actions/github-script@v7 + with: + script: | + const keepCount = 5; + const { owner, repo } = context.repo; + + core.info('Fetching workflow runs...'); + const runs = await github.paginate( + github.rest.actions.listWorkflowRunsForRepo, + { + owner, + repo, + per_page: 100, + } + ); + + const sortedRuns = runs.sort((a, b) => + new Date(b.created_at) - new Date(a.created_at) + ); + const runsToDelete = sortedRuns.slice(keepCount); + + core.info(`Found ${runs.length} workflow runs, keeping ${keepCount} newest, deleting ${runsToDelete.length} older...`); + for (const run of runsToDelete) { + core.info(`Deleting run #${run.run_number} from ${run.created_at}`); + try { + await github.rest.actions.deleteWorkflowRun({ + owner, + repo, + run_id: run.id, + }); + } catch (error) { + core.warning(`Failed to delete run ${run.id}: ${error.message}`); + } + } + + core.info('Fetching tags...'); + const tags = await github.paginate( + github.rest.repos.listTags, + { + owner, + repo, + per_page: 100, + } + ); + + const currentTag = context.ref.replace('refs/tags/', ''); + const otherTags = tags.filter(tag => tag.name !== currentTag); + const tagsToDelete = otherTags.slice(keepCount); + + core.info(`Found ${tags.length} tags, keeping ${keepCount} newest (including current: ${currentTag}), deleting ${tagsToDelete.length} older...`); + for (const tag of tagsToDelete) { + core.info(`Deleting tag ${tag.name}`); + try { + await github.rest.git.deleteRef({ + owner, + repo, + ref: `tags/${tag.name}`, + }); + } catch (error) { + core.warning(`Failed to delete tag ${tag.name}: ${error.message}`); + } + } + + core.info('Fetching artifacts...'); + const artifacts = await github.paginate( + github.rest.actions.listArtifactsForRepo, + { + owner, + repo, + per_page: 100, + } + ); + const sortedArtifacts = artifacts + .filter(artifact => !artifact.expired) + .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + const artifactsToDelete = sortedArtifacts.slice(keepCount); + + core.info(`Found ${artifacts.length} artifacts, keeping ${keepCount} newest, deleting ${artifactsToDelete.length} older...`); + for (const artifact of artifactsToDelete) { + core.info(`Deleting artifact ${artifact.name} (id ${artifact.id})`); + try { + await github.rest.actions.deleteArtifact({ + owner, + repo, + artifact_id: artifact.id, + }); + } catch (error) { + core.warning(`Failed to delete artifact ${artifact.id}: ${error.message}`); + } + }