#!/usr/bin/env bash # Generate a package changelog for a freshly built image and publish it as a # Gitea release (instead of committing JSON files under changelogs/). # # Usage: release.sh # # The full package list is attached to each release as # packages--.txt # and the previous release's asset is used as the diff baseline. set -euo pipefail if [[ $# -lt 4 ]]; then echo "Usage: $0 " >&2 exit 1 fi IMAGE="$1" DISTRO="$2" BUILDID="$3" PACKAGES_FILE="$4" GITEA_URL="${GITEA_URL:-https://git.plabble.org}" GITEA_REPO="${GITEA_REPO:-Misthios/bootc-images}" TOKEN="${RELEASE_TOKEN:-${GITHUB_TOKEN:-}}" [[ -n "${TOKEN}" ]] || { echo "RELEASE_TOKEN (or GITHUB_TOKEN) is required" >&2; exit 1; } [[ -f "${PACKAGES_FILE}" ]] || { echo "Missing package list: ${PACKAGES_FILE}" >&2; exit 1; } API="${GITEA_URL%/}/api/v1/repos/${GITEA_REPO}" RELEASE_TAG="${IMAGE}-${DISTRO}.${BUILDID}" ASSET_NAME="packages-${IMAGE}-${DISTRO}.txt" RELEASE_TITLE="${IMAGE} ${DISTRO} ${BUILDID}" WORK="$(mktemp -d)" trap 'rm -rf "${WORK}"' EXIT # Idempotency: never create the same release twice (e.g. a re-run). if curl -fsSL -H "Authorization: token ${TOKEN}" \ "${API}/releases/tags/${RELEASE_TAG}" >/dev/null 2>&1; then echo "Release ${RELEASE_TAG} already exists; nothing to do." exit 0 fi # --- package list from the build ------------------------------------------- echo "Using package list ${PACKAGES_FILE} ..." cp "${PACKAGES_FILE}" "${WORK}/packages-current.txt" # --- previous package list from the last release --------------------------- # Walk the release list (newest first) until we find the newest release for # this image/distro. prev_release_id="" page=1 while :; do page_json="$(curl -fsSL -H "Authorization: token ${TOKEN}" \ "${API}/releases?limit=50&page=${page}")" [[ "$(jq 'length' <<< "${page_json}")" -eq 0 ]] && break prev_release_id="$(jq -r --arg prefix "${IMAGE}-${DISTRO}." \ '.[] | select(.tag_name | startswith($prefix)) | .id' <<< "${page_json}" \ | head -n1)" [[ -n "${prev_release_id}" ]] && break page=$((page + 1)) [[ "${page}" -gt 20 ]] && break done if [[ -n "${prev_release_id}" ]]; then asset_url="$(curl -fsSL -H "Authorization: token ${TOKEN}" \ "${API}/releases/${prev_release_id}" \ | jq -r --arg name "${ASSET_NAME}" \ '.assets[] | select(.name == $name) | .browser_download_url' \ | head -n1)" if [[ -n "${asset_url}" && "${asset_url}" != "null" ]]; then curl -fsSL -H "Authorization: token ${TOKEN}" "${asset_url}" \ -o "${WORK}/packages-previous.txt" || true fi fi # First ever build: baseline against itself so nothing shows up as removed. [[ -f "${WORK}/packages-previous.txt" ]] \ || cp "${WORK}/packages-current.txt" "${WORK}/packages-previous.txt" # --- diff ------------------------------------------------------------------- CUR_NAMES="$(mktemp)" PREV_NAMES="$(mktemp)" cut -d' ' -f1 "${WORK}/packages-current.txt" | sort > "${CUR_NAMES}" cut -d' ' -f1 "${WORK}/packages-previous.txt" | sort > "${PREV_NAMES}" added_names="$(comm -13 "${PREV_NAMES}" "${CUR_NAMES}" || true)" removed_names="$(comm -23 "${PREV_NAMES}" "${CUR_NAMES}" || true)" common_names="$(comm -12 "${PREV_NAMES}" "${CUR_NAMES}" || true)" ver_of() { awk -v p="$1" '$1 == p {print $2}' "$2"; } added=() while read -r n; do [[ -z "${n}" ]] && continue added+=("${n} $(ver_of "${n}" "${WORK}/packages-current.txt")") done <<< "${added_names}" removed=() while read -r n; do [[ -z "${n}" ]] && continue removed+=("${n} $(ver_of "${n}" "${WORK}/packages-previous.txt")") done <<< "${removed_names}" updated=() while read -r n; do [[ -z "${n}" ]] && continue old="$(ver_of "${n}" "${WORK}/packages-previous.txt")" new="$(ver_of "${n}" "${WORK}/packages-current.txt")" [[ -z "${old}" || -z "${new}" ]] && continue [[ "${old}" != "${new}" ]] && updated+=("${n} ${old} -> ${new}") done <<< "${common_names}" { echo "Automated build of \`${IMAGE}\` \`${DISTRO}\` (\`${BUILDID}\`)." echo echo "- Added: ${#added[@]}" echo "- Removed: ${#removed[@]}" echo "- Updated: ${#updated[@]}" echo if (( ${#added[@]} )); then echo "### Added" printf -- '- %s\n' "${added[@]}" echo fi if (( ${#removed[@]} )); then echo "### Removed" printf -- '- %s\n' "${removed[@]}" echo fi if (( ${#updated[@]} )); then echo "### Updated" printf -- '- %s\n' "${updated[@]}" echo fi } > "${WORK}/changelog.md" # --- create the release ----------------------------------------------------- echo "Creating release ${RELEASE_TAG} ..." payload="$(jq -n \ --arg tag "${RELEASE_TAG}" \ --arg name "${RELEASE_TITLE}" \ --arg body "$(cat "${WORK}/changelog.md")" \ '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')" response="$(curl -fsSL -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ -d "${payload}" \ "${API}/releases")" release_id="$(jq -r '.id' <<< "${response}")" echo "Uploading ${ASSET_NAME} ..." curl -fsSL -X POST \ -H "Authorization: token ${TOKEN}" \ -F "attachment=@${WORK}/packages-current.txt" \ "${API}/releases/${release_id}/assets?name=${ASSET_NAME}" echo "Published release ${RELEASE_TAG}." # --- prune older releases (and their assets/tags) for this image/distro ----- echo "Pruning older ${IMAGE} ${DISTRO} releases ..." old_releases="" page=1 while :; do page_json="$(curl -fsSL -H "Authorization: token ${TOKEN}" \ "${API}/releases?limit=50&page=${page}")" [[ "$(jq 'length' <<< "${page_json}")" -eq 0 ]] && break old_releases+="$(jq -r --arg prefix "${IMAGE}-${DISTRO}." --arg keep "${release_id}" \ '.[] | select(.tag_name | startswith($prefix)) | select((.id | tostring) != $keep) | "\(.id)\t\(.tag_name)"' \ <<< "${page_json}")"$'\n' page=$((page + 1)) [[ "${page}" -gt 20 ]] && break done while IFS=$'\t' read -r old_id old_tag; do [[ -z "${old_id}" ]] && continue echo " removing release ${old_tag}" curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \ "${API}/releases/${old_id}" >/dev/null || true curl -fsSL -X DELETE -H "Authorization: token ${TOKEN}" \ "${API}/tags/${old_tag}" >/dev/null || true done <<< "${old_releases}" echo "Done."