feat: purge scripts for CI/CD artifacts
This commit is contained in:
91
scripts/ci/purge-action-runs.sh
Executable file
91
scripts/ci/purge-action-runs.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Defaults (override via CLI flags)
|
||||
GITEA_BASE_URL="https://git.motovaultpro.com"
|
||||
TOKEN=""
|
||||
OWNER="egullickson"
|
||||
REPO="motovaultpro"
|
||||
PER_PAGE=50
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$0 --token=YOUR_TOKEN [--base-url=https://git.motovaultpro.com] [--owner=egullickson] [--repo=motovaultpro] [--per-page=50]
|
||||
|
||||
Examples:
|
||||
# Delete all Actions runs in egullickson/motovaultpro
|
||||
$0 --token=XXXX
|
||||
|
||||
# Different repo
|
||||
$0 --token=XXXX --owner=someone --repo=otherrepo
|
||||
|
||||
# Different server + page size
|
||||
$0 --token=XXXX --base-url=https://git.example.com --per-page=100
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- CLI parsing ---
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--token=*) TOKEN="${arg#*=}" ;;
|
||||
--base-url=*) GITEA_BASE_URL="${arg#*=}" ;;
|
||||
--owner=*) OWNER="${arg#*=}" ;;
|
||||
--repo=*) REPO="${arg#*=}" ;;
|
||||
--per-page=*) PER_PAGE="${arg#*=}" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*)
|
||||
echo "ERROR: Unknown argument: $arg"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "ERROR: --token= is required"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Basic validation
|
||||
if ! [[ "$PER_PAGE" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: --per-page must be a number"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
api() {
|
||||
curl -fsS \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Accept: application/json" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
page=1
|
||||
deleted=0
|
||||
|
||||
while :; do
|
||||
echo "Fetching page ${page}..."
|
||||
json="$(api "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/actions/runs?limit=${PER_PAGE}&page=${page}")"
|
||||
|
||||
# Try both common shapes:
|
||||
# - {"workflow_runs":[...]}
|
||||
# - {"runs":[...]}
|
||||
ids="$(echo "$json" | jq -r '(.workflow_runs // .runs // []) | .[].id')"
|
||||
|
||||
if [[ -z "${ids}" ]]; then
|
||||
echo "No more runs found."
|
||||
break
|
||||
fi
|
||||
|
||||
while read -r id; do
|
||||
[[ -z "$id" ]] && continue
|
||||
echo "Deleting run id=${id}"
|
||||
api -X DELETE "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/actions/runs/${id}" >/dev/null
|
||||
deleted=$((deleted + 1))
|
||||
done <<< "${ids}"
|
||||
|
||||
page=$((page + 1))
|
||||
done
|
||||
|
||||
echo "Done. Deleted ${deleted} runs."
|
||||
191
scripts/ci/purge-container-images.sh
Executable file
191
scripts/ci/purge-container-images.sh
Executable file
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# =============================================================================
|
||||
# Defaults (override via CLI flags)
|
||||
# =============================================================================
|
||||
REGISTRY="https://git.motovaultpro.com"
|
||||
USERNAME="egullickson"
|
||||
TOKEN=""
|
||||
KEEP_TAG="latest"
|
||||
DRY_RUN=1
|
||||
|
||||
REPOS=(
|
||||
"egullickson/frontend"
|
||||
"egullickson/backend"
|
||||
)
|
||||
# =============================================================================
|
||||
|
||||
need() { command -v "$1" >/dev/null || { echo "ERROR: missing dependency: $1"; exit 1; }; }
|
||||
need curl
|
||||
need jq
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$0 --token=YOUR_PAT [--user=egullickson] [--registry=https://git.motovaultpro.com]
|
||||
[--keep-tag=latest] [--dry-run|--no-dry-run]
|
||||
|
||||
Examples:
|
||||
# Preview
|
||||
$0 --token=XXXX --dry-run
|
||||
|
||||
# Actually delete
|
||||
$0 --token=XXXX --no-dry-run
|
||||
|
||||
Notes:
|
||||
- Deletes all tags except KEEP_TAG for repos:
|
||||
${REPOS[*]}
|
||||
- Protects KEEP_TAG by digest (won't delete anything pointing to KEEP_TAG digest).
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- CLI parsing -------------------------------------------------------------
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--token=*) TOKEN="${arg#*=}" ;;
|
||||
--user=*) USERNAME="${arg#*=}" ;;
|
||||
--registry=*) REGISTRY="${arg#*=}" ;;
|
||||
--keep-tag=*) KEEP_TAG="${arg#*=}" ;;
|
||||
--dry-run) DRY_RUN=1 ;;
|
||||
--no-dry-run) DRY_RUN=0 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*)
|
||||
echo "ERROR: Unknown argument: $arg"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "ERROR: --token= is required"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACCEPT_MANIFEST="application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json"
|
||||
|
||||
get_bearer_token() {
|
||||
local repo="$1"
|
||||
local scope="repository:${repo}:pull,delete"
|
||||
curl -fsS -u "${USERNAME}:${TOKEN}" \
|
||||
"${REGISTRY}/v2/token?service=container_registry&scope=${scope}" \
|
||||
| jq -r '.token // .access_token // empty'
|
||||
}
|
||||
|
||||
auth_header_for_repo() {
|
||||
local repo="$1"
|
||||
local t
|
||||
t="$(get_bearer_token "$repo")"
|
||||
if [[ -z "$t" ]]; then
|
||||
echo "ERROR: Could not obtain bearer token for ${repo} (check PAT permissions)."
|
||||
exit 1
|
||||
fi
|
||||
echo "Authorization: Bearer ${t}"
|
||||
}
|
||||
|
||||
tags_list() {
|
||||
local repo="$1" auth="$2"
|
||||
curl -fsS -H "$auth" "${REGISTRY}/v2/${repo}/tags/list"
|
||||
}
|
||||
|
||||
digest_for_tag() {
|
||||
local repo="$1" tag="$2" auth="$3"
|
||||
curl -fsSI \
|
||||
-H "$auth" \
|
||||
-H "Accept: ${ACCEPT_MANIFEST}" \
|
||||
"${REGISTRY}/v2/${repo}/manifests/${tag}" \
|
||||
| tr -d '\r' \
|
||||
| awk 'BEGIN{IGNORECASE=1} /^docker-content-digest:/ {print $2; exit}'
|
||||
}
|
||||
|
||||
delete_digest() {
|
||||
local repo="$1" digest="$2" auth="$3"
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
echo "DRY_RUN: DELETE ${REGISTRY}/v2/${repo}/manifests/${digest}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
curl -fsS -X DELETE -H "$auth" \
|
||||
"${REGISTRY}/v2/${repo}/manifests/${digest}" >/dev/null
|
||||
}
|
||||
|
||||
purge_repo_keep_tag() {
|
||||
local repo="$1"
|
||||
echo "== Purging ${repo} (keep ${KEEP_TAG}) =="
|
||||
|
||||
local auth
|
||||
auth="$(auth_header_for_repo "$repo")"
|
||||
|
||||
local json tags
|
||||
json="$(tags_list "$repo" "$auth")"
|
||||
tags="$(echo "$json" | jq -r '.tags[]?' | sort || true)"
|
||||
|
||||
if [[ -z "$tags" ]]; then
|
||||
echo "No tags found."
|
||||
echo
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! echo "$tags" | grep -qx "${KEEP_TAG}"; then
|
||||
echo "WARN: No '${KEEP_TAG}' tag found; refusing to purge ${repo}."
|
||||
echo "$tags" | sed 's/^/ - /'
|
||||
echo
|
||||
return 0
|
||||
fi
|
||||
|
||||
local keep_digest
|
||||
keep_digest="$(digest_for_tag "$repo" "$KEEP_TAG" "$auth" || true)"
|
||||
if [[ -z "$keep_digest" ]]; then
|
||||
echo "ERROR: Could not resolve digest for ${repo}:${KEEP_TAG}"
|
||||
echo "Tip: curl -vI -H \"$auth\" ${REGISTRY}/v2/${repo}/manifests/${KEEP_TAG}"
|
||||
echo
|
||||
return 1
|
||||
fi
|
||||
echo "${KEEP_TAG} digest: ${keep_digest}"
|
||||
|
||||
# Collect digests referenced by non-KEEP_TAG tags (skipping those equal to keep_digest)
|
||||
local digests=()
|
||||
while read -r tag; do
|
||||
[[ -z "$tag" || "$tag" == "$KEEP_TAG" ]] && continue
|
||||
|
||||
local d
|
||||
d="$(digest_for_tag "$repo" "$tag" "$auth" || true)"
|
||||
if [[ -z "$d" ]]; then
|
||||
echo "WARN: couldn't resolve digest for tag=${tag} (skipping)"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$d" == "$keep_digest" ]]; then
|
||||
echo "SKIP: tag=${tag} points to ${KEEP_TAG} digest (deleting would remove ${KEEP_TAG} too)"
|
||||
continue
|
||||
fi
|
||||
|
||||
digests+=("$d")
|
||||
done <<< "$tags"
|
||||
|
||||
if [[ "${#digests[@]}" -eq 0 ]]; then
|
||||
echo "Nothing to delete."
|
||||
echo
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Will delete (deduped) manifest(s) (and all tags pointing to them)."
|
||||
printf '%s\n' "${digests[@]}" | sort -u | while read -r digest; do
|
||||
[[ -z "$digest" ]] && continue
|
||||
echo "Deleting digest: $digest"
|
||||
if ! delete_digest "$repo" "$digest" "$auth"; then
|
||||
echo "WARN: delete failed for $digest"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Done."
|
||||
echo
|
||||
}
|
||||
|
||||
for repo in "${REPOS[@]}"; do
|
||||
purge_repo_keep_tag "$repo"
|
||||
done
|
||||
|
||||
echo "Complete."
|
||||
Reference in New Issue
Block a user