From aab7d56f147660c8d435ff4e10e440ca01bb298f Mon Sep 17 00:00:00 2001 From: Dominik Schubert Date: Thu, 3 Sep 2026 19:49:38 +0200 Subject: [PATCH] Gate release tagging behind a Windows smoke-test approval GitHub-hosted Windows runners cannot run Linux containers, so CI never exercises lstk against a real Docker daemon on Windows. Both release workflows now bind the tag job to the `release` GitHub environment, which waits for a required reviewer to confirm the manual Windows smoke test. - Print the commit to test in the run summary of the job *before* the gate, so reviewers see it while the tag job is still waiting. - Pin the tag job to the SHA that was tested instead of re-resolving `main` after the approval wait. - Document the smoke test and approval flow in docs/RELEASING.md. Refs DEVX-1042. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/automated-release.yml | 29 +++++++++++++- .github/workflows/create-release-tag.yml | 44 ++++++++++++++++++++- docs/RELEASING.md | 50 +++++++++++++++++++++++- 3 files changed, 119 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index 42df8c92..b0ba5452 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -21,6 +21,7 @@ jobs: runs-on: ubuntu-latest outputs: has_changes: ${{ steps.check.outputs.has_changes }} + sha: ${{ steps.check.outputs.sha }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -37,6 +38,10 @@ jobs: run: | set -euo pipefail + # Pin the release candidate. create-tag checks out this exact commit + # after the approval wait, so main moving in the meantime is harmless. + echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}" + latest_tag="$(git tag --list "v*.*.*" --sort=-v:refname | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | head -n 1 || true)" if [[ -z "${latest_tag}" ]]; then echo "No previous tag found, proceeding with release" @@ -108,6 +113,23 @@ jobs: echo "Determined bump type: ${bump}" echo "bump=${bump}" >> "${GITHUB_OUTPUT}" + # Runs before the gated create-tag job so reviewers see what to test + # while that job is still waiting for approval. + - name: Print Windows smoke test checklist + env: + SHA: ${{ needs.check-changes.outputs.sha }} + BUMP: ${{ steps.bump.outputs.bump }} + run: | + { + echo "## Windows smoke test required" + echo + echo "\`Create release tag\` waits in the \`release\` environment until a reviewer approves." + echo "Approve only after the Windows smoke test in \`docs/RELEASING.md\` passed on this exact commit:" + echo + echo "- Commit: [\`${SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHA})" + echo "- Bump: \`${BUMP}\`" + } >> "${GITHUB_STEP_SUMMARY}" + ci: name: CI needs: check-changes @@ -120,11 +142,16 @@ jobs: needs: [check-changes, determine-bump, ci] if: needs.check-changes.outputs.has_changes == 'true' runs-on: ubuntu-latest + # Waits for a required reviewer. Approve only after the Windows smoke test + # in docs/RELEASING.md passed on the commit shown in the run summary. + environment: + name: release + url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.check-changes.outputs.sha }} steps: - name: Checkout code uses: actions/checkout@v7 with: - ref: main + ref: ${{ needs.check-changes.outputs.sha }} fetch-depth: 0 token: ${{ secrets.PRO_ACCESS_TOKEN }} diff --git a/.github/workflows/create-release-tag.yml b/.github/workflows/create-release-tag.yml index 38d74bb5..e1342c4a 100644 --- a/.github/workflows/create-release-tag.yml +++ b/.github/workflows/create-release-tag.yml @@ -23,14 +23,56 @@ concurrency: cancel-in-progress: false jobs: + resolve-ref: + name: Resolve release ref + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.resolve.outputs.sha }} + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + ref: ${{ inputs.release_ref }} + + # Pin the release candidate. create-tag checks out this exact commit + # after the approval wait, so the ref moving in the meantime is harmless. + - name: Resolve ref to commit + id: resolve + run: echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}" + + # Runs before the gated create-tag job so reviewers see what to test + # while that job is still waiting for approval. + - name: Print Windows smoke test checklist + env: + REF: ${{ inputs.release_ref }} + SHA: ${{ steps.resolve.outputs.sha }} + BUMP: ${{ inputs.bump }} + run: | + { + echo "## Windows smoke test required" + echo + echo "\`Create next tag\` waits in the \`release\` environment until a reviewer approves." + echo "Approve only after the Windows smoke test in \`docs/RELEASING.md\` passed on this exact commit:" + echo + echo "- Ref: \`${REF}\`" + echo "- Commit: [\`${SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHA})" + echo "- Bump: \`${BUMP}\`" + } >> "${GITHUB_STEP_SUMMARY}" + create-tag: name: Create next tag + needs: resolve-ref runs-on: ubuntu-latest + # Waits for a required reviewer. Approve only after the Windows smoke test + # in docs/RELEASING.md passed on the commit shown in the run summary. + environment: + name: release + url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve-ref.outputs.sha }} steps: - name: Checkout code uses: actions/checkout@v7 with: - ref: ${{ inputs.release_ref }} + ref: ${{ needs.resolve-ref.outputs.sha }} fetch-depth: 0 token: ${{ secrets.PRO_ACCESS_TOKEN }} diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 58ee6da3..257f1a94 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -16,9 +16,9 @@ Release automation uses three workflows: 2. `Create Release Tag` (`.github/workflows/create-release-tag.yml`) — manual-only. 3. `LSTK CI` (`.github/workflows/ci.yml`) -**Automated weekly release** (the default path): every Thursday, the workflow checks whether `main` has any commits since the last `v*.*.*` tag. If so, it inspects the labels on every PR merged since that tag, picks the highest release label found (`major` > `minor` > `patch`, defaulting to `patch`), runs full CI, then creates and pushes the next version tag — which in turn triggers `LSTK CI`'s `release` job below. If there are no changes since the last tag, it skips the release entirely. +**Automated weekly release** (the default path): every Thursday, the workflow checks whether `main` has any commits since the last `v*.*.*` tag. If so, it inspects the labels on every PR merged since that tag, picks the highest release label found (`major` > `minor` > `patch`, defaulting to `patch`), runs full CI, then waits for a reviewer to approve the [Windows smoke test](#windows-smoke-test-required-before-every-release). Once approved, it creates and pushes the next version tag — which in turn triggers `LSTK CI`'s `release` job below. If there are no changes since the last tag, it skips the release entirely. -**Manual release**: run `Create Release Tag` from GitHub Actions (default ref: `main`), choosing a `patch` or `minor` bump, when you need to cut a release outside the weekly schedule. +**Manual release**: run `Create Release Tag` from GitHub Actions (default ref: `main`), choosing a `patch` or `minor` bump, when you need to cut a release outside the weekly schedule. It waits for the same smoke-test approval before tagging. Either path pushes a version tag (e.g. `v0.2.4`), which triggers `LSTK CI`, running the `release` job to publish the GitHub release with GoReleaser. @@ -28,3 +28,49 @@ To validate release packaging locally without publishing: goreleaser release --snapshot --clean ``` +## Windows smoke test (required before every release) + +GitHub-hosted Windows runners cannot run Linux containers, so CI never exercises `lstk` against a real Docker daemon on Windows. Both release workflows therefore stop before tagging: the tag job is bound to the `release` GitHub environment and waits until one of its required reviewers approves. The reviewers are configured in the repository's environment settings. + +The run summary of the job before the gate (`Determine version bump` or `Resolve release ref`) shows the exact commit to test. The tag job later checks out that same commit, so `main` moving while the run waits does not change what gets released. + +Before approving: + +1. Build `lstk.exe` from the commit shown in the run summary, e.g. on the Windows machine: + + ```powershell + git fetch origin; git checkout + go build -o lstk.exe . + ``` + + Or cross-compile from macOS/Linux and copy the binary over: `GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o lstk.exe .` + +2. On Windows 11 with Docker Desktop running (the setup most users have) and the AWS CLI installed, run: + + ```powershell + .\lstk.exe start # container comes up + docker ps # note the emulator container name + docker inspect --format '{{json .HostConfig.Binds}}' # must include /var/run/docker.sock + .\lstk.exe logs # shows emulator output + + Set-Content handler.py 'def handler(event, context): return {"ok": True}' + Compress-Archive handler.py function.zip -Force + .\lstk.exe aws lambda create-function --function-name smoke ` + --runtime python3.12 --handler handler.handler ` + --role arn:aws:iam::000000000000:role/lambda-role --zip-file fileb://function.zip + .\lstk.exe aws lambda wait function-active-v2 --function-name smoke + .\lstk.exe aws lambda invoke --function-name smoke out.json; Get-Content out.json + + .\lstk.exe stop + ``` + + A successful invoke is the key check: Lambda spawns a sibling container through the mounted Docker socket, which is exactly what broke for Windows users before. + +3. In the run, open **Review deployments**, tick `release`, and paste the OS, Docker Desktop version and result into the comment. Approve on success. Reject on failure, fix on `main`, and start a new release run. + +Notes: + +- Both release workflows share one concurrency group. Approve or reject a pending run before triggering another release. +- A run that is not approved within 30 days fails. Re-trigger it manually. +- Repository admins cannot bypass the gate; the environment is configured without admin bypass. +