274 lines
7.4 KiB
Bash
Executable File
274 lines
7.4 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
log() {
|
|
printf '%s\n' "$*" >&2
|
|
}
|
|
|
|
fail() {
|
|
log "ERROR: $*"
|
|
exit 1
|
|
}
|
|
|
|
declare -A TARGET_PACKAGES=()
|
|
|
|
resolve_token() {
|
|
log "Trying token from RUNNER_TOKEN"
|
|
|
|
if [[ -n "${RUNNER_TOKEN:-}" ]]; then
|
|
log "Using token from RUNNER_TOKEN"
|
|
printf '%s' "${RUNNER_TOKEN}"
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
resolve_keep_versions() {
|
|
local raw_value="${INPUT_KEEP_COUNT:-2}"
|
|
|
|
if [[ -z "${raw_value}" ]]; then
|
|
raw_value="2"
|
|
fi
|
|
|
|
if [[ ! "${raw_value}" =~ ^[0-9]+$ ]]; then
|
|
fail "Invalid keep_count: ${raw_value}"
|
|
fi
|
|
|
|
printf '%s' "${raw_value}"
|
|
}
|
|
|
|
resolve_package_names() {
|
|
local raw_value="${INPUT_PACKAGE_NAMES:-}"
|
|
local normalized package_name
|
|
local -a package_name_list
|
|
|
|
if [[ -z "${raw_value}" ]]; then
|
|
fail "Missing PACKAGE_NAMES"
|
|
fi
|
|
|
|
normalized="${raw_value//$'\n'/,}"
|
|
|
|
IFS=',' read -r -a package_name_list <<< "${normalized}"
|
|
for package_name in "${package_name_list[@]}"; do
|
|
package_name="${package_name#"${package_name%%[![:space:]]*}"}"
|
|
package_name="${package_name%"${package_name##*[![:space:]]}"}"
|
|
[[ -n "${package_name}" ]] || continue
|
|
TARGET_PACKAGES["${package_name}"]=1
|
|
done
|
|
|
|
if (( ${#TARGET_PACKAGES[@]} == 0 )); then
|
|
fail "Missing PACKAGE_NAMES"
|
|
fi
|
|
}
|
|
|
|
init_repo_context() {
|
|
local repository="${GITEA_REPOSITORY:-}"
|
|
|
|
if [[ -z "${repository}" || "${repository}" != */* ]]; then
|
|
fail "Invalid GITEA_REPOSITORY: ${repository:-<empty>}"
|
|
fi
|
|
|
|
REPO_OWNER="${repository%%/*}"
|
|
REPO_NAME="${repository#*/}"
|
|
}
|
|
|
|
api_request() {
|
|
local method="$1"
|
|
local path="$2"
|
|
local url body_file headers_file
|
|
|
|
url="${GITEA_SERVER_URL%/}${path}"
|
|
body_file="$(mktemp)"
|
|
headers_file="$(mktemp)"
|
|
|
|
if ! API_HTTP_CODE="$(
|
|
curl -sS \
|
|
-H "Accept: application/json" \
|
|
-H "Authorization: token ${RESOLVED_GITEA_TOKEN}" \
|
|
-X "${method}" \
|
|
-D "${headers_file}" \
|
|
-o "${body_file}" \
|
|
-w '%{http_code}' \
|
|
"${url}"
|
|
)"; then
|
|
rm -f "${body_file}" "${headers_file}"
|
|
fail "Request failed: ${method} ${path}"
|
|
fi
|
|
|
|
API_RESPONSE_BODY="$(cat "${body_file}")"
|
|
API_RESPONSE_HEADERS="$(cat "${headers_file}")"
|
|
API_STATUS_TEXT="$(head -n 1 "${headers_file}" | tr -d '\r' | cut -d' ' -f2-)"
|
|
API_REQUEST_ID="$(
|
|
grep -iE '^(x-gitea-request-id|x-request-id):' "${headers_file}" | tail -n 1 | cut -d':' -f2- | tr -d '\r' | sed 's/^ *//'
|
|
)" || true
|
|
rm -f "${body_file}" "${headers_file}"
|
|
|
|
if [[ -n "${API_REQUEST_ID}" ]]; then
|
|
log "${method} ${path} -> ${API_STATUS_TEXT} request_id=${API_REQUEST_ID}"
|
|
else
|
|
log "${method} ${path} -> ${API_STATUS_TEXT}"
|
|
fi
|
|
[[ "${API_HTTP_CODE}" =~ ^2 ]]
|
|
}
|
|
|
|
fetch_all_pages() {
|
|
local base_path="$1"
|
|
local page=1
|
|
local limit=100
|
|
local aggregate_file page_file tmp_file page_path page_length
|
|
|
|
aggregate_file="$(mktemp)"
|
|
printf '[]' > "${aggregate_file}"
|
|
|
|
while :; do
|
|
page_path="${base_path}"
|
|
if [[ "${page_path}" == *\?* ]]; then
|
|
page_path="${page_path}&page=${page}&limit=${limit}"
|
|
else
|
|
page_path="${page_path}?page=${page}&limit=${limit}"
|
|
fi
|
|
|
|
if ! api_request GET "${page_path}"; then
|
|
rm -f "${aggregate_file}"
|
|
fail "Unexpected response for ${page_path}"
|
|
fi
|
|
|
|
page_file="$(mktemp)"
|
|
printf '%s' "${API_RESPONSE_BODY}" > "${page_file}"
|
|
page_length="$(jq 'length' "${page_file}")"
|
|
|
|
tmp_file="$(mktemp)"
|
|
jq -s '.[0] + .[1]' "${aggregate_file}" "${page_file}" > "${tmp_file}"
|
|
mv "${tmp_file}" "${aggregate_file}"
|
|
rm -f "${page_file}"
|
|
|
|
if (( page_length < limit )); then
|
|
break
|
|
fi
|
|
|
|
page=$((page + 1))
|
|
done
|
|
|
|
cat "${aggregate_file}"
|
|
rm -f "${aggregate_file}"
|
|
}
|
|
|
|
collect_package_candidates() {
|
|
local packages_json group_json package_name total_versions candidates_json
|
|
|
|
packages_json="$(
|
|
fetch_all_pages "/api/v1/packages/${REPO_OWNER}?type=nuget"
|
|
)"
|
|
|
|
if [[ "$(jq 'length' <<<"${packages_json}")" -eq 0 ]]; then
|
|
log "No nuget packages found for owner ${REPO_OWNER}"
|
|
return 0
|
|
fi
|
|
|
|
while IFS= read -r group_json; do
|
|
package_name="$(jq -r '.[0].name' <<<"${group_json}")"
|
|
|
|
if [[ -z "${TARGET_PACKAGES["$package_name"]+x}" ]]; then
|
|
continue
|
|
fi
|
|
|
|
total_versions="$(jq 'length' <<<"${group_json}")"
|
|
|
|
PACKAGE_COUNT=$((PACKAGE_COUNT + 1))
|
|
TOTAL_VERSION_COUNT=$((TOTAL_VERSION_COUNT + total_versions))
|
|
|
|
log "Package ${package_name}: total_versions=${total_versions} keep_count=${keep_versions}"
|
|
log "Package ${package_name} versions (oldest -> newest):"
|
|
while IFS=$'\t' read -r version created_at; do
|
|
[[ -z "${version}" ]] && continue
|
|
log " - ${version} (${created_at})"
|
|
done < <(jq -r 'sort_by(.created_at)[] | [.version, .created_at] | @tsv' <<<"${group_json}")
|
|
|
|
if (( total_versions <= keep_versions )); then
|
|
log " keep all ${total_versions} versions"
|
|
KEPT_COUNT=$((KEPT_COUNT + total_versions))
|
|
continue
|
|
fi
|
|
|
|
KEPT_COUNT=$((KEPT_COUNT + keep_versions))
|
|
candidates_json="$(
|
|
jq -c --argjson keep "${keep_versions}" \
|
|
'sort_by(.created_at) | .[0:(length - $keep)]' <<<"${group_json}"
|
|
)"
|
|
|
|
while IFS=$'\t' read -r name version created_at; do
|
|
[[ -z "${name}" ]] && continue
|
|
log "Candidate to delete: package ${name} version ${version} (created: ${created_at})"
|
|
printf '%s\t%s\t%s\n' "${name}" "${version}" "${created_at}" >> "${CANDIDATES_FILE}"
|
|
CANDIDATE_COUNT=$((CANDIDATE_COUNT + 1))
|
|
done < <(jq -r '.[] | [.name, .version, .created_at] | @tsv' <<<"${candidates_json}")
|
|
done < <(jq -c 'group_by(.name)[]' <<<"${packages_json}")
|
|
}
|
|
|
|
process_candidates() {
|
|
local name version created_at
|
|
local deleted_count=0
|
|
local error_count=0
|
|
|
|
if [[ ! -s "${CANDIDATES_FILE}" ]]; then
|
|
log "No delete candidates found"
|
|
log "Summary: packages=${PACKAGE_COUNT} versions=${TOTAL_VERSION_COUNT} kept=${KEPT_COUNT} candidates=0 deleted=0 errors=0"
|
|
return 0
|
|
fi
|
|
|
|
while IFS=$'\t' read -r name version created_at; do
|
|
[[ -z "${name}" ]] && continue
|
|
|
|
if api_request DELETE "/api/v1/packages/${REPO_OWNER}/nuget/${name}/${version}"; then
|
|
log "Deleted package ${name} version ${version} -> ${API_STATUS_TEXT}"
|
|
deleted_count=$((deleted_count + 1))
|
|
else
|
|
if [[ -n "${API_REQUEST_ID}" ]]; then
|
|
log "ERROR: DELETE package ${name} version ${version} -> ${API_STATUS_TEXT} request_id=${API_REQUEST_ID}"
|
|
else
|
|
log "ERROR: DELETE package ${name} version ${version} -> ${API_STATUS_TEXT}"
|
|
fi
|
|
error_count=$((error_count + 1))
|
|
fi
|
|
done < "${CANDIDATES_FILE}"
|
|
|
|
log "Summary: packages=${PACKAGE_COUNT} versions=${TOTAL_VERSION_COUNT} kept=${KEPT_COUNT} candidates=${CANDIDATE_COUNT} deleted=${deleted_count} errors=${error_count}"
|
|
}
|
|
|
|
main() {
|
|
local token keep_versions
|
|
|
|
log "Gitea Server Url: ${GITEA_SERVER_URL:-}"
|
|
log "Gitea Repository: ${GITEA_REPOSITORY:-}"
|
|
|
|
if ! token="$(resolve_token)"; then
|
|
fail "No Gitea token available, exiting"
|
|
fi
|
|
|
|
export RESOLVED_GITEA_TOKEN="$token"
|
|
init_repo_context
|
|
keep_versions="$(resolve_keep_versions)"
|
|
log "keep_count=${keep_versions}"
|
|
resolve_package_names
|
|
log "package_names=${INPUT_PACKAGE_NAMES}"
|
|
log "Token source resolved successfully"
|
|
|
|
CANDIDATES_FILE="$(mktemp)"
|
|
export CANDIDATES_FILE
|
|
PACKAGE_COUNT=0
|
|
TOTAL_VERSION_COUNT=0
|
|
KEPT_COUNT=0
|
|
CANDIDATE_COUNT=0
|
|
trap 'rm -f "${CANDIDATES_FILE}"' EXIT
|
|
|
|
collect_package_candidates
|
|
if (( PACKAGE_COUNT == 0 )); then
|
|
log "No matching packages found for requested package_names"
|
|
fi
|
|
process_candidates
|
|
log "Stage 4 complete"
|
|
}
|
|
|
|
main "$@"
|