Cache Maintenance #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: Cache Maintenance | |
| on: | |
| schedule: | |
| - cron: '30 3 * * 0' | |
| workflow_dispatch: | |
| inputs: | |
| keep_per_key: | |
| description: How many recent caches to keep for each normalized key | |
| type: choice | |
| default: '2' | |
| options: | |
| - '1' | |
| - '2' | |
| permissions: | |
| actions: write | |
| contents: read | |
| jobs: | |
| prune-caches: | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| KEEP_PER_KEY: ${{ inputs.keep_per_key || '2' }} | |
| REPO: ${{ github.repository }} | |
| steps: | |
| - name: List caches | |
| run: gh cache list --repo "$REPO" --limit 1000 --json createdAt,id,key,lastAccessedAt,ref,sizeInBytes > caches.json | |
| - name: Select stale caches | |
| shell: bash | |
| run: | | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const keepCount = Number(process.env.KEEP_PER_KEY || '2'); | |
| const caches = JSON.parse(fs.readFileSync('caches.json', 'utf8')); | |
| const groups = new Map(); | |
| const normalizeKey = (key) => key.replace(/-[0-9a-f]{32,}$/i, ''); | |
| const score = (cache) => new Date(cache.lastAccessedAt || cache.createdAt || 0).getTime(); | |
| for (const cache of caches) { | |
| const groupKey = normalizeKey(cache.key); | |
| if (!groups.has(groupKey)) { | |
| groups.set(groupKey, []); | |
| } | |
| groups.get(groupKey).push(cache); | |
| } | |
| const stale = []; | |
| const summary = []; | |
| for (const [groupKey, items] of groups.entries()) { | |
| items.sort((a, b) => score(b) - score(a)); | |
| const keep = items.slice(0, keepCount); | |
| const remove = items.slice(keepCount); | |
| summary.push( | |
| `${groupKey}: keep ${keep.length}, delete ${remove.length}, total ${items.length}`, | |
| ); | |
| stale.push(...remove); | |
| } | |
| fs.writeFileSync('stale-cache-ids.txt', stale.map((cache) => String(cache.id)).join('\n')); | |
| fs.writeFileSync( | |
| process.env.GITHUB_STEP_SUMMARY, | |
| [ | |
| '# Cache Maintenance', | |
| '', | |
| `Keep per normalized key: ${keepCount}`, | |
| 'Normalized key rule: strip trailing hex hash like `-f27f...`', | |
| '', | |
| `Total caches scanned: ${caches.length}`, | |
| `Caches selected for deletion: ${stale.length}`, | |
| '', | |
| '## Groups', | |
| ...summary.map((line) => `- ${line}`), | |
| ].join('\n'), | |
| ); | |
| console.log(`Caches scanned: ${caches.length}`); | |
| console.log(`Caches selected for deletion: ${stale.length}`); | |
| NODE | |
| - name: Delete stale caches | |
| shell: bash | |
| run: | | |
| if [ ! -s stale-cache-ids.txt ]; then | |
| echo "No stale caches to delete" | |
| exit 0 | |
| fi | |
| while IFS= read -r cache_id; do | |
| if [ -n "$cache_id" ]; then | |
| echo "Deleting cache $cache_id" | |
| gh cache delete "$cache_id" --repo "$REPO" | |
| fi | |
| done < stale-cache-ids.txt |