-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGNUmakefile
More file actions
319 lines (291 loc) · 14.9 KB
/
Copy pathGNUmakefile
File metadata and controls
319 lines (291 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# kzero — build, test, release (GNU Make). On FreeBSD use gmake (pkg install gmake).
# GNU Make prefers GNUmakefile over Makefile; the Makefile stub forwards to gmake.
BINARY := kzero
PLUGIN := kubectl-kzero
DIST := dist
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
# BSD dist tarball arch (cross-compile). Examples: make dist-freebsd FREEBSD_ARCH=arm64
FREEBSD_ARCH ?= amd64
OPENBSD_ARCH ?= amd64
check-docker = @docker info >/dev/null 2>&1 || { echo "Error: Docker is not running. Start Docker and try again."; exit 1; }
GRYPE_FAIL_ON ?= high
# Minimum total statement coverage for `make cover-check` (see .cursor/rules/release-tests.mdc).
COVERAGE_MIN ?= 80
VERSION ?= $(shell v=$$(cat VERSION 2>/dev/null | tr -d '\n\r'); [ -n "$$v" ] && echo "v$$v" || echo "v0.2.0")
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
BUILDDATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -ldflags "-s -w -X github.com/hrodrig/kzero/internal/cli.Version=$(VERSION) -X github.com/hrodrig/kzero/internal/notify.AppVersion=$(VERSION) -X github.com/hrodrig/kzero/internal/cli.Commit=$(COMMIT) -X github.com/hrodrig/kzero/internal/cli.BuildDate=$(BUILDDATE) -X github.com/hrodrig/kzero/internal/cli.Branch=$(BRANCH)"
PORT_VERSION := $(shell cat VERSION 2>/dev/null | tr -d '\n\r' | sed 's/^v//')
# Colored help (same pattern as pgwd). Disable: NO_COLOR=1 make help
GREEN := \033[0;32m
YELLOW := \033[0;33m
CYAN := \033[0;36m
RESET := \033[0m
ifneq ($(NO_COLOR),)
GREEN :=
YELLOW :=
CYAN :=
RESET :=
endif
.DEFAULT_GOAL := help
.PHONY: help
help:
@echo "$(GREEN)kzero$(RESET) — Kubernetes pipeline CLI"
@echo ""
@echo "Usage: make [target]"
@echo ""
@echo "$(YELLOW)Build:$(RESET)"
@echo " $(GREEN)build$(RESET) Build ./bin/kzero for current platform"
@echo " $(GREEN)build-all$(RESET) Cross-compile to $(DIST)/ (linux, darwin, windows, freebsd, openbsd)"
@echo ""
@echo "$(YELLOW)Install & clean:$(RESET)"
@echo " $(GREEN)install$(RESET) go install to \$$GOBIN"
@echo " $(GREEN)install-kubectl-plugin$(RESET) Install kubectl-kzero into \$$BINDIR (must be on PATH)"
@echo " $(GREEN)install-man$(RESET) Install man page to \$$MANDIR/man1 (default /usr/local/share/man)"
@echo " $(GREEN)clean$(RESET) Remove ./bin/kzero, ./bin/kubectl-kzero, coverage.out, and $(DIST)/"
@echo ""
@echo "$(YELLOW)Test:$(RESET)"
@echo " $(GREEN)test$(RESET) Unit tests (go test ./...)"
@echo " $(GREEN)test-kind$(RESET) Kind integration (testing/kind/kind-e2e.sh; needs Docker/kind/kubectl)"
@echo " $(GREEN)cover$(RESET) Unit tests with coverage.out"
@echo " $(GREEN)cover-check$(RESET) Fail if total statement coverage < $(COVERAGE_MIN)% (override: COVERAGE_MIN=70)"
@echo ""
@echo "$(YELLOW)Quality:$(RESET)"
@echo " $(GREEN)lint$(RESET) gofmt -s, go vet, gocyclo (<=14)"
@echo " $(GREEN)lint-fix$(RESET) gofmt -s -w"
@echo " $(GREEN)tools$(RESET) Install govulncheck and gocyclo to \$$GOBIN"
@echo " $(GREEN)security$(RESET) govulncheck ./..."
@echo ""
@echo "$(YELLOW)Docker:$(RESET)"
@echo " $(GREEN)docker-build$(RESET) Build container image kzero:local"
@echo " $(GREEN)docker-scan$(RESET) Build kzero:scan and run Grype (needs Docker)"
@echo ""
@echo "$(YELLOW)Release:$(RESET)"
@echo " $(GREEN)release-check$(RESET) VERSION semver + lint + test + cover-check + security + docker-scan"
@echo " $(GREEN)release$(RESET) release-check then goreleaser (only from main)"
@echo " $(GREEN)snapshot$(RESET) Goreleaser snapshot to $(DIST)/ (no tag; includes .deb/.rpm/.tar.gz)"
@echo " $(GREEN)dist-freebsd$(RESET) Tarball for FreeBSD ports (default FREEBSD_ARCH=amd64)"
@echo " $(GREEN)dist-openbsd$(RESET) Tarball for OpenBSD ports (default OPENBSD_ARCH=amd64)"
@echo " $(GREEN)port-freebsd-sync$(RESET) Set PORTVERSION in contrib/freebsd/Makefile from VERSION"
@echo " $(GREEN)port-openbsd-sync$(RESET) Set DISTNAME/PKGNAME/MASTER_SITES/DISTFILES in contrib/openbsd/port/Makefile"
@echo ""
@echo "$(CYAN)Current version:$(RESET) $$(cat VERSION 2>/dev/null | tr -d '\n\r' || echo '?') (ldflags $(VERSION), branch $(BRANCH))"
@echo ""
@echo "$(CYAN)Examples:$(RESET)"
@echo " make build"
@echo " make test-kind"
@echo " make release-check"
@echo " NO_COLOR=1 make help"
.PHONY: build build-all install install-kubectl-plugin install-man clean test test-kind cover cover-check lint lint-fix tools security docker-build docker-scan release-check release snapshot dist-freebsd dist-openbsd port-freebsd-sync port-openbsd-sync
build:
@mkdir -p bin
go build -trimpath $(LDFLAGS) -o bin/$(BINARY) ./cmd/kzero
test-kind:
bash testing/kind/kind-e2e.sh
install-kubectl-plugin: build
@case ":$$PATH:" in \
*":$(BINDIR):"*) \
;; \
*) \
echo "Error: $(BINDIR) is not in PATH."; \
echo "kubectl plugin discovery walks every directory in PATH and looks for executables starting with 'kubectl-'."; \
echo "Set PREFIX/BINDIR to a directory that IS in PATH (default PREFIX=$(PREFIX))."; \
exit 1; \
;; \
esac
install -d "$(BINDIR)"
install -m 755 bin/$(BINARY) "$(BINDIR)/$(PLUGIN)"
@echo "Installed $(PLUGIN) to $(BINDIR)."
@echo "Verify with: kubectl plugin list | grep kzero"
build-all:
@mkdir -p $(DIST)
GOOS=linux GOARCH=amd64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-linux-amd64 ./cmd/kzero
GOOS=linux GOARCH=arm64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-linux-arm64 ./cmd/kzero
GOOS=darwin GOARCH=amd64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-darwin-amd64 ./cmd/kzero
GOOS=darwin GOARCH=arm64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-darwin-arm64 ./cmd/kzero
GOOS=windows GOARCH=amd64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-windows-amd64.exe ./cmd/kzero
GOOS=freebsd GOARCH=amd64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-freebsd-amd64 ./cmd/kzero
GOOS=freebsd GOARCH=arm64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-freebsd-arm64 ./cmd/kzero
GOOS=openbsd GOARCH=amd64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-openbsd-amd64 ./cmd/kzero
GOOS=openbsd GOARCH=arm64 go build -trimpath $(LDFLAGS) -o $(DIST)/$(BINARY)-openbsd-arm64 ./cmd/kzero
install:
go install -trimpath $(LDFLAGS) ./cmd/kzero
MANDIR ?= /usr/local/share/man
.PHONY: install-man
install-man:
@mkdir -p $(MANDIR)/man1
@cp contrib/man/man1/kzero.1 $(MANDIR)/man1/
@echo "Installed man page to $(MANDIR)/man1/kzero.1"
clean:
rm -f bin/$(BINARY) bin/$(PLUGIN) coverage.out
rm -rf $(DIST)
test:
go test ./...
cover:
go test ./... -coverprofile=coverage.out -covermode=atomic
@go tool cover -func=coverage.out | tail -1
cover-check:
go test ./... -coverprofile=coverage.out -covermode=atomic
@pct=$$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $$NF}' | tr -d '%'); \
echo "Total statement coverage: $$pct% (minimum $(COVERAGE_MIN)%)"; \
awk -v p="$$pct" -v m="$(COVERAGE_MIN)" 'BEGIN { if (p+0 < m+0) { print "Error: coverage is below " m "% — add tests or set COVERAGE_MIN="; exit 1 } }'
lint:
@echo "Checking gofmt -s..."
@unformatted=$$(gofmt -s -l .); [ -z "$$unformatted" ] || { echo "Files not formatted (run: make lint-fix):"; echo "$$unformatted"; exit 1; }
@echo "Running go vet..."
@go vet ./...
@echo "Running gocyclo (complexity <= 14)..."
@go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
@"$(shell go env GOPATH)/bin/gocyclo" -over 14 .
lint-fix:
gofmt -s -w .
tools:
go install golang.org/x/vuln/cmd/govulncheck@latest
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
security:
@echo "Running govulncheck..."
@tmp=$$(mktemp); \
go run golang.org/x/vuln/cmd/govulncheck@latest ./... >"$$tmp" 2>&1 || true; \
output=$$(grep -v '^exit status [0-9]*$$' "$$tmp" || true); \
rm -f "$$tmp"; \
ignored_ids=$$(grep 'id:' .govulncheck-ignore.yaml 2>/dev/null | cut -d'"' -f2); \
total_vulns=$$(echo "$$output" | grep -c 'Vulnerability #' || true); \
matching=0; \
for id in $$ignored_ids; do \
c=$$(echo "$$output" | grep -c "^Vulnerability.*$$id" || true); \
matching=$$((matching + c)); \
done; \
if [ $$total_vulns -eq 0 ]; then \
echo "$$output"; \
echo ""; \
echo "=== security: PASS (govulncheck clean) ==="; \
elif [ $$total_vulns -eq $$matching ]; then \
echo "$$output"; \
echo ""; \
echo "=== security: PASS (known false positives only) ==="; \
echo "govulncheck reported $$matching advisories filtered — see .govulncheck-ignore.yaml (containerd v2-only CRI)."; \
echo "Pending upstream: Go vulndb module-path correction; no kzero release action until vulndb or helm/containerd graph changes."; \
echo "Policy: .govulncheck-ignore.yaml"; \
else \
echo "$$output"; \
echo ""; \
echo "ERROR: $$((total_vulns - matching)) unfiltered govulncheck finding(s)."; \
echo "Add to .govulncheck-ignore.yaml only with documented false-positive rationale."; \
exit 1; \
fi
docker-build:
$(check-docker)
docker build --build-arg VERSION=$(VERSION) --build-arg COMMIT=$(COMMIT) --build-arg BUILDDATE=$(BUILDDATE) --build-arg BRANCH=$(BRANCH) -t kzero:local .
docker-scan:
$(check-docker)
docker build --build-arg VERSION=$(VERSION) --build-arg COMMIT=$(COMMIT) --build-arg BUILDDATE=$(BUILDDATE) --build-arg BRANCH=$(BRANCH) -t kzero:scan .
@if command -v grype >/dev/null 2>&1; then \
grype kzero:scan -c .grype.yaml --fail-on $(GRYPE_FAIL_ON); \
else \
echo "grype not on PATH; using anchore/grype container..."; \
docker run --rm --pull=always -v /var/run/docker.sock:/var/run/docker.sock -v "$(CURDIR)/.grype.yaml:/.grype.yaml:ro" anchore/grype:latest \
kzero:scan -c /.grype.yaml --fail-on $(GRYPE_FAIL_ON); \
fi
.PHONY: release-check
release-check:
$(check-docker)
@set -e; \
test -f VERSION || { echo "Error: VERSION file is required"; exit 1; }; \
ver_raw=$$(cat VERSION | tr -d '\n\r'); ver=$${ver_raw#v}; \
echo "$$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "Error: VERSION must be semantic MAJOR.MINOR.PATCH (got: $$ver_raw)"; exit 1; }; \
echo "Release version: $$ver (tag v$$ver)"; \
man_ver=$$(sed -n 's/^\.TH KZERO 1 "[^"]*" "\([^"]*\)".*/\1/p' contrib/man/man1/kzero.1 | head -1); \
expect_ver="kzero v$$ver"; \
test "$$man_ver" = "$$expect_ver" || { echo "Error: contrib/man/man1/kzero.1 .TH version ($$man_ver) must match VERSION ($$expect_ver)"; exit 1; };
@$(MAKE) lint
@$(MAKE) test
@$(MAKE) cover-check
@$(MAKE) security
@$(MAKE) docker-scan
@echo "All release checks passed."
release: release-check
$(check-docker)
@branch=$$(git branch --show-current 2>/dev/null); \
if [ "$$branch" != "main" ]; then \
echo "Error: release only from main (current: $$branch). Merge develop → main first."; \
exit 1; \
fi; \
goreleaser release --clean
snapshot:
@ver_raw=$$(cat VERSION 2>/dev/null | tr -d '\n\r'); \
[ -n "$$ver_raw" ] || { echo "Error: VERSION file is required for snapshot"; exit 1; }; \
ver=$${ver_raw#v}; \
echo "$$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "Error: VERSION must be semantic MAJOR.MINOR.PATCH (got: $$ver_raw)"; exit 1; }; \
KZERO_SNAPSHOT_VERSION="$$ver-next" goreleaser release --snapshot --clean
.PHONY: port-freebsd-sync
port-freebsd-sync:
@[ -n "$(PORT_VERSION)" ] || { echo "Error: VERSION file empty or missing"; exit 1; }
@sed -i.bak "s/^PORTVERSION=.*/PORTVERSION=\t$(PORT_VERSION)/" contrib/freebsd/Makefile
@rm -f contrib/freebsd/Makefile.bak
@echo "Updated contrib/freebsd/Makefile PORTVERSION to $(PORT_VERSION)"
.PHONY: port-openbsd-sync
port-openbsd-sync:
@[ -n "$(PORT_VERSION)" ] || { echo "Error: VERSION file empty or missing"; exit 1; }
@test -f contrib/openbsd/port/Makefile || { echo "Error: contrib/openbsd/port/Makefile not found"; exit 1; }
@sed -i.bak \
-e 's#^DISTNAME =.*#DISTNAME = kzero_v$(PORT_VERSION)_openbsd_$${MACHINE_ARCH:S/aarch64/arm64/}#' \
-e 's#^PKGNAME =.*#PKGNAME = kzero-$(PORT_VERSION)#' \
-e 's#^MASTER_SITES =.*#MASTER_SITES = https://github.com/hrodrig/kzero/releases/download/v$(PORT_VERSION)/#' \
-e 's#^DISTFILES =.*#DISTFILES = kzero_v$(PORT_VERSION)_openbsd_$${MACHINE_ARCH:S/aarch64/arm64/}.tar.gz#' \
contrib/openbsd/port/Makefile
@rm -f contrib/openbsd/port/Makefile.bak
@echo "Updated contrib/openbsd/port/Makefile to $(PORT_VERSION)"
.PHONY: dist-freebsd
dist-freebsd:
@set -e; \
ver_raw=$$(cat VERSION 2>/dev/null | tr -d '\n\r'); \
[ -n "$$ver_raw" ] || { echo "Error: VERSION file is required"; exit 1; }; \
ver=$${ver_raw#v}; \
echo "$$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "Error: VERSION must be semantic MAJOR.MINOR.PATCH (got: $$ver_raw)"; exit 1; }; \
echo "$(FREEBSD_ARCH)" | grep -qE '^(amd64|arm64)$$' || { echo "Error: FREEBSD_ARCH must be amd64 or arm64"; exit 1; }; \
arch="$(FREEBSD_ARCH)"; \
out="$(DIST)/kzero_v$${ver}_freebsd_$$arch.tar.gz"; \
stage="/tmp/kzero-dist-root-$$PPID"; \
tmpbin="$(DIST)/kzero-freebsd-$$arch-$$PPID"; \
echo "Building kzero for FreeBSD $$arch with VERSION=v$$ver..."; \
mkdir -p "$(DIST)"; \
GOOS=freebsd GOARCH="$$arch" go build -trimpath $(LDFLAGS) -o "$$tmpbin" ./cmd/kzero; \
rm -rf "$$stage"; \
mkdir -p "$$stage/share/man/man1" "$$stage/share/doc/kzero" "$$stage/share/examples/kzero"; \
cp "$$tmpbin" "$$stage/kzero"; \
cp "$$tmpbin" "$$stage/kubectl-kzero"; \
rm -f "$$tmpbin"; \
cp LICENSE "$$stage/share/doc/kzero/LICENSE"; \
cp configs/kzero.sample.yml "$$stage/share/examples/kzero/kzero.sample.yml"; \
cp contrib/man/man1/kzero.1 "$$stage/share/man/man1/kzero.1"; \
tar -C "$$stage" -czf "$$out" .; \
rm -rf "$$stage"; \
echo "Wrote $$out"
.PHONY: dist-openbsd
dist-openbsd:
@set -e; \
ver_raw=$$(cat VERSION 2>/dev/null | tr -d '\n\r'); \
[ -n "$$ver_raw" ] || { echo "Error: VERSION file is required"; exit 1; }; \
ver=$${ver_raw#v}; \
echo "$$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "Error: VERSION must be semantic MAJOR.MINOR.PATCH (got: $$ver_raw)"; exit 1; }; \
echo "$(OPENBSD_ARCH)" | grep -qE '^(amd64|arm64)$$' || { echo "Error: OPENBSD_ARCH must be amd64 or arm64"; exit 1; }; \
arch="$(OPENBSD_ARCH)"; \
out="$(DIST)/kzero_v$${ver}_openbsd_$$arch.tar.gz"; \
stage="/tmp/kzero-openbsd-dist-root-$$PPID"; \
tmpbin="$(DIST)/kzero-openbsd-$$arch-$$PPID"; \
echo "Building kzero for OpenBSD $$arch with VERSION=v$$ver..."; \
mkdir -p "$(DIST)"; \
GOOS=openbsd GOARCH="$$arch" go build -trimpath $(LDFLAGS) -o "$$tmpbin" ./cmd/kzero; \
rm -rf "$$stage"; \
mkdir -p "$$stage/share/man/man1" "$$stage/share/doc/kzero" "$$stage/share/examples/kzero"; \
cp "$$tmpbin" "$$stage/kzero"; \
cp "$$tmpbin" "$$stage/kubectl-kzero"; \
rm -f "$$tmpbin"; \
cp LICENSE "$$stage/share/doc/kzero/LICENSE"; \
cp configs/kzero.sample.yml "$$stage/share/examples/kzero/kzero.sample.yml"; \
cp contrib/man/man1/kzero.1 "$$stage/share/man/man1/kzero.1"; \
tar -C "$$stage" -czf "$$out" .; \
rm -rf "$$stage"; \
echo "Wrote $$out"