Cleanup E2E Docker Images #3
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 E2E Docker Images | |
| on: | |
| pull_request: | |
| types: [closed] | |
| schedule: | |
| - cron: "0 3 * * 6" # Every Saturday at 03:00 UTC | |
| workflow_dispatch: | |
| inputs: | |
| max_age_days: | |
| description: "Delete CI tags older than this many days (0 = delete all)" | |
| required: false | |
| default: 7 | |
| type: number | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| IMAGES: >- | |
| opik-backend | |
| opik-python-backend | |
| opik-guardrails-backend | |
| jobs: | |
| cleanup: | |
| name: Cleanup CI images | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Delete CI images | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| MAX_AGE_DAYS: ${{ github.event.inputs.max_age_days || '7' }} | |
| run: | | |
| DELETED=0 | |
| SKIPPED=0 | |
| FAILED=0 | |
| # Build the jq filter and age cutoff based on trigger type | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| echo "Mode: PR close — deleting all ci-e2e-pr${PR_NUMBER}-* images" | |
| JQ_FILTER=".[] | select(.metadata.container.tags | any(startswith(\"ci-e2e-pr${PR_NUMBER}-\"))) | {id: .id, tags: .metadata.container.tags, created_at: .created_at}" | |
| CUTOFF="" | |
| else | |
| echo "Mode: stale sweep — deleting ci-e2e-* images older than ${MAX_AGE_DAYS} days" | |
| JQ_FILTER='.[] | select(.metadata.container.tags | any(startswith("ci-e2e-"))) | {id: .id, tags: .metadata.container.tags, created_at: .created_at}' | |
| CUTOFF=$(date -u -d "${MAX_AGE_DAYS} days ago" +%s 2>/dev/null \ | |
| || date -u -v-"${MAX_AGE_DAYS}"d +%s) | |
| fi | |
| for IMAGE in ${{ env.IMAGES }}; do | |
| echo "=== Scanning ${IMAGE} ===" | |
| if ! VERSIONS=$(gh api \ | |
| "/orgs/comet-ml/packages/container/opik%2F${IMAGE}/versions" \ | |
| --paginate \ | |
| --jq "$JQ_FILTER" 2>&1); then | |
| echo " Failed to list versions: ${VERSIONS}" | |
| continue | |
| fi | |
| if [ -z "$VERSIONS" ]; then | |
| echo " No matching versions found" | |
| continue | |
| fi | |
| COUNT=$(echo "$VERSIONS" | wc -l | tr -d ' ') | |
| echo " Found ${COUNT} matching versions" | |
| while read -r VERSION; do | |
| [ -z "$VERSION" ] && continue | |
| VERSION_ID=$(echo "$VERSION" | jq -r '.id') | |
| CI_TAG=$(echo "$VERSION" | jq -r '.tags[] | select(startswith("ci-e2e-"))' | head -1) | |
| # For stale sweep, check age; for PR close, delete unconditionally | |
| if [ -n "$CUTOFF" ]; then | |
| CREATED_AT=$(echo "$VERSION" | jq -r '.created_at') | |
| CREATED_TS=$(date -u -d "$CREATED_AT" +%s 2>/dev/null \ | |
| || date -u -j -f "%Y-%m-%dT%H:%M:%S" "${CREATED_AT%%.*}" +%s 2>/dev/null \ | |
| || echo "") | |
| if [ -z "$CREATED_TS" ]; then | |
| echo " Skipping ${IMAGE}:${CI_TAG} (could not parse created_at: ${CREATED_AT})" | |
| SKIPPED=$((SKIPPED + 1)) | |
| continue | |
| fi | |
| if [ "$CREATED_TS" -ge "$CUTOFF" ]; then | |
| SKIPPED=$((SKIPPED + 1)) | |
| continue | |
| fi | |
| echo " Deleting ${IMAGE}:${CI_TAG} (older than ${MAX_AGE_DAYS} days)" | |
| else | |
| echo " Deleting ${IMAGE}:${CI_TAG}" | |
| fi | |
| if gh api --method DELETE \ | |
| "/orgs/comet-ml/packages/container/opik%2F${IMAGE}/versions/${VERSION_ID}"; then | |
| DELETED=$((DELETED + 1)) | |
| else | |
| FAILED=$((FAILED + 1)) | |
| fi | |
| done <<< "$VERSIONS" | |
| done | |
| echo "" | |
| echo "=== Summary ===" | |
| echo "Deleted: ${DELETED}" | |
| echo "Skipped: ${SKIPPED}" | |
| echo "Failed: ${FAILED}" |