From 48b6e469066e8b6d05c8c58a9d536cf37c7580a5 Mon Sep 17 00:00:00 2001 From: Raoul Date: Fri, 24 Jul 2026 13:48:01 +0000 Subject: [PATCH] Fold GitHub release steps into deploy.sh Add release-draft, release-cut, and release-abort subcommands so the release lifecycle (draft the GitHub release, publish it, delete the draft on failure) lives in the same single source of truth as the cache and Docker deploy steps. The Release workflow's draft-release, cut-release, and failure-cleanup steps now call these subcommands instead of inlining gh, and the CONTRIBUTING.md runbook uses them too. The runbook also now states that a manual release must be built from a commit on main, since kup resolves komet-node versions against main. --- .github/workflows/release.yml | 45 +++++++++------------------ CONTRIBUTING.md | 29 ++++++++++-------- scripts/deploy.sh | 57 +++++++++++++++++++++++++++++------ 3 files changed, 79 insertions(+), 52 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7860a35..2017bb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,19 +12,14 @@ jobs: steps: - name: 'Check out code' uses: actions/checkout@v4 - - name: 'Make release' - id: 'make-release' + # Drafting logic lives in scripts/deploy.sh so CI and manual releases stay + # in sync (single source of truth). See CONTRIBUTING.md. + - name: 'Draft the release' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -x - VERSION=v$(cat package/version) - gh release create "${VERSION}" \ - --repo "${GITHUB_REPOSITORY}" \ - --draft \ - --title "${VERSION}" \ - --target ${{ github.sha }} - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + OWNER_REPO: '${{ github.repository }}' + REV: '${{ github.sha }}' + run: ./scripts/deploy.sh release-draft nix-cache: name: 'Populate Nix Caches' @@ -55,13 +50,8 @@ jobs: if: failure() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -x - VERSION=v$(cat package/version) - gh release delete "${VERSION}" \ - --repo "${GITHUB_REPOSITORY}" \ - --yes \ - --cleanup-tag || true + OWNER_REPO: '${{ github.repository }}' + run: ./scripts/deploy.sh release-abort dockerhub: name: 'Build and Publish Docker Image' @@ -85,13 +75,8 @@ jobs: if: failure() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -x - VERSION=v$(cat package/version) - gh release delete "${VERSION}" \ - --repo "${GITHUB_REPOSITORY}" \ - --yes \ - --cleanup-tag || true + OWNER_REPO: '${{ github.repository }}' + run: ./scripts/deploy.sh release-abort cut-release: name: 'Cut Release' @@ -104,12 +89,10 @@ jobs: ref: ${{ github.sha }} fetch-depth: 0 + # Finalizing logic lives in scripts/deploy.sh so CI and manual releases + # stay in sync (single source of truth). See CONTRIBUTING.md. - name: 'Finalize Release' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -x - VERSION=v$(cat package/version) - gh release edit "${VERSION}" \ - --repo "${GITHUB_REPOSITORY}" \ - --draft=false + OWNER_REPO: '${{ github.repository }}' + run: ./scripts/deploy.sh release-cut diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5976a91..1637cd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,12 +79,14 @@ where `` comes from `package/version`. ### Prerequisites -Both subcommands run from a checkout of the revision you are releasing, with the +The subcommands run from a checkout of the revision you are releasing, with the following tools on `PATH`: - `nix-cache` needs `nix` and `cachix`. It fetches `kup` from `github:runtimeverification/kup` itself, so you do not install kup separately. - `docker` needs `docker`. +- `release-draft`, `release-cut`, and `release-abort` need `gh`, authenticated + via `gh auth login` or a `GH_TOKEN`/`GITHUB_TOKEN` in the environment. Each subcommand checks its tools up front and exits with a clear message if one is missing, before starting the build. @@ -102,14 +104,16 @@ further setup. | ----------- | ------------------------------ | | `nix-cache` | `CACHIX_PUBLIC_TOKEN`, `CACHIX_PRIVATE_KFB_TOKEN` | | `docker` | `DOCKERHUB_PASSWORD` | +| `release-*` | none — `gh` supplies its own authentication | `OWNER_REPO` and `REV` default to the current checkout's `origin` remote and `HEAD`. Override them if you are publishing a revision other than the one checked out. `DOCKERHUB_USERNAME`, `DOCKERHUB_NAMESPACE`, and `DOCKERHUB_REPO` default to the release account and repository; override them only to publish elsewhere. -Both subcommands refuse to run if the working tree is not clean. CI builds from a -fresh checkout, so a manual build must too: uncommitted changes and untracked +The `nix-cache` and `docker` subcommands refuse to run if the working tree is not +clean. CI builds from a fresh checkout, so a manual build must too: uncommitted +changes and untracked files would otherwise be baked into the published image or flake build without matching the `REV` they are published under. Commit, stash, or clean the tree first. `git status --porcelain` ignores `.gitignore`d paths, so runtime artifacts @@ -118,17 +122,18 @@ deliberately want to publish an uncommitted state. ### Manual release while CI is down -The workflow also drafts, cleans up, and finalizes the GitHub release around the -deployment jobs. When you deploy by hand, run those `gh` steps yourself in this -order. `nix-cache` runs once per architecture (the workflow uses an `x86_64` and -an `ARM64` runner), so run it on a machine of each architecture that should be +`deploy.sh` wraps the GitHub-release steps too, so a manual release runs the same +subcommands the workflow does, in the same order. Run them from a checkout of the +`main` commit you are releasing: `release-draft` targets `REV` (the current +`HEAD` by default), and kup resolves komet-node versions against `main`, so a +release built from an unmerged commit will not be installable with `kup`. +`nix-cache` runs once per architecture (the workflow uses an `x86_64` and an +`ARM64` runner), so run it on a machine of each architecture that should be cached. ```sh -VERSION=v$(cat package/version) - # 1. Draft the release. -gh release create "$VERSION" --draft --title "$VERSION" --target "$(git rev-parse HEAD)" +./scripts/deploy.sh release-draft # 2. Deploy. Export the secrets first (see the table above). # Run nix-cache once per architecture; docker once. @@ -136,8 +141,8 @@ gh release create "$VERSION" --draft --title "$VERSION" --target "$(git rev-pars ./scripts/deploy.sh docker # 3. Finalize the release once every deployment has succeeded. -gh release edit "$VERSION" --draft=false +./scripts/deploy.sh release-cut # If a deployment fails, remove the draft instead: -# gh release delete "$VERSION" --yes --cleanup-tag +# ./scripts/deploy.sh release-abort ``` diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 2e3bcd5..27cab2f 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -13,13 +13,16 @@ set -euo pipefail # unavailable. See CONTRIBUTING.md ("Deploying a release") for the runbook. # # Subcommands: -# nix-cache Build komet-node and push it to both Nix caches (the -# k-framework closure + the k-framework-binary kup binary). -# docker Build the Docker image, smoke-test it, and push to Docker Hub. -# all Run nix-cache then docker. +# nix-cache Build komet-node and push it to both Nix caches (the +# k-framework closure + the k-framework-binary kup binary). +# docker Build the Docker image, smoke-test it, and push to Docker Hub. +# all Run nix-cache then docker. +# release-draft Create the draft GitHub release for this version and revision. +# release-cut Publish (un-draft) the release once its artifacts are up. +# release-abort Delete the draft release and its tag (failure cleanup). # # Usage: -# scripts/deploy.sh +# scripts/deploy.sh REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${REPO_ROOT}" @@ -156,15 +159,51 @@ deploy_docker() { docker image push "${tag}" } +# --- release lifecycle ----------------------------------------------------- +# The GitHub release is drafted before the artifacts are published and finalized +# after, so it only becomes visible once its Docker image and cache pins exist. +# These wrap the `gh` calls the Release workflow makes, keeping the release +# lifecycle in the same single source of truth as the deploy steps. `gh` +# authenticates via its own login or GH_TOKEN/GITHUB_TOKEN; no secret is read +# here directly. The release is drafted against REV, which must be the commit you +# deployed -- and, for a real release, a commit on `main`, since kup resolves +# komet-node versions against `main`. +release_tag() { echo "v${KOMET_NODE_VERSION}"; } + +deploy_release_draft() { + require_cmd gh git + local tag; tag="$(release_tag)" + echo ":: drafting release ${tag} targeting ${REV}" + gh release create "${tag}" --repo "${OWNER_REPO}" --draft --title "${tag}" --target "${REV}" +} + +deploy_release_cut() { + require_cmd gh git + local tag; tag="$(release_tag)" + echo ":: publishing release ${tag}" + gh release edit "${tag}" --repo "${OWNER_REPO}" --draft=false +} + +deploy_release_abort() { + require_cmd gh git + local tag; tag="$(release_tag)" + echo ":: deleting drafted release ${tag}" + # Best-effort: the cleanup path must never fail on its own account. + gh release delete "${tag}" --repo "${OWNER_REPO}" --yes --cleanup-tag || true +} + usage() { - sed -n '8,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' + sed -n '8,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' } main() { case "${1:-}" in - nix-cache) deploy_nix_cache ;; - docker) deploy_docker ;; - all) deploy_nix_cache; deploy_docker ;; + nix-cache) deploy_nix_cache ;; + docker) deploy_docker ;; + all) deploy_nix_cache; deploy_docker ;; + release-draft) deploy_release_draft ;; + release-cut) deploy_release_cut ;; + release-abort) deploy_release_abort ;; -h | --help | help | "") usage [ -n "${1:-}" ] # exit non-zero when no subcommand was given