Skip to content

feat: add CI/CD pipeline, Makefile, Docker, and goreleaser#18

Merged
omattsson merged 7 commits intomainfrom
feature/issue-8-cicd-makefile
Mar 28, 2026
Merged

feat: add CI/CD pipeline, Makefile, Docker, and goreleaser#18
omattsson merged 7 commits intomainfrom
feature/issue-8-cicd-makefile

Conversation

@omattsson
Copy link
Copy Markdown
Owner

Summary

Adds complete build infrastructure and CI/CD pipeline for stackctl.

New Files

File Purpose
cli/Makefile Local dev targets: build, build-all (5 platforms), test, lint, coverage, install, clean
.github/workflows/ci.yml CI on push/PR: test → lint → build-all
.github/workflows/release.yml Release on v* tags: test → goreleaser with Homebrew tap publishing
.goreleaser.yml goreleaser v2 config: cross-compilation, archives, checksums, changelog, brew formula
Dockerfile Multi-stage build: golang:1.26.1-alpine builder → alpine:3.20 runtime (non-root)
.dockerignore Excludes build artifacts from Docker context

Key Decisions

  • Go 1.26.1 pinned everywhere (ci.yml, release.yml, Dockerfile) to match go.mod
  • staticcheck@2026.1 pinned in CI for reproducible builds
  • **goreleaser- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **mebre- **goreleaser- **goreleaser- **goreleaser- **goreleaser- **gorelW_TAP_TOKEN` secret
  • **5 build targe- **5 buiux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64

Setup Required

Before the first release, add a HOMEBREW_TAP_TOKEN secret to this repo (PAT with repo scope for pushing the Homebrew formula).

Closes #8

- Makefile: build, build-all (5 platforms), test, lint, coverage, install, clean
- CI workflow: test + lint + build on push/PR to main
- Release workflow: goreleaser on version tags with Homebrew tap publishing
- Dockerfile: multi-stage build, non-root user, alpine runtime
- goreleaser v2: cross-compilation, archives, checksums, changelog, brew formula
- Pin Go 1.26.1 and staticcheck 2026.1 for reproducible builds

Closes #8
Copilot AI review requested due to automatic review settings March 26, 2026 18:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds build/release infrastructure for the stackctl Go CLI (under cli/) via a Makefile, CI workflows, Docker image build, and GoReleaser-based release automation (including Homebrew tap publishing).

Changes:

  • Add cli/Makefile targets for local build/test/lint/coverage/install and cross-compilation.
  • Add GitHub Actions workflows for CI (test/lint/build) and tag-based releases via GoReleaser.
  • Add packaging artifacts: multi-stage Dockerfile, .dockerignore, and .goreleaser.yml (with Homebrew formula publishing).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
cli/Makefile Defines local build/test/lint/coverage targets and cross-platform builds.
cli/.gitignore Ignores GoReleaser output (dist/) alongside bin/ and coverage output.
Dockerfile Multi-stage build for a minimal non-root runtime image.
.goreleaser.yml GoReleaser v2 config for cross-compilation, archives, checksums, changelog, and Homebrew tap publishing.
.github/workflows/ci.yml CI workflow running tests, lint, and cross-platform builds on push/PR.
.github/workflows/release.yml Release workflow triggering on v* tags and running GoReleaser.
.dockerignore Reduces Docker build context by excluding artifacts and non-build files.

Comment thread cli/Makefile
Comment on lines +14 to +25
build:
CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY_NAME) .

build-all:
@for platform in $(PLATFORMS); do \
os=$${platform%/*}; \
arch=$${platform#*/}; \
output=bin/$(BINARY_NAME)-$${os}-$${arch}; \
if [ "$${os}" = "windows" ]; then output=$${output}.exe; fi; \
echo "Building $${output}..."; \
CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} go build -ldflags "$(LDFLAGS)" -o $${output} . || exit 1; \
done
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make build / make build-all write outputs under bin/, but the Makefile never creates that directory. On a clean checkout (including in CI), go build -o bin/... will fail with “no such file or directory”. Create bin/ (e.g., mkdir -p bin) before the build commands (and/or before the loop in build-all).

Copilot uses AI. Check for mistakes.
Ensures 'make build' and 'make build-all' work on a clean checkout
where bin/ does not yet exist.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Comment thread cli/Makefile Outdated
go tool cover -func=coverage.out

install:
go install -ldflags "$(LDFLAGS)" .
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

install builds without CGO_ENABLED=0, so make install may produce a dynamically-linked binary that differs from make build / build-all and from release artifacts (which use CGO_ENABLED=0). Consider setting CGO_ENABLED=0 here as well to keep local installs consistent with CI/release builds.

Suggested change
go install -ldflags "$(LDFLAGS)" .
CGO_ENABLED=0 go install -ldflags "$(LDFLAGS)" .

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/ci.yml
Comment on lines +22 to +26
- uses: actions/setup-go@v5
with:
go-version: '1.26.1'
cache-dependency-path: cli/go.sum
- run: make test
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Go version is duplicated across jobs (go-version: '1.26.1'). To avoid future drift from cli/go.mod, consider switching to go-version-file: cli/go.mod (and drop the hard-coded version) in each actions/setup-go step.

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/release.yml Outdated
Comment on lines +23 to +27
- name: Run tests
working-directory: cli
run: make test
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v6
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release workflow runs tests but skips linting/static analysis. This allows publishing a tagged release that passes tests yet fails go vet/staticcheck (e.g., if the tag is pushed directly). Consider running make lint (and installing staticcheck) in this workflow as well, matching CI’s gating checks.

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile
Comment on lines +1 to +3
# Stage 1: Build
FROM golang:1.26.1-alpine AS builder

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For supply-chain/reproducibility, consider pinning the base images by digest (e.g., golang:...@sha256:..., alpine:...@sha256:...) instead of mutable tags. This reduces the risk of builds changing unexpectedly over time.

Copilot uses AI. Check for mistakes.
- Add CGO_ENABLED=0 to make install for consistency with build/release
- Switch CI from go-version to go-version-file: cli/go.mod to avoid drift
- Add lint step to release workflow to gate tagged releases
- Pin Dockerfile base images by digest for reproducibility
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread cli/Makefile Outdated
Comment on lines +34 to +38
@if command -v staticcheck >/dev/null 2>&1; then \
staticcheck ./...; \
else \
echo "staticcheck not installed, skipping (go install honnef.co/go/tools/cmd/staticcheck@latest)"; \
fi
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lint silently skips staticcheck when it isn't installed, which can lead to local lint results diverging from CI (where a specific staticcheck version is installed). Consider making staticcheck a required dependency for make lint (fail with a clear message) or auto-installing a pinned version via a Makefile variable so the same checks run everywhere.

Suggested change
@if command -v staticcheck >/dev/null 2>&1; then \
staticcheck ./...; \
else \
echo "staticcheck not installed, skipping (go install honnef.co/go/tools/cmd/staticcheck@latest)"; \
fi
@if ! command -v staticcheck >/dev/null 2>&1; then \
echo "Error: staticcheck is not installed. Please install it with:"; \
echo " go install honnef.co/go/tools/cmd/staticcheck@latest"; \
exit 1; \
fi
staticcheck ./...

Copilot uses AI. Check for mistakes.
Comment thread cli/Makefile Outdated
CGO_ENABLED=0 go install -ldflags "$(LDFLAGS)" .

clean:
rm -rf bin/
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean currently only removes bin/, but this Makefile also creates other artifacts (coverage.out from coverage, and dist/ from goreleaser). Consider extending clean to remove those as well so it reliably resets the working tree.

Suggested change
rm -rf bin/
rm -rf bin/ coverage.out dist/

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread cli/Makefile Outdated
Comment on lines +14 to +16
build:
@mkdir -p bin
CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o bin/$(BINARY_NAME) .
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make build always outputs bin/stackctl without the .exe suffix. On Windows, this produces a non-standard binary name (unlike go build default and unlike build-all, which correctly appends .exe) and can break expected execution/packaging on Windows. Consider adding a small OS check in the build target to append .exe when GOOS=windows (or when running on Windows).

Copilot uses AI. Check for mistakes.
Comment thread cli/Makefile
Comment on lines +32 to +38
lint:
go vet ./...
@if command -v staticcheck >/dev/null 2>&1; then \
staticcheck ./...; \
else \
echo "staticcheck not installed, skipping (go install honnef.co/go/tools/cmd/staticcheck@latest)"; \
fi
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint target suggests installing staticcheck@latest, while CI/release workflows pin staticcheck@2026.1. This can lead to local lint results diverging from CI. Consider centralizing the staticcheck version (e.g., a Makefile var) and referencing that in both the install hint and workflows (or add a make tools target that installs the pinned version).

Copilot uses AI. Check for mistakes.
…ck version

- Detect GOOS=windows or OS=Windows_NT to append .exe in build target
- Add STATICCHECK_VERSION variable (2026.1) and make tools target
- Update CI and release workflows to use make tools instead of hardcoded version
- Update lint hint to reference make tools

Addresses PR review comments from #18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread cli/Makefile Outdated
go install honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION)

clean:
rm -rf bin/
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make clean only removes bin/, but this PR also introduces additional build artifacts (cli/dist from GoReleaser and coverage.out from make coverage). Consider extending clean to remove these as well so developers can reliably reset the workspace with one target.

Suggested change
rm -rf bin/
rm -rf bin/ dist/ coverage.out

Copilot uses AI. Check for mistakes.
run: make test && make lint
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v6
with:
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The release workflow pins the action major version (goreleaser/goreleaser-action@v6) but not the GoReleaser binary version itself, so a new GoReleaser release could change behavior or break releases unexpectedly. Consider setting the action input to pin a specific GoReleaser version (or at least a constrained major/minor range) for reproducible releases.

Suggested change
with:
with:
version: v2.7.0

Copilot uses AI. Check for mistakes.
- Add dist/ and coverage.out to make clean target
- Pin goreleaser to v2.7.0 in release workflow for reproducible builds

Addresses PR review comments from #18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread Dockerfile
Comment on lines +23 to +28
RUN apk add --no-cache ca-certificates && \
adduser -D -h /home/stackctl stackctl

COPY --from=builder /build/stackctl /usr/local/bin/stackctl

USER stackctl
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime image creates a non-root user but leaves the default login shell enabled. For a CLI container image, it’s safer to create the user with a nologin shell (or otherwise disable interactive login) to reduce the attack surface.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +36
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v6
with:
version: v2.7.0
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description’s “Key Decisions” section appears corrupted (repeated/truncated text around goreleaser/Homebrew token and build targets). Please clean up the PR description so reviewers/users can understand the intent and required setup.

Copilot uses AI. Check for mistakes.
Comment thread cli/Makefile Outdated
@if command -v staticcheck >/dev/null 2>&1; then \
staticcheck ./...; \
else \
echo "staticcheck not installed, skipping (run 'make tools' to install staticcheck@$(STATICCHECK_VERSION))"; \
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make lint currently succeeds even when staticcheck is missing (it prints a message and skips). This can hide lint failures locally and makes make lint behavior differ depending on whether tools are installed. Consider making lint fail with a clear error when staticcheck is not present, or have lint depend on tools so it always runs the full lint suite.

Suggested change
echo "staticcheck not installed, skipping (run 'make tools' to install staticcheck@$(STATICCHECK_VERSION))"; \
echo "error: staticcheck not installed (run 'make tools' to install staticcheck@$(STATICCHECK_VERSION))" >&2; \
exit 1; \

Copilot uses AI. Check for mistakes.
Address PR review comment: lint target now exits with error
instead of silently skipping when staticcheck is missing.
@omattsson omattsson merged commit 7bdcd25 into main Mar 28, 2026
3 checks passed
@omattsson omattsson deleted the feature/issue-8-cicd-makefile branch March 28, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3.2: CI/CD, cross-compilation, and Makefile

2 participants