name: Cleanup Old Nightly Builds on: schedule: # Run weekly on Sundays at 3 AM UTC - cron: '0 3 * * 0' workflow_dispatch: inputs: retention_days: description: 'Number of days to retain nightly builds' required: false default: '7' dry_run: description: 'Dry run (show what would be deleted without deleting)' required: false type: boolean default: false jobs: cleanup: runs-on: ubuntu-latest permissions: packages: write steps: - name: Cleanup old nightly builds uses: actions/github-script@v7 with: script: | const retentionDays = parseInt('${{ github.event.inputs.retention_days || 7 }}'); const dryRun = ${{ github.event.inputs.dry_run || false }}; const packageName = 'karpenter-provider-ibm-cloud/controller'; console.log(`Retention period: ${retentionDays} days`); console.log(`Dry run: ${dryRun}`); // Get all package versions let versions = []; try { versions = await github.paginate( github.rest.packages.getAllPackageVersionsForPackageOwnedByUser, { package_type: 'container', package_name: packageName, username: context.repo.owner, } ); } catch (error) { if (error.status === 404) { console.log(`Package '${packageName}' not found for user '${context.repo.owner}'. No cleanup needed.`); return; } throw error; } const now = new Date(); const cutoffDate = new Date(now.getTime() - (retentionDays * 24 * 60 * 60 * 1000)); console.log(`Cutoff date: ${cutoffDate.toISOString()}`); console.log(`Total versions found: ${versions.length}`); const nightlyVersions = versions.filter(v => { // Check if this is a nightly build by examining tags const tags = v.metadata?.container?.tags || []; return tags.some(tag => tag.includes('nightly') || tag.match(/v\d+\.\d+\.\d+-\d{4}-\d{2}-\d{2}-[a-f0-9]+-/) ); }); console.log(`Nightly versions found: ${nightlyVersions.length}`); const versionsToDelete = nightlyVersions.filter(v => { const createdAt = new Date(v.created_at); return createdAt < cutoffDate; }); console.log(`Versions to delete: ${versionsToDelete.length}`); if (versionsToDelete.length === 0) { console.log('No old nightly builds to clean up'); return; } // Create summary let summary = '# Nightly Build Cleanup Summary\n\n'; summary += `- **Retention Period**: ${retentionDays} days\n`; summary += `- **Cutoff Date**: ${cutoffDate.toISOString()}\n`; summary += `- **Total Nightly Versions**: ${nightlyVersions.length}\n`; summary += `- **Versions to Delete**: ${versionsToDelete.length}\n`; summary += `- **Dry Run**: ${dryRun}\n\n`; summary += '## Versions to be deleted:\n\n'; summary += '| Version | Created At | Tags |\n'; summary += '|---------|------------|------|\n'; for (const version of versionsToDelete) { const tags = version.metadata?.container?.tags || []; summary += `| ${version.name} | ${version.created_at} | ${tags.join(', ')} |\n`; if (!dryRun) { try { // First, try to get the version to confirm it exists try { await github.rest.packages.getPackageVersionForUser({ package_type: 'container', package_name: packageName, username: context.repo.owner, package_version_id: version.id, }); } catch (getError) { if (getError.status === 404) { console.warn(`Version ${version.name} (${version.id}) not found, skipping deletion.`); continue; } throw getError; } // Version exists, proceed with deletion await github.rest.packages.deletePackageVersionForUser({ package_type: 'container', package_name: packageName, username: context.repo.owner, package_version_id: version.id, }); console.log(`Deleted version: ${version.name}`); } catch (error) { if (error.status === 404) { console.warn(`Version ${version.name} (${version.id}) not found during deletion, skipping.`); } else { console.error(`Failed to delete version ${version.name}: ${error.message}`); } } } } // Write summary const fs = require('fs'); fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY, summary); if (dryRun) { console.log('Dry run completed - no versions were actually deleted'); } else { console.log('Cleanup completed'); }