diff --git a/Dockerfile b/Dockerfile index f75f077..f6383ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -RUN apk add --no-cache --no-check-certificate bash curl ca-certificates +RUN apk add --no-cache --no-check-certificate bash curl jq ca-certificates COPY entrypoint.sh /entrypoint.sh diff --git a/entrypoint.sh b/entrypoint.sh index 3f81e07..101f28a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -47,6 +47,130 @@ resolve_keep_versions() { printf '%s' "${raw_value}" } +init_repo_context() { + local repository="${GITEA_REPOSITORY:-}" + + if [[ -z "${repository}" || "${repository}" != */* ]]; then + fail "Invalid GITEA_REPOSITORY: ${repository:-}" + 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}")" + rm -f "${body_file}" "${headers_file}" + + log "${method} ${path} -> ${API_HTTP_CODE}" + [[ "${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}")" + total_versions="$(jq 'length' <<<"${group_json}")" + + 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" + continue + fi + + 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}" + done < <(jq -r '.[] | [.name, .version, .created_at] | @tsv' <<<"${candidates_json}") + done < <(jq -c 'group_by(.name)[]' <<<"${packages_json}") +} + main() { local token keep_versions @@ -58,10 +182,17 @@ main() { fi export RESOLVED_GITEA_TOKEN="$token" + init_repo_context keep_versions="$(resolve_keep_versions)" log "keep_versions=${keep_versions}" log "Token source resolved successfully" - log "Stage 2 complete" + + CANDIDATES_FILE="$(mktemp)" + export CANDIDATES_FILE + trap 'rm -f "${CANDIDATES_FILE}"' EXIT + + collect_package_candidates + log "Stage 3 complete" } main "$@"