Release pipeline: tag-triggered goreleaser to GitHub Releases + GHCR#4
Merged
Conversation
Mirrors mcp-test's release setup. Closes the M5-deferred gap that left
v1.x tags producing nothing on push.
`.github/workflows/release.yml` fires on tags matching `v*`, sets up Go
1.26.3 + Node 22 + pnpm 10 (so the embedded SPA is fresh in the
release binary), logs into GHCR with the workflow token, and runs
goreleaser. All third-party actions pinned to commit SHAs. Permissions
empty at the workflow level; the release job grants only contents:write
+ packages:write + id-token:write.
`.goreleaser.yml` (v2) builds darwin/{amd64,arm64} + linux/{amd64,arm64}
+ windows/amd64 with -trimpath -s -w and version metadata stamped via
-ldflags. Multi-arch GHCR image at ghcr.io/plexara/api-test, tagged
:latest, :{Tag}, :v{Major}. Changelog uses GitHub PR titles grouped by
feat/fix/security/others. The SPA before-hook builds ui/ via pnpm if
present so the embedded portal ships in every release artifact.
Dockerfile rewritten from a self-building distroless image to scratch +
ca-certs + a goreleaser-staged binary at linux/${TARGETARCH}/api-test.
The previous in-stage build path was mismatched against the existing
`make docker` target, which already pre-stages a binary the way
goreleaser does. HEALTHCHECK preserved (binary's --healthcheck flag).
`.dockerignore` matches mcp-test conventions. .gitignore adds /dist/
(goreleaser output) and /linux/ (`make docker` scratch dir).
Validated locally with `goreleaser check` and `goreleaser release
--snapshot --clean --skip=publish,docker,sign,before` — all 5
cross-platform binaries + archives + checksums produce in ~18s.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the M5-deferred gap that left
v*tag pushes producing nothing on GitHub. Mirrors the sister project's (mcp-test) release setup verbatim, adapted for api-test naming and the HTTP-fixture description.You pushed
v1.0.0, no Action ran, and there was norelease.ymlto run — that's what this fixes.What's in here
.github/workflows/release.ymlTrigger:
push: tags: v*. Singlereleasejob onubuntu-24.04, 30-min timeout. Sets up Go 1.26.3 + Node 22 + pnpm 10 (so the embedded SPA is fresh in the released binary), QEMU + Buildx for multi-arch image builds, logs into GHCR with the workflow'sGITHUB_TOKEN, then runs goreleaser.Hardening:
permissions: {}at workflow level. Job grants onlycontents: write(release upload),packages: write(GHCR push),id-token: write(cosign keyless when wired).fetch-depth: 0so goreleaser's changelog walks full history..goreleaser.yml(v2)Five-platform binary matrix:
darwin/{amd64,arm64},linux/{amd64,arm64},windows/amd64.windows/arm64ignored (no current consumer). Build flags:CGO_ENABLED=0 -trimpath -a -s -w. Version metadata stamped through-ldflags -Xagainstpkg/build.{Version,Commit,Date}, matching the Makefile's existing convention.mod_timestamp: '{{ .CommitTimestamp }}'for reproducible builds.Archives:
LICENSE+README.md{{.ProjectName}}_{{.Version}}_{{.Os}}_{{.Arch}}checksums.txt(sha256)Container image (
dockers_v2:):ghcr.io/plexara/api-testlatest,{{ .Tag }},v{{ .Major }}linux/amd64,linux/arm64extra_files: [LICENSE]so the OCI license file is in the build contextChangelog:
use: github(uses PR titles), grouped by regex into Features (^feat) / Bug Fixes (^fix) / Security (^security) / Others. Excludes^docs:,^test:,^chore:,^build:, merges.Release notes: prerelease auto-detect;
mode: appendso re-runs don't replace the body. Footer includes the docker pull command, the curl one-liner for binaries with auto-detectedos/arch, and a docs link.SPA before-hook:
corepack enable && pnpm install --frozen-lockfile && pnpm buildagainstui/, then copiesui/distintointernal/ui/distso the//go:embeddirective ininternal/uiships the up-to-date portal in every release artifact. Falls back tonpm install && npm run build. Skipped silently ifui/package.jsonis missing.Dockerfile— rewrittenPrevious Dockerfile did its own in-stage build (
WORKDIR /src; COPY . .; RUN go build). That conflicted with the existingmake dockertarget (d31fc12Makefile) which already pre-stages a binary intolinux/amd64/api-testbefore invoking buildx — exactly the layout goreleaser uses. The Dockerfile was the odd one out.New Dockerfile (mcp-test pattern):
FROM alpine:3.23 AS certs→ ca-certificates layerFROM scratchruntimeCOPY linux/${TARGETARCH}/api-test /usr/local/bin/api-test— consumes goreleaser- or Make-staged binaryUSER 1000:1000(scratch has no /etc/passwd)CMD ["/usr/local/bin/api-test", "--healthcheck"]exists incmd/api-test/main.go:32(verified before keeping)/app/configs/api-test.yamlfor operators to mountDockerfile.dev(used bydocker-compose.dev.yml) is untouched..dockerignoremcp-test parity. Excludes
bin/,dist/,linux/,site/, coverage, allnode_modules,ui/dist,internal/ui/dist,docs/,mkdocs.yml, repo metadata,.env*(with.env.exampleallowlisted), IDE/OS noise, and the local screenshot tooling'snode_modules..gitignoreAdds
/dist/(goreleaser output) and/linux/(make dockerscratch dir). Both were untracked-but-uncommitted before; now they're explicitly ignored.Verification
goreleaser check→ 1 config validated, no warnings.goreleaser release --snapshot --clean --skip=publish,docker,sign,before→ all 5 cross-platform binaries built, 5 archives produced (api-test_1.0.1-next_*.tar.gz+_windows_amd64.zip),checksums.txtwritten. 18s total.beforeskipped locally because corepack isn't installed on this Mac. CI installs pnpm viapnpm/action-setup, so the SPA hook runs there.dockerskipped locally because the snapshot path doesn't exercise GHCR push; the Dockerfile syntax is otherwise straightforward.Test plan
Re-tagging after this merges is what actually exercises the workflow. Suggested sequence:
git tag v1.0.0 && git push origin v1.0.0.https://github.com/plexara/api-test/releases/tag/v1.0.0has 5 archives +checksums.txt.ghcr.io/plexara/api-test:v1.0.0,:v1,:latestare pushed:docker pull ghcr.io/plexara/api-test:v1.0.0.docker run --rm ghcr.io/plexara/api-test:v1.0.0 --versionreturns the stamped version/commit/date.Wider gap I noticed but did not touch
While auditing for this PR I diff'd the trees against mcp-test. Listing what's missing here so you can decide:
pkg/audit/jsonfilter.go), live-tail SSE, audit replay (pkg/httpsrv/portal_api_replay_test.go), audit export, rate limiting (pkg/httpsrv/ratelimit.go), admin API (admin_api.go).Compare.tsx,Wellknown.tsx. (Tools.tsxis MCP-specific; api-test's analogue isEndpoints.tsx.)0002_audit_payloads.{up,down}.sql,0003_audit_payloads_cleanup.{up,down}.sql.operations/inspection.md,operations/kubernetes.md,getting-started/connect-client.md.tests/audit_replay_test.go,audit_stream_test.go,portal_test.go,spa_test.go.examples/directory,scripts/screenshots/pnpm-lock.yaml.Skipped from the gap because they're MCP-specific by definition:
pkg/mcpmw/,pkg/tools/,docs/tools/,docs/reference/mcp.md. api-test's analogues live underpkg/endpoints/anddocs/endpoints/.