cleanup-stale-resources #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: cleanup-stale-resources | |
| permissions: | |
| actions: write | |
| contents: write | |
| on: | |
| schedule: | |
| - cron: "17 3 * * 0" | |
| workflow_dispatch: | |
| inputs: | |
| retention_days: | |
| description: "Delete workflow runs and artifacts older than this many days" | |
| required: false | |
| default: "30" | |
| type: string | |
| keep_releases: | |
| description: "Keep this many newest mcp-server releases" | |
| required: false | |
| default: "10" | |
| type: string | |
| release_tag_prefix: | |
| description: "Only cleanup releases with tags starting with this prefix" | |
| required: false | |
| default: "mcp-server-v" | |
| type: string | |
| dry_run: | |
| description: "List resources that would be deleted without deleting" | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| cleanup: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Cleanup stale workflow runs | |
| uses: actions/github-script@v7 | |
| env: | |
| RETENTION_DAYS: ${{ github.event.inputs.retention_days || '30' }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} | |
| with: | |
| script: | | |
| const retentionDays = Number(process.env.RETENTION_DAYS); | |
| const dryRun = process.env.DRY_RUN === 'true'; | |
| const msPerDay = 24 * 60 * 60 * 1000; | |
| const cutoff = Date.now() - retentionDays * msPerDay; | |
| const runs = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100, | |
| status: 'completed' | |
| }); | |
| const staleRuns = runs.filter(run => new Date(run.created_at).getTime() < cutoff); | |
| core.info(`Found ${staleRuns.length} stale workflow runs older than ${retentionDays} days.`); | |
| for (const run of staleRuns) { | |
| core.info(`${dryRun ? '[dry-run] ' : ''}Deleting workflow run #${run.id} (${run.name}) created ${run.created_at}`); | |
| if (!dryRun) { | |
| await github.rest.actions.deleteWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: run.id | |
| }); | |
| } | |
| } | |
| - name: Cleanup stale artifacts | |
| uses: actions/github-script@v7 | |
| env: | |
| RETENTION_DAYS: ${{ github.event.inputs.retention_days || '30' }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} | |
| with: | |
| script: | | |
| const retentionDays = Number(process.env.RETENTION_DAYS); | |
| const dryRun = process.env.DRY_RUN === 'true'; | |
| const msPerDay = 24 * 60 * 60 * 1000; | |
| const cutoff = Date.now() - retentionDays * msPerDay; | |
| const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100 | |
| }); | |
| const staleArtifacts = artifacts.filter(artifact => new Date(artifact.created_at).getTime() < cutoff); | |
| core.info(`Found ${staleArtifacts.length} stale artifacts older than ${retentionDays} days.`); | |
| for (const artifact of staleArtifacts) { | |
| core.info(`${dryRun ? '[dry-run] ' : ''}Deleting artifact #${artifact.id} (${artifact.name}) created ${artifact.created_at}`); | |
| if (!dryRun) { | |
| await github.rest.actions.deleteArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: artifact.id | |
| }); | |
| } | |
| } | |
| - name: Cleanup stale releases | |
| uses: actions/github-script@v7 | |
| env: | |
| KEEP_RELEASES: ${{ github.event.inputs.keep_releases || '10' }} | |
| RELEASE_TAG_PREFIX: ${{ github.event.inputs.release_tag_prefix || 'mcp-server-v' }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} | |
| with: | |
| script: | | |
| const keepReleases = Number(process.env.KEEP_RELEASES); | |
| const prefix = process.env.RELEASE_TAG_PREFIX; | |
| const dryRun = process.env.DRY_RUN === 'true'; | |
| const releases = await github.paginate(github.rest.repos.listReleases, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100 | |
| }); | |
| const matchingReleases = releases | |
| .filter(release => release.tag_name?.startsWith(prefix)) | |
| .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); | |
| const staleReleases = matchingReleases.slice(keepReleases); | |
| core.info(`Found ${staleReleases.length} stale releases for prefix '${prefix}' after keeping ${keepReleases}.`); | |
| for (const release of staleReleases) { | |
| core.info(`${dryRun ? '[dry-run] ' : ''}Deleting release #${release.id} (${release.tag_name})`); | |
| if (!dryRun) { | |
| await github.rest.repos.deleteRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.id | |
| }); | |
| try { | |
| await github.rest.git.deleteRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: `tags/${release.tag_name}` | |
| }); | |
| } catch (error) { | |
| core.warning(`Could not delete tag '${release.tag_name}': ${error.message}`); | |
| } | |
| } | |
| } |