-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
376 lines (309 loc) · 12.2 KB
/
Copy pathMakefile
File metadata and controls
376 lines (309 loc) · 12.2 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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
.PHONY: all build run-api run-worker run-scheduler test clean deps infra infra-down test-coverage-ci check-infra
# Build all binaries
all: build
build:
go build -o bin/howk-api ./cmd/api
go build -o bin/howk-worker ./cmd/worker
go build -o bin/howk-scheduler ./cmd/scheduler
# Run individual components
run-api:
go run ./cmd/api
run-worker:
go run ./cmd/worker
run-scheduler:
go run ./cmd/scheduler
# Run dev mode (single process, no Kafka/Redis needed)
run-dev:
go run ./cmd/dev
run-dev-dry:
go run ./cmd/dev --dry
run-dev-scripts:
go run ./cmd/dev --scripts-dir=./scripts
# Run all components together (for development)
run-all:
@echo "Starting all components..."
@make run-api &
@sleep 2
@make run-worker &
@sleep 1
@make run-scheduler &
@wait
# Tests
test-unit:
go test -v -race -short ./...
test: test-unit
# Coverage packages to exclude (test utilities, mocks, and stress-test binaries)
COVERAGE_EXCLUDES := cmd/ internal/mocks/ internal/testutil/ bench/
# Filter coverage file to exclude certain packages
define filter_coverage
grep -v -E "$(shell echo $(COVERAGE_EXCLUDES) | tr ' ' '|')" coverage.out > coverage.filtered.out
mv coverage.filtered.out coverage.out
endef
# Unit test coverage
test-unit-coverage:
go test -short -coverprofile=coverage.out -coverpkg=./... ./...
$(call filter_coverage)
go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report: coverage.html"
go tool cover -func=coverage.out | grep total
# Integration tests (requires infrastructure)
test-integration:
go test -v -race -count=1 -tags=integration ./...
# E2E tests (requires all services)
test-e2e:
go test -v -race -tags=e2e ./...
# Run all tests
test-all: test-unit test-integration test-e2e
# CI test pipeline (unit + integration)
test-ci: infra
@echo "Waiting for services..."
@sleep 15
go test -v -race -count=1 -tags=integration -timeout=10m ./...
test-coverage:
go test -coverprofile=coverage.out -coverpkg=./... ./...
$(call filter_coverage)
go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report: coverage.html"
@go tool cover -func=coverage.out | grep total
# Check if infrastructure is running (Kafka on 19092, Redis on 6380)
check-infra:
@echo "Checking infrastructure..."
@if ! nc -z localhost 19092 2>/dev/null; then \
echo "Kafka not running on port 19092. Starting infrastructure..."; \
$(MAKE) infra; \
echo "Waiting for services to be ready..."; \
sleep 15; \
elif ! nc -z localhost 6380 2>/dev/null; then \
echo "Redis not running on port 6380. Starting infrastructure..."; \
$(MAKE) infra; \
echo "Waiting for services to be ready..."; \
sleep 15; \
else \
echo "Infrastructure is already running!"; \
fi
# Combined coverage for CI (unit + integration merged, auto-starts infrastructure if needed)
test-coverage-ci: check-infra .ensure-gocovmerge
@echo "Running unit tests..."
go test -race -short -coverprofile=coverage_unit.out -coverpkg=./... -count=1 ./...
@echo "Running integration tests..."
go test -race -coverprofile=coverage_integration.out -coverpkg=./... -count=1 -tags=integration -timeout=10m ./...
@echo "Merging coverage reports..."
@./scripts/merge-coverage.sh coverage_unit.out coverage_integration.out > coverage.out
$(call filter_coverage)
go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report: coverage.html"
@go tool cover -func=coverage.out | grep total
@rm -f coverage_unit.out coverage_integration.out
# Install gocovmerge if not present
.ensure-gocovmerge:
@which gocovmerge > /dev/null 2>&1 || (echo "Installing gocovmerge..." && go install github.com/wadey/gocovmerge@latest)
# Local coverage with merged unit + integration (for development)
test-coverage-full: check-infra .ensure-gocovmerge
@echo "Running unit tests..."
go test -short -coverprofile=coverage_unit.out -coverpkg=./... ./...
@echo "Running integration tests..."
go test -coverprofile=coverage_integration.out -coverpkg=./... -tags=integration ./...
@echo "Merging coverage reports..."
@./scripts/merge-coverage.sh coverage_unit.out coverage_integration.out > coverage.out
$(call filter_coverage)
go tool cover -html=coverage.out -o coverage.html
@echo "Full coverage report (unit + integration): coverage.html"
@go tool cover -func=coverage.out | grep total
@rm -f coverage_unit.out coverage_integration.out
# Dependencies
deps:
go mod download
go mod tidy
# Infrastructure
infra:
docker-compose up -d
@echo "Waiting for services to be ready..."
@sleep 10
@echo "Infrastructure ready!"
@echo " Kafka: localhost:19092"
@echo " Redis: localhost:6380 (Docker)"
@echo " Console: http://localhost:8888"
@echo " Echo server: http://localhost:8090"
infra-down:
docker-compose down
infra-clean:
docker-compose down -v
# Development helpers
lint:
golangci-lint run
fmt:
gofmt -s -w .
# Quick test with curl
test-enqueue:
curl -X POST http://localhost:8080/webhooks/tenant123/enqueue \
-H "Content-Type: application/json" \
-d '{"endpoint": "http://localhost:8090/webhook", "payload": {"event": "test.event", "data": {"id": 123}}}'
test-status:
@echo "Usage: make test-status ID=wh_xxx"
curl http://localhost:8080/webhooks/$(ID)/status
test-stats:
curl http://localhost:8080/stats
# Clean up
clean:
rm -rf bin/
rm -f coverage.out coverage.html coverage_unit.out coverage_integration.out coverage.filtered.out
# =============================================================================
# Docker Hub Publishing
# =============================================================================
DOCKER_HUB_USER ?= renatocron
# VERSION defaults to the most recent annotated git tag (e.g. v0.4.7). The
# Helm chart pins v-prefixed tags, so the guard below blocks accidental pushes
# of a bare semver (which produced the v0.4.7 ImagePullBackOff incident).
# Override explicitly when cutting a pre-release: make dockerhub-push VERSION=v0.4.8-rc1
VERSION ?= $(shell git describe --tags --abbrev=0 2>/dev/null)
# Guard: VERSION must be "latest" or start with "v". Prevents bare-semver
# tags (e.g. "0.4.7") that won't match the Helm chart's `v0.4.7` pin.
_check-docker-version:
@if [ -z "$(VERSION)" ]; then \
echo "ERROR: VERSION is empty and no git tag was found."; \
echo "Pass VERSION=v0.x.y on the command line, or create an annotated tag first."; \
exit 1; \
fi
@case "$(VERSION)" in \
latest|v*) ;; \
*) echo "ERROR: VERSION must start with 'v' (got: $(VERSION))."; \
echo "The Helm chart pins v-prefixed tags. Try VERSION=v$(VERSION) or omit VERSION to use the latest git tag."; \
exit 1;; \
esac
# Build all images for Docker Hub
dockerhub-build: _check-docker-version
@echo "Building images for Docker Hub ($(DOCKER_HUB_USER)) tag=$(VERSION)..."
docker build --target api -t $(DOCKER_HUB_USER)/howk-api:$(VERSION) .
docker build --target worker -t $(DOCKER_HUB_USER)/howk-worker:$(VERSION) .
docker build --target scheduler -t $(DOCKER_HUB_USER)/howk-scheduler:$(VERSION) .
docker build --target reconciler -t $(DOCKER_HUB_USER)/howk-reconciler:$(VERSION) .
# Push all images to Docker Hub
dockerhub-push: dockerhub-build
@echo "Pushing images to Docker Hub tag=$(VERSION)..."
docker push $(DOCKER_HUB_USER)/howk-api:$(VERSION)
docker push $(DOCKER_HUB_USER)/howk-worker:$(VERSION)
docker push $(DOCKER_HUB_USER)/howk-scheduler:$(VERSION)
docker push $(DOCKER_HUB_USER)/howk-reconciler:$(VERSION)
# Build and push multi-arch images (requires buildx)
dockerhub-buildx: _check-docker-version
@echo "Building multi-arch images for Docker Hub tag=$(VERSION)..."
docker buildx build --platform linux/amd64,linux/arm64 --target api -t $(DOCKER_HUB_USER)/howk-api:$(VERSION) --push .
docker buildx build --platform linux/amd64,linux/arm64 --target worker -t $(DOCKER_HUB_USER)/howk-worker:$(VERSION) --push .
docker buildx build --platform linux/amd64,linux/arm64 --target scheduler -t $(DOCKER_HUB_USER)/howk-scheduler:$(VERSION) --push .
docker buildx build --platform linux/amd64,linux/arm64 --target reconciler -t $(DOCKER_HUB_USER)/howk-reconciler:$(VERSION) --push .
# Tag and push as latest
dockerhub-release: VERSION=latest
dockerhub-release: dockerhub-push
@echo "Released $(VERSION) to Docker Hub"
# Full release: push the versioned tag (from the latest git tag) and update :latest.
# Run from a tagged commit. The version guard ensures the Helm chart's `v0.4.x`
# pin will resolve on Docker Hub.
release:
@VERSION=$$(git describe --tags --abbrev=0 2>/dev/null); \
if [ -z "$$VERSION" ]; then echo "ERROR: no git tag found; tag the release first."; exit 1; fi; \
echo "==> Releasing $$VERSION (versioned + latest) to Docker Hub..."; \
$(MAKE) dockerhub-push VERSION=$$VERSION; \
$(MAKE) dockerhub-release
# =============================================================================
# Helm Chart
# =============================================================================
# Package Helm chart
helm-package:
@echo "Packaging Helm chart..."
helm package charts/howk -d charts/
# Install HOWK via Helm
helm-install:
@echo "Installing HOWK via Helm..."
helm upgrade --install howk ./charts/howk -n howk --create-namespace
# Install with custom values
helm-install-custom:
@echo "Installing HOWK with custom values..."
helm upgrade --install howk ./charts/howk -n howk --create-namespace -f my-values.yaml
# Uninstall HOWK
helm-uninstall:
@echo "Uninstalling HOWK..."
helm uninstall howk -n howk
# Lint Helm chart
helm-lint:
@echo "Linting Helm chart..."
helm lint charts/howk
# Template Helm chart (for debugging)
helm-template:
@echo "Rendering Helm templates..."
helm template howk ./charts/howk
# =============================================================================
# Kubernetes Operations
# =============================================================================
# Build Docker images for Kubernetes
docker-build:
docker build --target api -t howk:api-latest .
docker build --target worker -t howk:worker-latest .
docker build --target scheduler -t howk:scheduler-latest .
docker build --target reconciler -t howk:reconciler-latest .
docker-build-api:
docker build --target api -t howk:latest .
# Build stress test image
docker-build-stress:
docker build -t howk-stress-test:latest -f k8s/stress-test/Dockerfile k8s/stress-test/
# Deploy to Kubernetes
k8s-deploy: docker-build-api
kubectl apply -k k8s/
@echo "Waiting for infrastructure..."
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=redis -n howk --timeout=120s
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=redpanda -n howk --timeout=120s
@echo "Waiting for topic initialization..."
kubectl wait --for=condition=complete job/redpanda-topics-init -n howk --timeout=120s || true
@echo "Deploying HOWK services..."
kubectl apply -k k8s/
@echo ""
@echo "Deployment complete! Services:"
@echo " API: kubectl port-forward svc/api -n howk 8080:8080"
# Deploy stress test infrastructure
k8s-deploy-stress: docker-build-stress
kubectl apply -k k8s/stress-test/
@echo "Stress test deployed!"
# Run stress test
k8s-stress-test: docker-build-stress
kubectl delete job stress-test -n howk 2>/dev/null || true
kubectl apply -k k8s/stress-test/
@echo "Waiting for stress test to complete..."
kubectl wait --for=condition=complete job/stress-test -n howk --timeout=300s
@echo "Stress test logs:"
kubectl logs job/stress-test -n howk
# View stress test logs
k8s-stress-logs:
kubectl logs job/stress-test -n howk
# Delete all resources
k8s-delete:
kubectl delete -k k8s/stress-test/ --ignore-not-found=true
kubectl delete -k k8s/ --ignore-not-found=true
# Port forward API for local access
k8s-port-forward:
@echo "Forwarding API to localhost:8080..."
kubectl port-forward svc/api -n howk 8080:8080
# View logs
k8s-logs-api:
kubectl logs -l app.kubernetes.io/name=api -n howk --tail=100 -f
k8s-logs-worker:
kubectl logs -l app.kubernetes.io/name=worker -n howk --tail=100 -f
k8s-logs-scheduler:
kubectl logs -l app.kubernetes.io/name=scheduler -n howk --tail=100 -f
# Scale workers
k8s-scale-workers:
@echo "Usage: make k8s-scale-workers REPLICAS=5"
kubectl scale deployment worker -n howk --replicas=$(REPLICAS)
# Check status
k8s-status:
@echo "=== Pods ==="
kubectl get pods -n howk
@echo ""
@echo "=== Services ==="
kubectl get svc -n howk
@echo ""
@echo "=== Jobs ==="
kubectl get jobs -n howk
# Full reset (delete and redeploy)
k8s-reset: k8s-delete
@echo "Waiting for cleanup..."
@sleep 5
$(MAKE) k8s-deploy