ci(docker): prune old image versions

This commit is contained in:
2026-07-09 10:23:34 +08:00
parent fc92c3a5d5
commit e8b745d991
6 changed files with 255 additions and 35 deletions
+7 -32
View File
@@ -5,28 +5,6 @@ warn() {
printf 'Notification warning: %s\n' "$*" >&2
}
generate_chat_id() {
if [ -n "${ACTION_NOTIFY_CHAT_ID:-}" ]; then
printf '%s' "$ACTION_NOTIFY_CHAT_ID"
return 0
fi
if command -v node >/dev/null 2>&1; then
node <<'NODE'
const { randomUUID } = require("node:crypto");
process.stdout.write(`oc_${randomUUID().replaceAll("-", "")}`);
NODE
return 0
fi
if command -v openssl >/dev/null 2>&1; then
printf 'oc_%s' "$(openssl rand -hex 16)"
return 0
fi
printf 'oc_%s%s' "$(date +%s)" "$$"
}
branch_name="${BRANCH_NAME:-${GITHUB_REF_NAME:-}}"
if [ -z "$branch_name" ] && [ -n "${GITHUB_REF:-}" ]; then
branch_name="${GITHUB_REF#refs/heads/}"
@@ -43,9 +21,11 @@ if [ -z "$short_sha" ]; then
short_sha="unknown"
fi
base_url="${FLOW_NOTIFY_BASE_URL:-https://flow.banlv-ai.com}"
base_url="${base_url%/}"
chat_id="$(generate_chat_id)"
notify_url="${ACTION_NOTIFY_WEBHOOK_URL:-}"
if [ -z "$notify_url" ]; then
warn "ACTION_NOTIFY_WEBHOOK_URL is not configured, skip notification."
exit 0
fi
deploy_env="${DEPLOY_ENV:-unknown}"
image_tag="${IMAGE_VERSION_TAG:-unknown}"
@@ -84,11 +64,6 @@ process.stdout.write(JSON.stringify(payload));
NODE
)"
encoded_chat_id="$(
node -e 'process.stdout.write(encodeURIComponent(process.argv[1] || ""))' "$chat_id"
)"
notify_url="$base_url/webhooks/action-notify?chat_id=$encoded_chat_id"
if [ "${FLOW_NOTIFY_DRY_RUN:-}" = "1" ]; then
printf 'Action notification dry run URL: %s\n' "$notify_url"
printf 'Action notification dry run payload: %s\n' "$payload"
@@ -99,8 +74,8 @@ if ! curl -fsS \
-X POST "$notify_url" \
-H "Content-Type: application/json" \
-d "$payload" >/dev/null; then
warn "failed to send action notification for $chat_id"
warn "failed to send action notification."
exit 0
fi
echo "Sent action notification for $chat_id."
echo "Sent action notification."
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env bash
set -euo pipefail
KEEP_COUNT="${GITEA_PACKAGE_KEEP_COUNT:-3}"
API_BASE_URL="${GITEA_API_BASE_URL:-https://gitea.banlv-ai.com/api/v1}"
TOKEN="${GITEA_PACKAGE_TOKEN:-}"
REGISTRY_IMAGE="${REGISTRY_IMAGE:-${IMAGE:-}}"
DEPLOY_ENV="${DEPLOY_ENV:-}"
CURRENT_IMAGE_TAG="${IMAGE_VERSION_TAG:-}"
if [ -z "$TOKEN" ] && [ -z "${GITEA_PACKAGE_VERSIONS_FILE:-}" ]; then
echo "Missing GITEA_PACKAGE_TOKEN" >&2
exit 1
fi
if [ -z "$REGISTRY_IMAGE" ] || [ -z "$DEPLOY_ENV" ]; then
echo "Missing REGISTRY_IMAGE/IMAGE or DEPLOY_ENV" >&2
exit 1
fi
case "$DEPLOY_ENV" in
test|prod) ;;
*)
echo "Unsupported DEPLOY_ENV for package pruning: $DEPLOY_ENV" >&2
exit 1
;;
esac
if ! command -v node >/dev/null 2>&1; then
echo "node is required for package pruning" >&2
exit 127
fi
if ! command -v curl >/dev/null 2>&1; then
echo "curl is required for package pruning" >&2
exit 127
fi
image_without_tag="${REGISTRY_IMAGE%%:*}"
path_without_registry="${image_without_tag#*/}"
owner="${path_without_registry%%/*}"
package="${path_without_registry#*/}"
if [ "$owner" = "$path_without_registry" ] || [ -z "$owner" ] || [ -z "$package" ]; then
echo "Could not parse owner/package from REGISTRY_IMAGE: $REGISTRY_IMAGE" >&2
exit 1
fi
current_version="${CURRENT_IMAGE_TAG##*:}"
version_prefix="${DEPLOY_ENV}-"
latest_version="${DEPLOY_ENV}-latest"
api_base="${API_BASE_URL%/}"
echo "=== prune Gitea container package versions ==="
echo "owner: $owner"
echo "package: $package"
echo "environment: $DEPLOY_ENV"
echo "keep count: $KEEP_COUNT"
versions_json="$(mktemp)"
delete_list="$(mktemp)"
cleanup() {
rm -f "$versions_json" "$delete_list"
}
trap cleanup EXIT
if [ -n "${GITEA_PACKAGE_VERSIONS_FILE:-}" ]; then
cp "$GITEA_PACKAGE_VERSIONS_FILE" "$versions_json"
else
curl -fsS \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
"$api_base/packages/$owner/container/$package" \
-o "$versions_json"
fi
KEEP_COUNT="$KEEP_COUNT" \
VERSION_PREFIX="$version_prefix" \
LATEST_VERSION="$latest_version" \
CURRENT_VERSION="$current_version" \
VERSIONS_JSON_FILE="$versions_json" \
node <<'NODE' > "$delete_list"
const fs = require("node:fs");
const file = process.env.VERSIONS_JSON_FILE;
const keepCount = Number.parseInt(process.env.KEEP_COUNT || "3", 10);
const prefix = process.env.VERSION_PREFIX || "";
const latest = process.env.LATEST_VERSION || "";
const current = process.env.CURRENT_VERSION || "";
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
const list = Array.isArray(raw) ? raw : Array.isArray(raw.versions) ? raw.versions : [];
function versionOf(item) {
return String(item.version || item.name || item.tag_name || "").trim();
}
function createdAtOf(item) {
return String(
item.created_at ||
item.created ||
item.createdAt ||
item.published_at ||
item.updated_at ||
"",
);
}
const candidates = list
.map((item) => ({
version: versionOf(item),
createdAt: createdAtOf(item),
}))
.filter(({ version }) => {
if (!version.startsWith(prefix)) return false;
if (version === latest) return false;
return true;
})
.sort((a, b) => {
const bTime = Date.parse(b.createdAt);
const aTime = Date.parse(a.createdAt);
if (Number.isFinite(bTime) && Number.isFinite(aTime) && bTime !== aTime) {
return bTime - aTime;
}
return b.version.localeCompare(a.version);
});
const kept = new Set(candidates.slice(0, Math.max(0, keepCount)).map((item) => item.version));
const toDelete = candidates
.slice(Math.max(0, keepCount))
.map((item) => item.version)
.filter((version) => version !== current && !kept.has(version));
process.stdout.write(toDelete.join("\n"));
if (toDelete.length > 0) process.stdout.write("\n");
NODE
if [ ! -s "$delete_list" ]; then
echo "No old $DEPLOY_ENV package versions to delete."
exit 0
fi
echo "Package versions selected for deletion:"
sed 's/^/ - /' "$delete_list"
if [ "${PRUNE_DRY_RUN:-}" = "1" ]; then
echo "Dry run enabled, skip DELETE requests."
exit 0
fi
while IFS= read -r version; do
[ -n "$version" ] || continue
encoded_owner="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1] || ""))' "$owner")"
encoded_package="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1] || ""))' "$package")"
encoded_version="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1] || ""))' "$version")"
curl -fsS \
-X DELETE \
-H "Authorization: token $TOKEN" \
"$api_base/packages/$encoded_owner/container/$encoded_package/$encoded_version" \
>/dev/null
echo "Deleted package version: $version"
done < "$delete_list"
echo "=== package prune done ==="