221 lines
6.5 KiB
YAML
221 lines
6.5 KiB
YAML
# Forgejo OCI Registry Cleanup CronJob
|
|
# Deletes old image tags, keeping only the latest N versions per repository.
|
|
# Useful for retiring old builds when new versions are pushed.
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: forgejo-registry-cleanup-script
|
|
namespace: cicd
|
|
data:
|
|
cleanup.sh: |
|
|
#!/bin/bash
|
|
set -eo pipefail
|
|
|
|
# Configuration
|
|
REGISTRY_HOST="${REGISTRY_HOST:-forgejo.riotpiao.com}"
|
|
REGISTRY_URL="https://${REGISTRY_HOST}"
|
|
KEEP_VERSIONS="${KEEP_VERSIONS:-3}" # Keep latest N versions per image
|
|
DRY_RUN="${DRY_RUN:-false}"
|
|
|
|
# Load credentials from mounted secret
|
|
REGISTRY_USER="${REGISTRY_USER:-_json_key}"
|
|
REGISTRY_PASS="$(cat /etc/registry-secret/password 2>/dev/null || echo '')"
|
|
|
|
log() {
|
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
|
|
}
|
|
|
|
error() {
|
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2
|
|
return 1
|
|
}
|
|
|
|
# Verify crane is available
|
|
if ! command -v crane &> /dev/null; then
|
|
error "crane not found. Install google/crane image for registry operations."
|
|
exit 1
|
|
fi
|
|
|
|
log "Starting Forgejo registry cleanup"
|
|
log "Registry: $REGISTRY_URL"
|
|
log "Keep versions: $KEEP_VERSIONS per image"
|
|
log "Dry run: $DRY_RUN"
|
|
|
|
# Authenticate crane with registry
|
|
if [ -n "$REGISTRY_PASS" ]; then
|
|
echo "$REGISTRY_PASS" | crane auth login "$REGISTRY_HOST" -u "$REGISTRY_USER" --password-stdin
|
|
log "Authenticated to $REGISTRY_HOST"
|
|
fi
|
|
|
|
# List all repositories (catalog)
|
|
# Note: This endpoint requires the registry to expose /v2/_catalog (standard OCI)
|
|
# If not available, images must be discovered another way
|
|
CATALOG=$(curl -s -u "${REGISTRY_USER}:${REGISTRY_PASS}" \
|
|
"${REGISTRY_URL}/v2/_catalog" | grep -o '"repositories":\[\K[^]]*' || echo '')
|
|
|
|
if [ -z "$CATALOG" ]; then
|
|
log "WARNING: Could not retrieve catalog from ${REGISTRY_URL}/v2/_catalog"
|
|
log "Registry may not expose _catalog endpoint or credentials invalid"
|
|
exit 0
|
|
fi
|
|
|
|
# Parse repositories from catalog JSON
|
|
REPOS=$(echo "$CATALOG" | grep -o '"[^"]*"' | tr -d '"')
|
|
|
|
TOTAL_DELETED=0
|
|
|
|
for REPO in $REPOS; do
|
|
log "Processing repository: $REPO"
|
|
|
|
IMAGE="${REGISTRY_HOST}/${REPO}"
|
|
|
|
# Get all tags for this image
|
|
TAGS=$(crane ls "$IMAGE" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$TAGS" ]; then
|
|
log " No tags found for $REPO (or access denied)"
|
|
continue
|
|
fi
|
|
|
|
# Filter out 'latest' tag and sort by creation time (newer first)
|
|
# Note: crane doesn't provide direct date sorting; we use the order returned
|
|
# Assumption: tags are returned newest first (not always true)
|
|
TAG_COUNT=$(echo "$TAGS" | wc -l)
|
|
|
|
if [ "$TAG_COUNT" -le "$KEEP_VERSIONS" ]; then
|
|
log " $REPO: $TAG_COUNT tags total, keeping all (≤ $KEEP_VERSIONS)"
|
|
continue
|
|
fi
|
|
|
|
# Get tags to delete (all except the first N)
|
|
TAGS_TO_DELETE=$(echo "$TAGS" | tail -n +$((KEEP_VERSIONS + 1)))
|
|
|
|
for TAG in $TAGS_TO_DELETE; do
|
|
FULL_IMAGE="${IMAGE}:${TAG}"
|
|
DELETED_SIZE="0"
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
log " [DRY RUN] Would delete: $FULL_IMAGE"
|
|
else
|
|
if crane delete "$FULL_IMAGE" 2>&1; then
|
|
log " Deleted: $FULL_IMAGE"
|
|
((TOTAL_DELETED++))
|
|
else
|
|
error "Failed to delete $FULL_IMAGE (may already be deleted)"
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
|
|
log "Cleanup complete. Total images deleted: $TOTAL_DELETED"
|
|
|
|
---
|
|
apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: forgejo-registry-cleanup
|
|
namespace: cicd
|
|
labels:
|
|
app: forgejo-registry-cleanup
|
|
spec:
|
|
# Run at 2 AM UTC every day (adjust as needed)
|
|
schedule: "0 2 * * *"
|
|
|
|
# Keep last 3 successful/failed runs for debugging
|
|
successfulJobsHistoryLimit: 3
|
|
failedJobsHistoryLimit: 3
|
|
|
|
# Suspend if needed (set to false to enable)
|
|
suspend: false
|
|
|
|
jobTemplate:
|
|
spec:
|
|
# Cleanup jobs after 6 hours whether they succeeded or failed
|
|
ttlSecondsAfterFinished: 21600
|
|
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: forgejo-registry-cleanup
|
|
spec:
|
|
serviceAccountName: forgejo-registry-cleanup
|
|
restartPolicy: OnFailure
|
|
|
|
containers:
|
|
- name: cleanup
|
|
# Use google/crane for registry operations
|
|
image: gcr.io/go-containerregistry/crane:latest
|
|
imagePullPolicy: IfNotPresent
|
|
|
|
env:
|
|
- name: REGISTRY_HOST
|
|
value: "forgejo.riotpiao.com"
|
|
- name: KEEP_VERSIONS
|
|
value: "3" # Keep 3 latest versions
|
|
- name: DRY_RUN
|
|
value: "false" # Set to "true" for dry-run mode
|
|
- name: REGISTRY_USER
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: forgejo-registry-token
|
|
key: username
|
|
optional: true
|
|
|
|
volumeMounts:
|
|
- name: script
|
|
mountPath: /scripts
|
|
- name: registry-secret
|
|
mountPath: /etc/registry-secret
|
|
readOnly: true
|
|
|
|
# Run cleanup script via entrypoint override
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
# Install bash and curl if needed
|
|
apk add --no-cache bash curl
|
|
chmod +x /scripts/cleanup.sh
|
|
/scripts/cleanup.sh
|
|
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 128Mi
|
|
limits:
|
|
cpu: 500m
|
|
memory: 512Mi
|
|
|
|
# Safety: kill after 30 min (prevents hanging on large registries)
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: false
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
|
|
volumes:
|
|
- name: script
|
|
configMap:
|
|
name: forgejo-registry-cleanup-script
|
|
defaultMode: 0755
|
|
- name: registry-secret
|
|
secret:
|
|
secretName: forgejo-registry-token
|
|
optional: true
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: forgejo-registry-cleanup
|
|
namespace: cicd
|
|
|
|
---
|
|
# No RBAC needed: this pod only talks to the registry API (external service)
|
|
# If expanded to manage in-cluster resources, add Role/RoleBinding here
|