227 lines
9.6 KiB
YAML
227 lines
9.6 KiB
YAML
name: Container Registry Prune
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 3 * * 0' # Weekly: Sundays at 03:00 UTC
|
|
workflow_dispatch:
|
|
inputs:
|
|
keep_days:
|
|
description: 'Number of days to retain images (unprotected)'
|
|
required: false
|
|
default: '30'
|
|
dry_run:
|
|
description: 'If true, only logs candidates and does not delete (default: false for active cleanup)'
|
|
required: false
|
|
default: 'false'
|
|
keep_last_n:
|
|
description: 'Keep last N newest images (global)'
|
|
required: false
|
|
default: '30'
|
|
|
|
permissions:
|
|
packages: write
|
|
contents: read
|
|
|
|
jobs:
|
|
prune-ghcr:
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
OWNER: ${{ github.repository_owner }}
|
|
IMAGE_NAME: charon
|
|
KEEP_DAYS: ${{ github.event.inputs.keep_days || '30' }}
|
|
KEEP_LAST_N: ${{ github.event.inputs.keep_last_n || '30' }}
|
|
DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || github.event.inputs.dry_run || 'false' }}
|
|
PROTECTED_REGEX: '["^v?[0-9]+\\.[0-9]+\\.[0-9]+$","^latest$","^main$","^develop$"]'
|
|
PRUNE_UNTAGGED: 'true'
|
|
PRUNE_SBOM_TAGS: 'true'
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Install tools
|
|
run: |
|
|
sudo apt-get update && sudo apt-get install -y jq curl
|
|
|
|
- name: Run GHCR prune
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
chmod +x scripts/prune-ghcr.sh
|
|
./scripts/prune-ghcr.sh 2>&1 | tee prune-ghcr-${{ github.run_id }}.log
|
|
|
|
- name: Summarize GHCR results
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
SUMMARY_FILE=prune-summary-ghcr.env
|
|
LOG_FILE=prune-ghcr-${{ github.run_id }}.log
|
|
|
|
human() {
|
|
local bytes=${1:-0}
|
|
if [ -z "$bytes" ] || [ "$bytes" -eq 0 ]; then
|
|
echo "0 B"
|
|
return
|
|
fi
|
|
awk -v b="$bytes" 'BEGIN { split("B KiB MiB GiB TiB",u," "); i=0; x=b; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1] }'
|
|
}
|
|
|
|
if [ -f "$SUMMARY_FILE" ]; then
|
|
TOTAL_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
TOTAL_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
TOTAL_DELETED=$(grep -E '^TOTAL_DELETED=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
TOTAL_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
|
|
{
|
|
echo "## GHCR prune summary"
|
|
echo "- candidates: ${TOTAL_CANDIDATES} (≈ $(human "${TOTAL_CANDIDATES_BYTES}"))"
|
|
echo "- deleted: ${TOTAL_DELETED} (≈ $(human "${TOTAL_DELETED_BYTES}"))"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
deleted_bytes=$(grep -oE '\( *approx +[0-9]+ bytes\)' "$LOG_FILE" | sed -E 's/.*approx +([0-9]+) bytes.*/\1/' | awk '{s+=$1} END {print s+0}' || true)
|
|
deleted_count=$(grep -cE 'deleting |DRY RUN: would delete' "$LOG_FILE" || true)
|
|
|
|
{
|
|
echo "## GHCR prune summary"
|
|
echo "- deleted (approx): ${deleted_count} (≈ $(human "${deleted_bytes}"))"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
- name: Upload GHCR prune artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: prune-ghcr-log-${{ github.run_id }}
|
|
path: |
|
|
prune-ghcr-${{ github.run_id }}.log
|
|
prune-summary-ghcr.env
|
|
|
|
prune-dockerhub:
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
OWNER: ${{ github.repository_owner }}
|
|
IMAGE_NAME: charon
|
|
KEEP_DAYS: ${{ github.event.inputs.keep_days || '30' }}
|
|
KEEP_LAST_N: ${{ github.event.inputs.keep_last_n || '30' }}
|
|
DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || github.event.inputs.dry_run || 'false' }}
|
|
PROTECTED_REGEX: '["^v?[0-9]+\\.[0-9]+\\.[0-9]+$","^latest$","^main$","^develop$"]'
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
|
|
- name: Install tools
|
|
run: |
|
|
sudo apt-get update && sudo apt-get install -y jq curl
|
|
|
|
- name: Run Docker Hub prune
|
|
env:
|
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
run: |
|
|
chmod +x scripts/prune-dockerhub.sh
|
|
./scripts/prune-dockerhub.sh 2>&1 | tee prune-dockerhub-${{ github.run_id }}.log
|
|
|
|
- name: Summarize Docker Hub results
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
SUMMARY_FILE=prune-summary-dockerhub.env
|
|
LOG_FILE=prune-dockerhub-${{ github.run_id }}.log
|
|
|
|
human() {
|
|
local bytes=${1:-0}
|
|
if [ -z "$bytes" ] || [ "$bytes" -eq 0 ]; then
|
|
echo "0 B"
|
|
return
|
|
fi
|
|
awk -v b="$bytes" 'BEGIN { split("B KiB MiB GiB TiB",u," "); i=0; x=b; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1] }'
|
|
}
|
|
|
|
if [ -f "$SUMMARY_FILE" ]; then
|
|
TOTAL_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
TOTAL_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
TOTAL_DELETED=$(grep -E '^TOTAL_DELETED=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
TOTAL_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
|
|
|
{
|
|
echo "## Docker Hub prune summary"
|
|
echo "- candidates: ${TOTAL_CANDIDATES} (≈ $(human "${TOTAL_CANDIDATES_BYTES}"))"
|
|
echo "- deleted: ${TOTAL_DELETED} (≈ $(human "${TOTAL_DELETED_BYTES}"))"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
deleted_bytes=$(grep -oE '\( *approx +[0-9]+ bytes\)' "$LOG_FILE" | sed -E 's/.*approx +([0-9]+) bytes.*/\1/' | awk '{s+=$1} END {print s+0}' || true)
|
|
deleted_count=$(grep -cE 'deleting |DRY RUN: would delete' "$LOG_FILE" || true)
|
|
|
|
{
|
|
echo "## Docker Hub prune summary"
|
|
echo "- deleted (approx): ${deleted_count} (≈ $(human "${deleted_bytes}"))"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
- name: Upload Docker Hub prune artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: prune-dockerhub-log-${{ github.run_id }}
|
|
path: |
|
|
prune-dockerhub-${{ github.run_id }}.log
|
|
prune-summary-dockerhub.env
|
|
|
|
summarize:
|
|
runs-on: ubuntu-latest
|
|
needs: [prune-ghcr, prune-dockerhub]
|
|
if: always()
|
|
steps:
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
pattern: prune-*-log-${{ github.run_id }}
|
|
merge-multiple: true
|
|
|
|
- name: Combined summary
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
human() {
|
|
local bytes=${1:-0}
|
|
if [ -z "$bytes" ] || [ "$bytes" -eq 0 ]; then
|
|
echo "0 B"
|
|
return
|
|
fi
|
|
awk -v b="$bytes" 'BEGIN { split("B KiB MiB GiB TiB",u," "); i=0; x=b; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1] }'
|
|
}
|
|
|
|
GHCR_CANDIDATES=0 GHCR_CANDIDATES_BYTES=0 GHCR_DELETED=0 GHCR_DELETED_BYTES=0
|
|
if [ -f prune-summary-ghcr.env ]; then
|
|
GHCR_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
|
GHCR_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
|
GHCR_DELETED=$(grep -E '^TOTAL_DELETED=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
|
GHCR_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
|
fi
|
|
|
|
HUB_CANDIDATES=0 HUB_CANDIDATES_BYTES=0 HUB_DELETED=0 HUB_DELETED_BYTES=0
|
|
if [ -f prune-summary-dockerhub.env ]; then
|
|
HUB_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
|
HUB_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
|
HUB_DELETED=$(grep -E '^TOTAL_DELETED=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
|
HUB_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
|
fi
|
|
|
|
TOTAL_CANDIDATES=$((GHCR_CANDIDATES + HUB_CANDIDATES))
|
|
TOTAL_CANDIDATES_BYTES=$((GHCR_CANDIDATES_BYTES + HUB_CANDIDATES_BYTES))
|
|
TOTAL_DELETED=$((GHCR_DELETED + HUB_DELETED))
|
|
TOTAL_DELETED_BYTES=$((GHCR_DELETED_BYTES + HUB_DELETED_BYTES))
|
|
|
|
{
|
|
echo "## Combined container prune summary"
|
|
echo ""
|
|
echo "| Registry | Candidates | Deleted | Space Reclaimed |"
|
|
echo "|----------|------------|---------|-----------------|"
|
|
echo "| GHCR | ${GHCR_CANDIDATES} | ${GHCR_DELETED} | $(human "${GHCR_DELETED_BYTES}") |"
|
|
echo "| Docker Hub | ${HUB_CANDIDATES} | ${HUB_DELETED} | $(human "${HUB_DELETED_BYTES}") |"
|
|
echo "| **Total** | **${TOTAL_CANDIDATES}** | **${TOTAL_DELETED}** | **$(human "${TOTAL_DELETED_BYTES}")** |"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
printf 'PRUNE_SUMMARY: candidates=%s candidates_bytes=%s deleted=%s deleted_bytes=%s\n' \
|
|
"${TOTAL_CANDIDATES}" "${TOTAL_CANDIDATES_BYTES}" "${TOTAL_DELETED}" "${TOTAL_DELETED_BYTES}"
|
|
echo "Total space reclaimed: $(human "${TOTAL_DELETED_BYTES}")"
|