name: ACR Cleanup on: schedule: - cron: '0 19 * * *' # UTC 19:00 = 北京时间凌晨3:00 workflow_dispatch: inputs: pr_sha: description: "PR commit SHA(仅清理指定PR镜像,留空则全量清理)" required: false default: "" dry_run: description: "预览模式(dry-run),不实际删除" required: false default: "true" pull_request_target: types: [closed] branches: [develop, main] concurrency: group: acr-cleanup-${{ gitea.ref }} cancel-in-progress: false jobs: cleanup: name: ACR Image Cleanup runs-on: ci-l2 timeout-minutes: 20 permissions: contents: read env: ACR_REGISTRY: xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com ACR_NAMESPACE: xiaoxiakeji ACR_SERVICE: registry.aliyuncs.com:cn-hangzhou:china:cri-fvec8o9q4mmxrkaa GITEA_URL: https://git.xiaoxiajianji.com GITEA_REPO: xiaoxia/xiaoxia-saas steps: - name: Checkout code shell: sh env: GITHUB_TOKEN: ${{ github.token }} run: | curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash # ====== Cron模式:获取staging运行中镜像作为白名单 ====== - name: Get staging running images (whitelist) id: protected_images if: gitea.event_name != 'pull_request_target' && !gitea.event.inputs.pr_sha env: STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }} STAGING_SSH_PORT: ${{ secrets.STAGING_SSH_PORT }} STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }} STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }} run: | set +e echo "获取staging服务器运行中镜像作为白名单..." staging_host="${STAGING_SSH_HOST:-47.98.113.167}" staging_port="${STAGING_SSH_PORT:-22222}" staging_user="${STAGING_SSH_USER:-root}" key_path=~/.ssh/id_rsa if [ -n "${STAGING_SSH_KEY:-}" ]; then printf '%s\n' "$STAGING_SSH_KEY" > "$key_path" chmod 600 "$key_path" echo "Using key from STAGING_SSH_KEY secret" else echo "⚠️ STAGING_SSH_KEY not set, skipping whitelist" echo "protected_tags=" >> $GITHUB_OUTPUT exit 0 fi ssh-keyscan -p "$staging_port" -H "$staging_host" >> ~/.ssh/known_hosts 2>/dev/null # 获取所有运行容器的镜像,提取tag部分 IMAGES=$(ssh -p "$staging_port" -i "$key_path" -o StrictHostKeyChecking=no \ "$staging_user@$staging_host" "docker ps --format '{{.Image}}' 2>/dev/null" 2>/dev/null | grep -v "^$" | sort -u) PROTECTED_TAGS="" if [ -n "$IMAGES" ]; then while IFS= read -r img; do # 从完整镜像名中提取tag(最后一个冒号后) tag=$(echo "$img" | rev | cut -d: -f1 | rev) if [ -n "$tag" ] && [ "$tag" != "latest" ] && [ ${#tag} -gt 5 ]; then if [ -z "$PROTECTED_TAGS" ]; then PROTECTED_TAGS="$tag" else PROTECTED_TAGS="$PROTECTED_TAGS,$tag" fi fi done <<< "$IMAGES" fi echo "staging运行中镜像tag: ${PROTECTED_TAGS:-(无)}" echo "protected_tags=$PROTECTED_TAGS" >> $GITHUB_OUTPUT # ====== Docker登录 ====== - name: Docker login to ACR env: ACR_USERNAME: ${{ secrets.ACR_USERNAME }} ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} run: | printf '%s' "$ACR_PASSWORD" | docker login "$ACR_REGISTRY" -u "$ACR_USERNAME" --password-stdin # ====== 模式1:PR关闭时清理 ====== - name: Cleanup PR images (PR closed) if: gitea.event_name == 'pull_request_target' env: ACR_USERNAME: ${{ secrets.ACR_USERNAME }} ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} PR_SHA: ${{ gitea.event.pull_request.head.sha }} PR_NUMBER: ${{ gitea.event.pull_request.number }} run: | echo "============================================" echo " PR #$PR_NUMBER 已关闭,清理对应镜像" echo " Head SHA: ${PR_SHA::12}" echo "============================================" echo "" python3 scripts/ci/acr_cleanup.py \ --pr-sha "$PR_SHA" \ --execute # ====== 模式2:Cron全量清理 ====== - name: Full cleanup (cron / manual) if: gitea.event_name != 'pull_request_target' && !gitea.event.inputs.pr_sha env: ACR_USERNAME: ${{ secrets.ACR_USERNAME }} ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} PROTECTED_TAGS: ${{ steps.protected_images.outputs.protected_tags }} DRY_RUN_INPUT: ${{ gitea.event.inputs.dry_run }} run: | echo "============================================" echo " ACR 全量清理(${{ gitea.event_name }})" echo "============================================" echo "" # 决定是否dry-run DRY_RUN_FLAG="" if [ "$DRY_RUN_INPUT" = "true" ]; then DRY_RUN_FLAG="--dry-run" echo "模式: 预览模式 (dry-run)" else echo "模式: 执行模式" fi echo "" python3 scripts/ci/acr_cleanup.py \ --keep 20 \ --protected-tags "$PROTECTED_TAGS" \ $DRY_RUN_FLAG # ====== 模式3:手动指定PR SHA清理 ====== - name: Cleanup specific PR image (manual) if: gitea.event_name == 'workflow_dispatch' && gitea.event.inputs.pr_sha env: ACR_USERNAME: ${{ secrets.ACR_USERNAME }} ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} PR_SHA: ${{ gitea.event.inputs.pr_sha }} DRY_RUN_INPUT: ${{ gitea.event.inputs.dry_run }} run: | echo "手动清理PR镜像: ${PR_SHA::12}" echo "" DRY_RUN_FLAG="" if [ "$DRY_RUN_INPUT" = "true" ]; then DRY_RUN_FLAG="--dry-run" fi python3 scripts/ci/acr_cleanup.py \ --pr-sha "$PR_SHA" \ $DRY_RUN_FLAG