-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
265 lines (229 loc) · 13.5 KB
/
Copy pathMakefile
File metadata and controls
265 lines (229 loc) · 13.5 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
.DEFAULT_GOAL := help
# ==================================================================================== #
# VARIABLES
# ==================================================================================== #
GOOS := $(shell go env GOOS)
GOBIN := $(shell go env GOBIN)
ifeq ($(strip $(GOBIN)),)
GOBIN := $(shell go env GOPATH)/bin
endif
SERVER_MAIN := ./cmd/maping-server
# Docker stacks: one neutral base (docker-compose.yml) + overlays. Both stacks
# read .env for credentials, ports, and auth config.
# make local -> laptop stack (host ports published, dev config)
# make up -> production stack (only the server port published)
ENV_FILE := .env
BASE_COMPOSE := docker compose --env-file $(ENV_FILE) -f docker-compose.yml
LOCAL_COMPOSE := $(BASE_COMPOSE) -f docker-compose.local.yml
PROD_COMPOSE := $(BASE_COMPOSE) -f docker-compose.prod.yml
# Target a service for logs/restart, e.g. `make logs service=clickhouse`.
service ?= maping-server
ifeq ($(GOOS),windows)
BINARY_NAME := maping-server.exe
else
BINARY_NAME := maping-server
endif
# The repo is a Go workspace (go.work). Every publishable module is listed here
# so test/audit/tidy/checklen iterate over all of them. example/ is intentionally
# excluded (demo code, not a distributed library).
MODULES := proto client client/gin client/nethttp client/echo client/chi client/beego server
# Modules published to proxy.golang.org, in dependency (release) order: a module
# is only tagged after everything it imports is already tagged. Used by `make
# release`. example/ is not published.
RELEASE_MODULES := proto client server client/gin client/nethttp client/echo client/chi client/beego
# Client framework adapters (client/<name> modules), released as client/<name>/vX.Y.Z
# after client is tagged, since each imports the core client at that version.
ADAPTERS := gin nethttp echo chi beego
# Release version for `make release VERSION=vX.Y.Z` (path-prefixed tags per module).
VERSION ?=
# Build-time identity linked into the server binary. Distinct from VERSION
# above: that one drives release tagging, these describe whatever tree is being
# compiled right now, so an untagged dev build still identifies itself.
VERSION_PKG := github.com/arhuman/maping/server/internal/version
# Tags are path-prefixed per module (server/vX.Y.Z), so describe must match only
# the server's own tags: a bare --tags would report whichever module was tagged
# last, e.g. client/beego/v0.12.0, as the server's version.
BUILD_VERSION ?= $(shell git describe --tags --match 'server/v*' --always --dirty 2>/dev/null | sed 's|^server/||' || echo dev)
BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo none)
LDFLAGS := -s -w -X $(VERSION_PKG).Version=$(BUILD_VERSION) -X $(VERSION_PKG).Commit=$(BUILD_COMMIT)
LINE_LIMIT ?= 500
# Coverage floor enforced by `make cover`, which `make audit` depends on, so the
# number lives in exactly one place. proto/ is exempt: it is dominated by
# generated .pb.go/.connect.go stubs no test executes, so its total (~8%) says
# nothing about its handwritten packages, which are above 90%.
COVER_MIN ?= 70
COVER_MODULES := $(filter-out proto,$(MODULES))
# Rounds of sample requests fired by `make generate-traffic` (one round hits
# every example route once).
ROUNDS ?= 20
# ==================================================================================== #
# PHONY DECLARATIONS (in alphabetical order)
# ==================================================================================== #
.PHONY: audit build checklen confirm cover down generate-traffic help integration local logs preflight proto release restart test tidy tools up
# ==================================================================================== #
# STANDARD TARGETS (in alphabetical order)
# ==================================================================================== #
## audit: run quality control checks across all modules
audit:
@$(MAKE) checklen
@which golangci-lint > /dev/null || $(MAKE) tools
@which govulncheck > /dev/null || $(MAKE) tools
@for m in $(MODULES); do echo "== audit $$m =="; (cd $$m && go mod verify && golangci-lint run ./... && govulncheck ./...) || exit 1; done
@$(MAKE) cover
## cover: fail if any module's total test coverage is below COVER_MIN percent
cover:
@fail=""; \
for m in $(COVER_MODULES); do \
prof=$$(mktemp); \
(cd $$m && go test -short -coverprofile=$$prof ./... >/dev/null 2>&1) || { echo "tests failed in $$m" >&2; rm -f $$prof; exit 1; }; \
pct=$$(go tool cover -func=$$prof 2>/dev/null | awk '/^total:/{print $$3}' | tr -d '%'); \
rm -f $$prof; \
[ -n "$$pct" ] || pct=0; \
echo "$$m $$pct%"; \
awk "BEGIN{exit !($$pct < $(COVER_MIN))}" && fail="$$fail $$m ($$pct%)\n"; \
done; \
if [ -n "$$fail" ]; then printf "coverage below COVER_MIN=$(COVER_MIN)%%:\n$$fail" >&2; exit 1; fi
## build: build the server binary
build:
CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/$(BINARY_NAME) ./server/cmd/maping-server
## checklen: fail if any non-generated, non-test Go source file exceeds LINE_LIMIT lines
checklen:
@bad=""; for f in $$(find $(MODULES) -name '*.go' ! -name '*_test.go' ! -name '*.pb.go' ! -name '*.connect.go'); do \
n=$$(wc -l < "$$f"); \
if [ "$$n" -gt $(LINE_LIMIT) ]; then bad="$$bad$$f ($$n)\n"; fi; \
done; \
if [ -n "$$bad" ]; then printf "Files over $(LINE_LIMIT) lines:\n$$bad" >&2; exit 1; fi; \
echo "file length OK (all <= $(LINE_LIMIT) lines)"
## down: stop and remove the running stack (local or prod)
down:
$(BASE_COMPOSE) down
## generate-traffic: run the example app against the LOCAL stack and fire sample requests so every dashboard panel fills with data (make generate-traffic ROUNDS=30)
generate-traffic: $(ENV_FILE)
@set -a; . ./$(ENV_FILE) >/dev/null 2>&1; set +a; \
port="$${MAPING_PORT:-8080}"; \
case "$$port" in ''|*[!0-9]*) echo "MAPING_PORT must be a number, got '$$port'" >&2; exit 1;; esac; \
base="http://127.0.0.1:$$port"; \
key="$${MAPING_KEY:-dev-key}"; \
curl -fsS -o /dev/null "$$base/" || { echo "local stack unreachable at $$base -- run 'make local' first" >&2; exit 1; }; \
echo "== building example-api =="; \
(cd example && go build -o ../bin/example .) || exit 1; \
echo "== starting example-api, shipping to LOCAL $$base (logs: bin/example.log) =="; \
MAPING_KEY="$$key" MAPING_ENDPOINT="$$base" ./bin/example >bin/example.log 2>&1 & \
ex=$$!; trap 'kill $$ex 2>/dev/null; wait $$ex 2>/dev/null' EXIT; \
for i in $$(seq 1 40); do curl -fsS -o /dev/null http://127.0.0.1:9090/hello/ready 2>/dev/null && break; sleep 0.5; done; \
echo "== driving $(ROUNDS) rounds across every route =="; \
for i in $$(seq 1 $(ROUNDS)); do \
for p in /hello/world /downstream /db-error /upstream-timeout /timeout /canceled /boom; do \
curl -fsS -o /dev/null "http://127.0.0.1:9090$$p" 2>/dev/null || true; \
done; \
done; \
echo "== waiting ~12s for a flush so summaries ship =="; \
sleep 12; \
echo "traffic done -- open $$base to see the data"
## help: display this help message
help:
@echo 'Usage:'
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
## proto: lint and regenerate protobuf + connect code
proto: tools
buf lint
buf generate
## test: run all tests with race detector across all modules
test:
@for m in $(MODULES); do echo "== test $$m =="; (cd $$m && go test -race -short -cover ./...) || exit 1; done
## tidy: format code and tidy every module
tidy:
@for m in $(MODULES); do echo "== tidy $$m =="; (cd $$m && go fmt ./... && go mod tidy) || exit 1; done
## tools: install required Go development tools
tools:
@echo "Installing Go tools..."
@go install github.com/bufbuild/buf/cmd/buf@latest
@go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
@go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
@go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
@go install golang.org/x/vuln/cmd/govulncheck@latest
@echo "Tools installed in $(GOBIN)"
# ==================================================================================== #
# UTILITY TARGETS
# ==================================================================================== #
## confirm: prompt for user confirmation before proceeding
confirm:
@echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]
# ==================================================================================== #
# PROJECT-SPECIFIC TARGETS
# ==================================================================================== #
## integration: run storage (ClickHouse) and control-plane (Postgres) integration tests
# DSNs point at localhost using the HOST-published ports from .env: compose maps
# Postgres to MAPING_PG_PORT (15432, off 5432 to avoid clashing a local Postgres)
# and ClickHouse native to MAPING_CH_NATIVE_PORT (9000). .env's own MAPING_*_DSN
# use the in-network service hostnames (clickhouse/postgres) and are unreachable
# from the host, so they are NOT reused here — the DSN is rebuilt for localhost.
# An already-exported MAPING_*_DSN still wins; otherwise it is built from .env.
integration:
@echo "== storage + control integration tests (needs ClickHouse + Postgres; run 'make local' first) =="
@ch_dsn="$$MAPING_CLICKHOUSE_DSN"; pg_dsn="$$MAPING_POSTGRES_DSN"; \
set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE) >/dev/null 2>&1; set +a; \
ch_dsn="$${ch_dsn:-clickhouse://$${MAPING_CH_USER:-maping}:$${MAPING_CH_PASSWORD:-maping}@localhost:$${MAPING_CH_NATIVE_PORT:-9000}/$${MAPING_CH_DB:-maping}}"; \
pg_dsn="$${pg_dsn:-postgres://$${MAPING_PG_USER:-maping}:$${MAPING_PG_PASSWORD:-maping}@localhost:$${MAPING_PG_PORT:-15432}/$${MAPING_PG_DB:-maping}?sslmode=disable}"; \
cd server && \
MAPING_CLICKHOUSE_DSN="$$ch_dsn" \
MAPING_POSTGRES_DSN="$$pg_dsn" \
go test -tags=integration -race -cover ./internal/storage/... ./internal/control/...
## local: start the full dev stack (server + ClickHouse + Postgres), host ports published
local: $(ENV_FILE)
$(LOCAL_COMPOSE) up -d --build
@set -a; [ -f $(ENV_FILE) ] && . ./$(ENV_FILE) >/dev/null 2>&1; set +a; \
echo "Local stack up — dashboard http://localhost:$${MAPING_PORT:-8080}"
## preflight: refuse to start production on a missing .env or env.sample defaults
preflight:
@test -f $(ENV_FILE) || { echo "$(ENV_FILE) is missing: cp env.sample .env and fill the production secrets first" >&2; exit 1; }
@set -a; . ./$(ENV_FILE) >/dev/null 2>&1; set +a; \
bad=""; \
[ $${#MAPING_SESSION_KEY} -ge 32 ] || bad="$$bad MAPING_SESSION_KEY: empty or under 32 bytes, sessions cannot be signed\n"; \
[ -n "$$MAPING_POSTGRES_DSN" ] || bad="$$bad MAPING_POSTGRES_DSN: empty, prod would run as a single unauthenticated tenant\n"; \
[ "$$MAPING_PG_PASSWORD" != maping ] || bad="$$bad MAPING_PG_PASSWORD: still the env.sample value\n"; \
[ "$$MAPING_CH_PASSWORD" != maping ] || bad="$$bad MAPING_CH_PASSWORD: still the env.sample value\n"; \
case "$$MAPING_BASE_URL" in https://*) ;; *) bad="$$bad MAPING_BASE_URL: not an https:// URL, session cookies stay insecure\n";; esac; \
if [ -n "$$bad" ]; then printf "refusing to start production, unsafe $(ENV_FILE):\n$$bad" >&2; exit 1; fi
@echo "preflight OK"
## up: start the full production stack (only the server port published)
up: preflight
$(PROD_COMPOSE) up -d --build
@echo "Production stack up."
## release: tag + publish all modules at VERSION in dependency order (make release VERSION=v0.1.0)
release: confirm
@test -n "$(VERSION)" || { echo "VERSION is required, e.g. make release VERSION=v0.1.0" >&2; exit 1; }
@echo "$(VERSION)" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+' || { echo "VERSION must look like vX.Y.Z" >&2; exit 1; }
@test -z "$$(git status --porcelain)" || { echo "working tree not clean; commit or stash first" >&2; exit 1; }
@echo "== 1/3 proto (no internal deps) =="
git tag proto/$(VERSION)
git push origin proto/$(VERSION)
@echo "== 2/3 client + server: pin proto@$(VERSION), drop local replace =="
cd client && go mod edit -dropreplace=github.com/arhuman/maping/proto -require=github.com/arhuman/maping/proto@$(VERSION) && go mod tidy
cd server && go mod edit -dropreplace=github.com/arhuman/maping/proto -require=github.com/arhuman/maping/proto@$(VERSION) && go mod tidy
git commit -am "chore(release): client & server $(VERSION) on proto@$(VERSION)"
git tag client/$(VERSION) && git tag server/$(VERSION)
git push origin client/$(VERSION) server/$(VERSION)
@echo "== 3/3 client adapters: pin client@$(VERSION) + proto@$(VERSION), drop local replaces =="
@for a in $(ADAPTERS); do \
echo "-- client/$$a"; \
(cd client/$$a && go mod edit \
-dropreplace=github.com/arhuman/maping/client -dropreplace=github.com/arhuman/maping/proto \
-require=github.com/arhuman/maping/client@$(VERSION) -require=github.com/arhuman/maping/proto@$(VERSION) && go mod tidy) || exit 1; \
done
git commit -am "chore(release): client adapters $(VERSION)"
@for a in $(ADAPTERS); do git tag client/$$a/$(VERSION); done
@for a in $(ADAPTERS); do git push origin client/$$a/$(VERSION); done
@echo "released: proto/$(VERSION) client/$(VERSION) server/$(VERSION)$(foreach a,$(ADAPTERS), client/$(a)/$(VERSION))"
@echo "verify as a consumer: (cd /tmp && GOWORK=off go get github.com/arhuman/maping/client@$(VERSION))"
## logs: follow logs of a service (make logs service=clickhouse)
logs:
$(BASE_COMPOSE) logs -f $(service)
## restart: restart a service in the local stack (make restart service=maping-server)
restart:
$(LOCAL_COMPOSE) restart $(service)
# Bootstrap .env from the template on first use so make local / make up work
# out of the box. Never overwrites an existing .env.
$(ENV_FILE):
@cp env.sample $(ENV_FILE)
@echo "Created $(ENV_FILE) from env.sample — review it (secrets, OIDC) before prod."