Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/automated-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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 }}

Expand Down
44 changes: 43 additions & 1 deletion .github/workflows/create-release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}

Expand Down
50 changes: 48 additions & 2 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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 <sha>
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 <container> --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.

Loading