Files
NanoKVM-MIRROR/.github/workflows/release.yml

245 lines
7.9 KiB
YAML

name: NanoKVM Release
on:
workflow_dispatch:
inputs:
tag:
description: Existing numeric tag to release, e.g. 2.5.0
required: true
type: string
release_action:
description: Publish a release or promote a tested prerelease
required: true
type: choice
options:
- publish-prerelease
- publish-stable
- promote-stable
# Latest is repository-global, so all Release changes must be serialized.
concurrency:
group: nanokvm-github-release
cancel-in-progress: false
jobs:
validate:
name: Resolve release tag
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
source_sha: ${{ steps.tag.outputs.source_sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Verify tag
id: tag
env:
TAG: ${{ inputs.tag }}
run: |
SOURCE_SHA=$(./scripts/verify-release-tag.sh "$TAG")
echo "source_sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT"
package:
name: Build tag package
needs: validate
if: inputs.release_action != 'promote-stable'
permissions:
contents: read
packages: read
uses: ./.github/workflows/package.yml
with:
ref: ${{ needs.validate.outputs.source_sha }}
version: ${{ inputs.tag }}
publish:
name: Publish GitHub release
needs:
- validate
- package
if: inputs.release_action != 'promote-stable'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout release tooling
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Download package artifact
uses: actions/download-artifact@v4
with:
name: ${{ needs.package.outputs.artifact_name }}
path: build/release
- name: Publish release
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_ACTION: ${{ inputs.release_action }}
SOURCE_SHA: ${{ needs.validate.outputs.source_sha }}
TAG: ${{ inputs.tag }}
run: |
./scripts/verify-release-assets.sh build/release "$TAG"
case "$RELEASE_ACTION" in
publish-prerelease) IS_PRERELEASE=true ;;
publish-stable) IS_PRERELEASE=false ;;
*)
echo "::error::invalid publish action '$RELEASE_ACTION'"
exit 1
;;
esac
ENDPOINT="repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}"
set +e
HEADERS=$(gh api --include --silent "$ENDPOINT" 2>&1)
API_STATUS=$?
set -e
HTTP_STATUS=$(printf '%s\n' "$HEADERS" | awk '
$1 ~ /^HTTP\// { status = $2 }
END { print status }
')
if [ "$API_STATUS" -eq 0 ] && [ "$HTTP_STATUS" = 200 ]; then
echo "::error::release or draft '$TAG' already exists"
echo "Publishing is one-shot. Inspect it at https://github.com/${GITHUB_REPOSITORY}/releases."
exit 1
fi
if [ "$API_STATUS" -eq 0 ] || [ "$HTTP_STATUS" != 404 ]; then
echo "::error::failed to query release '$TAG' (HTTP status: ${HTTP_STATUS:-unknown})"
printf '%s\n' "$HEADERS" >&2
exit 1
fi
TARBALL="build/release/nanokvm_${TAG}.tar.gz"
SHA256=$(sha256sum "$TARBALL" | cut -d' ' -f1)
SHA512=$(jq -er '.sha512' build/release/latest.json)
# Backticks below are intentional Markdown, not command substitution.
# shellcheck disable=SC2016
NOTES=$(printf '%s\n' \
"Application package for NanoKVM ${TAG}." \
'' \
'### Checksums' \
'' \
'`SHA-256` (hex):' \
'' \
" ${SHA256}" \
'' \
'`SHA-512` (base64, as expected by the on-device updater):' \
'' \
" ${SHA512}" \
'' \
'To offer this build over OTA, publish it through the separate CDN workflow.')
STATE_ARGS=(--prerelease="$IS_PRERELEASE")
if [ "$IS_PRERELEASE" = true ]; then
STATE_ARGS+=(--latest=false)
fi
./scripts/verify-release-tag.sh "$TAG" "$SOURCE_SHA" >/dev/null
gh release create "$TAG" \
--verify-tag \
--title "$TAG" \
--notes "$NOTES" \
"${STATE_ARGS[@]}" \
"$TARBALL" build/release/latest.json build/release/sha256.txt
- name: Summary
env:
RELEASE_ACTION: ${{ inputs.release_action }}
SOURCE_SHA: ${{ needs.validate.outputs.source_sha }}
TAG: ${{ inputs.tag }}
run: |
{
echo '### NanoKVM GitHub Release published'
echo
echo "- Tag: \`$TAG\`"
echo "- Commit: \`$SOURCE_SHA\`"
echo "- Action: \`$RELEASE_ACTION\`"
echo
echo 'No CDN object was uploaded.'
} >> "$GITHUB_STEP_SUMMARY"
promote:
name: Promote verified prerelease
needs: validate
if: inputs.release_action == 'promote-stable'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout release tooling
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Verify and promote release
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SOURCE_SHA: ${{ needs.validate.outputs.source_sha }}
TAG: ${{ inputs.tag }}
run: |
RELEASE_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}")
if [ "$(jq -r '.draft' <<< "$RELEASE_JSON")" != false ]; then
echo "::error::release '$TAG' is a draft"
exit 1
fi
if [ "$(jq -r '.prerelease' <<< "$RELEASE_JSON")" != true ]; then
echo "::error::release '$TAG' is not a prerelease"
exit 1
fi
CURRENT_ASSETS=$(jq -r '.assets[].name' <<< "$RELEASE_JSON" | LC_ALL=C sort)
EXPECTED_ASSETS=$(printf '%s\n' \
latest.json "nanokvm_${TAG}.tar.gz" sha256.txt | LC_ALL=C sort)
if [ "$CURRENT_ASSETS" != "$EXPECTED_ASSETS" ]; then
echo "::error::release '$TAG' does not have the exact expected asset set"
exit 1
fi
mkdir -p build/release
gh release download "$TAG" --dir build/release \
--pattern "nanokvm_${TAG}.tar.gz" \
--pattern latest.json \
--pattern sha256.txt
./scripts/verify-release-assets.sh build/release "$TAG"
./scripts/verify-release-tag.sh "$TAG" "$SOURCE_SHA" >/dev/null
gh release edit "$TAG" --prerelease=false --latest
./scripts/verify-release-tag.sh "$TAG" "$SOURCE_SHA" >/dev/null
if [ "$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" --jq '.prerelease')" != false ]; then
echo "::error::release '$TAG' was not promoted"
exit 1
fi
if [ "$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq '.tag_name')" != "$TAG" ]; then
echo "::error::release '$TAG' was not marked latest"
exit 1
fi
- name: Summary
env:
SOURCE_SHA: ${{ needs.validate.outputs.source_sha }}
TAG: ${{ inputs.tag }}
run: |
{
echo '### NanoKVM prerelease promoted'
echo
echo "- Tag: \`$TAG\`"
echo "- Commit: \`$SOURCE_SHA\`"
echo '- Assets: unchanged and checksum-verified'
echo
echo 'No package was rebuilt and no CDN object was uploaded.'
} >> "$GITHUB_STEP_SUMMARY"