Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
721 lines (618 loc) · 29.5 KB
/
Copy pathMakefile
File metadata and controls
721 lines (618 loc) · 29.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
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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
# Copyright 2018 The Trickster Authors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-include ci.mk release.mk
DEFAULT: build
PROJECT_DIR := $(shell pwd)
GO ?= go
GOFMT ?= $(GO)fmt
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
TRICKSTER_MAIN := cmd/trickster
TRICKSTER := $(FIRST_GOPATH)/bin/trickster
BUILD_TIME := $(shell date -u +%FT%T%z)
GIT_LATEST_COMMIT_ID ?= $(shell git rev-parse HEAD)
IMAGE_TAG ?= latest
IMAGE_ARCH ?= $(shell $(GO) env GOARCH)
GOARCH ?= $(shell $(GO) env GOARCH)
TAGVER ?= $(shell git describe --tags --dirty --always)
LDFLAGS =-ldflags "-extldflags '-static' -w -s -X main.applicationBuildTime=$(BUILD_TIME) -X main.applicationGitCommitID=$(GIT_LATEST_COMMIT_ID) -X main.applicationVersion=$(TAGVER)"
BUILD_SUBDIR := bin
CGO_ENABLED ?= 0
BUMPER_FILE := ./testdata/license_header_template.txt
THIRD_PARTY_LICENSES_DIR := $(BUILD_SUBDIR)/third-party-licenses
# `go tool` compiles for GOOS/GOARCH, so a cross-build must point this at a host-built binary
GO_LICENSES ?= $(GO) tool go-licenses
GOLANG_CI_LINT_VERSION ?= v2.14.0
.PHONY: go-mod-vendor
go-mod-vendor:
$(GO) mod vendor
.PHONY: go-mod-tidy
go-mod-tidy:
@set -e; for mod in $$(git ls-files '*go.mod'); do \
(cd "$$(dirname "$$mod")" && $(GO) mod tidy); \
done
.PHONY: test-go-mod
test-go-mod:
@git diff --quiet --exit-code go.mod go.sum || echo "There are changes to go.mod and go.sum that need to be committed"
# Modules whose LICENSE file go-licenses cannot classify. Each is excluded
# from automatic classification only -- its license notice is still copied
# verbatim into the generated distribution below, and its own dependencies
# are still classified -- so this narrows what the classifier is asked to
# recognize without narrowing what ships. Every entry needs a reason:
#
# github.com/segmentio/asm MIT-0 (MIT No Attribution), an OSI-approved
# license the bundled classifier has no template
# for; it is unrecognized at any confidence, and
# `save` takes no threshold flag anyway
#
# Remove an entry as soon as the module leaves the dependency graph -- the
# loop below fails on a stale one rather than skipping it, because a stale
# exception silently keeps a classifier waiver alive for a module that could
# come back under a different license.
UNCLASSIFIED_LICENSE_MODULES := \
github.com/segmentio/asm
.PHONY: third-party-licenses
third-party-licenses:
$(GO_LICENSES) save ./cmd/trickster \
--force \
--save_path=$(THIRD_PARTY_LICENSES_DIR) \
--ignore=github.com/trickstercache/trickster/v2 \
$(foreach m,$(UNCLASSIFIED_LICENSE_MODULES),--ignore=$(m))
@set -e; for m in $(UNCLASSIFIED_LICENSE_MODULES); do \
dir="$$($(GO) list -mod=mod -m -f '{{.Dir}}' $$m 2>/dev/null)" || { \
echo "$$m is listed in UNCLASSIFIED_LICENSE_MODULES but is no longer a dependency; remove it" >&2; \
exit 1; }; \
mkdir -p "$(THIRD_PARTY_LICENSES_DIR)/$$m"; \
cp "$$dir/LICENSE" "$(THIRD_PARTY_LICENSES_DIR)/$$m/LICENSE"; \
test -s "$(THIRD_PARTY_LICENSES_DIR)/$$m/LICENSE"; \
echo "copied license notice for $$m"; \
done
@test -s "$(THIRD_PARTY_LICENSES_DIR)/vitess.io/vitess/go/LICENSE"
.PHONY: check-third-party-licenses
check-third-party-licenses: third-party-licenses
@echo "verified release notices for Vitess and the complete linked dependency graph"
BUILD_FLAGS ?= -a -v
.PHONY: build
build: go-mod-tidy
CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(LDFLAGS) $(BUILD_FLAGS) -o ./$(BUILD_SUBDIR)/trickster $(TRICKSTER_MAIN)/*.go
rpm: build third-party-licenses
mkdir -p ./$(BUILD_SUBDIR)/SOURCES
cp -p ./$(BUILD_SUBDIR)/trickster ./$(BUILD_SUBDIR)/SOURCES/
cp deploy/systemd/trickster.service ./$(BUILD_SUBDIR)/SOURCES/
cp -p LICENSE NOTICE ./$(BUILD_SUBDIR)/SOURCES/
cp -R ./$(THIRD_PARTY_LICENSES_DIR) ./$(BUILD_SUBDIR)/SOURCES/
sed -e 's%^# log_file:.*$$%log_file: /var/log/trickster/trickster.log%' \
-e 's%prometheus:9090%localhost:9090%' \
< examples/conf/example.full.yaml > ./$(BUILD_SUBDIR)/SOURCES/trickster.yaml
rpmbuild --define "_topdir $(CURDIR)/$(BUILD_SUBDIR)" \
--define "_version $(TAGVER)" \
--define "_release 1" \
-ba deploy/packaging/trickster.spec
.PHONY: install
install:
$(GO) install -o $(TRICKSTER) $(TAGVER)
# Minikube and helm bootstrapping are done via deploy/helm/Makefile
.PHONY: helm-local
helm-local:
kubectl config use-context minikube --namespace=trickster
kubectl scale --replicas=0 deployment/dev-trickster -n trickster
eval $$(minikube docker-env) \
&& docker build -f Dockerfile -t trickster:dev .
kubectl set image deployment/dev-trickster trickster=trickster:dev -n trickster
kubectl scale --replicas=1 deployment/dev-trickster -n trickster
# Minikube and helm bootstrapping are done via deploy/kube/Makefile
.PHONY: kube-local
kube-local:
kubectl config use-context minikube
kubectl scale --replicas=0 deployment/trickster
eval $$(minikube docker-env) \
&& docker build -f Dockerfile -t trickster:dev .
kubectl set image deployment/trickster trickster=trickster:dev
kubectl scale --replicas=1 deployment/trickster
DOCKER_TARGET ?= final
.PHONY: docker
docker:
docker buildx build \
--progress=plain \
--build-arg GIT_LATEST_COMMIT_ID=$(GIT_LATEST_COMMIT_ID) \
--target $(DOCKER_TARGET) \
--build-arg TAGVER=$(TAGVER) \
-f ./Dockerfile \
-t trickster:$(TAGVER) \
--platform linux/$(IMAGE_ARCH) \
.
# the Dockerfile cross-compiles from BuildKit's TARGETARCH, so the target
# platform selects both the binary and the final base image
DOCKER_RELEASE_ARGS = --load --build-arg GIT_LATEST_COMMIT_ID=$(GIT_LATEST_COMMIT_ID) --build-arg TAGVER=$(TAGVER) -f ./Dockerfile
.PHONY: docker-release
docker-release:
# linux x86 image
docker build --platform linux/amd64 $(DOCKER_RELEASE_ARGS) -t trickstercache/trickster:$(IMAGE_TAG) .
# linux arm image
docker build --platform linux/arm64 $(DOCKER_RELEASE_ARGS) -t trickstercache/trickster:arm64v8-$(IMAGE_TAG) .
@for t in $(IMAGE_TAG):amd64 arm64v8-$(IMAGE_TAG):arm64; do \
want=$${t#*:}; got=$$(docker image inspect --format '{{.Architecture}}' trickstercache/trickster:$${t%:*}); \
[ "$$got" = "$$want" ] || { echo "trickstercache/trickster:$${t%:*} is $$got, expected $$want"; exit 1; }; \
done
.PHONY: style
style:
! gofmt -d $$(find . -path ./vendor -prune -o -name '*.go' -print) | grep '^'
.PHONY: check-imports
check-imports:
@go run hack/check-imports/main.go
# fails the build if weak randomness crosses the application/test boundary;
# pkg/util/weak/weaktest also panics at runtime once the application registers
.PHONY: check-weak-random
check-weak-random:
@go run hack/check-weak-random/main.go
.PHONY: gofix-apply
gofix-apply:
@go fix ./...
.PHONY: gofix-diff
gofix-diff:
@go fix -diff ./...
LINT_FLAGS ?=
# TOOL_MODULES are the separate Go modules of the developer tools, which lint and gotest cover too
TOOL_MODULES := hack/seedgen hack/druidseed hack/greptimeseed hack/vmseed hack/devorigin hack/mimirseed hack/diagstreamer
# tests are linted in a second pass by the rules that replaced the Makefile's scans, as the
# full set of linters is not run over them
TEST_LINTERS := forbidigo,depguard,godox,goheader
.PHONY: golangci-lint
golangci-lint:
@go tool golangci-lint run $(LINT_FLAGS) -c .golangci.yml
@go tool golangci-lint run $(LINT_FLAGS) --tests --enable-only $(TEST_LINTERS) -c .golangci.yml
@for m in $(TOOL_MODULES); do \
(cd $$m && go tool -modfile ../../go.mod golangci-lint run $(LINT_FLAGS) -c ../../.golangci.yml ./... && \
go tool -modfile ../../go.mod golangci-lint run $(LINT_FLAGS) --tests --enable-only $(TEST_LINTERS) \
-c ../../.golangci.yml ./...) || exit 1; \
done
.PHONY: lint
lint: check-imports check-weak-random spelling vulncheck gofix-diff golangci-lint
.PHONY: lint-all
lint-all:
@LINT_FLAGS="--new-from-rev= --fix=false" $(MAKE) lint
.PHONY: vulncheck
vulncheck:
@go tool govulncheck ./...
.PHONY: benchmark-mysql-smoke
benchmark-mysql-smoke:
@go test ./pkg/backends/mysql -run '^$$' -bench '^BenchmarkMySQLSmoke$$' -benchtime=1x -benchmem
.PHONY: benchmark-mysql
benchmark-mysql:
@go test ./pkg/backends/mysql -run '^$$' -bench '^BenchmarkMySQL' -benchmem -count=5
@go test ./pkg/backends/alb/mech/ur -run '^$$' -bench '^BenchmarkResolveRouteProtocolNeutral$$' -benchmem -count=5
# benchmark-redis compares the redis and valkey cache clients against the developer environment's
# servers; start it with COMPOSE_PROFILES=cache-cluster to include the Valkey cluster
.PHONY: benchmark-redis
benchmark-redis:
@TRICKSTER_REDIS_BENCH=1 go test ./pkg/cache/redis -run '^$$' -bench '^BenchmarkLive' -benchmem \
-count=$(or $(BENCH_COUNT),6) -timeout 3h
REDIS_COMPAT_IMAGES ?= redis:6.2-alpine valkey/valkey:9-alpine
REDIS_COMPAT_PORT ?= 6393
# test-redis-compat runs the redis cache provider and ACME storage against each REDIS_COMPAT_IMAGES
# server, started one at a time in a throwaway container on REDIS_COMPAT_PORT
.PHONY: test-redis-compat
test-redis-compat:
@set -e; for img in $(REDIS_COMPAT_IMAGES); do \
echo "== $$img"; \
docker run -d --rm --name trickster-redis-compat -p $(REDIS_COMPAT_PORT):6379 $$img >/dev/null; \
trap 'docker rm -f trickster-redis-compat >/dev/null 2>&1' EXIT; \
until docker exec trickster-redis-compat sh -c 'redis-cli ping 2>/dev/null || valkey-cli ping' 2>/dev/null | grep -q PONG; do sleep 0.2; done; \
TRICKSTER_REDIS_TEST=1 TRICKSTER_REDIS_COMPAT_ADDR=127.0.0.1:$(REDIS_COMPAT_PORT) go test -count=1 \
-run '^TestLiveCompat$$' ./pkg/cache/redis ./pkg/proxy/tls/acme/redisstorage; \
docker rm -f trickster-redis-compat >/dev/null; \
done
.PHONY: benchmark-flightsql-smoke
benchmark-flightsql-smoke:
@go test ./pkg/proxy/flightsql -run '^$$' -bench '^BenchmarkFlightSQLSmoke$$' -benchtime=1x -benchmem
.PHONY: benchmark-flightsql
benchmark-flightsql:
@go test ./pkg/proxy/flightsql -run '^$$' -bench '^BenchmarkFlightSQL' -benchmem -count=5
.PHONY: benchmark-graphite
benchmark-graphite:
@go test ./pkg/backends/graphite/resolution -run '^$$' -bench '^BenchmarkResolver' \
-benchmem -count=5
# DataSet build, cache codec and retained-heap measurements; run by hand, never on CI runners
DSBENCH_BUDGET_MB ?= 1024
DSBENCH_LOAD ?= 5s
.PHONY: benchmark-dataset
benchmark-dataset:
@go test ./pkg/timeseries/dataset/dsbench -run '^$$' -bench . -benchmem -count=6
@TRICKSTER_DSBENCH=1 TRICKSTER_DSBENCH_BUDGET_MB=$(DSBENCH_BUDGET_MB) TRICKSTER_DSBENCH_LOAD=$(DSBENCH_LOAD) \
go test ./pkg/timeseries/dataset/dsbench -run '^TestRetainedHeap$$' -v -count=1 -timeout 30m
.PHONY: benchmark-mysql-acceptance
benchmark-mysql-acceptance:
@go test ./pkg/backends/mysql -run '^$$' -bench '^BenchmarkMySQLCompatibilityCorpus$$' \
-benchmem -benchtime=200ms | tee /tmp/trickster-mysql-benchmarks.txt
@awk -f hack/check-mysql-benchmarks.awk /tmp/trickster-mysql-benchmarks.txt
.PHONY: lint-fix
lint-fix:
@go fix ./...
@LINT_FLAGS="--fix" $(MAKE) lint
@go tool golangci-lint fmt -c .golangci.yml
GO_TEST_FLAGS ?= -coverprofile=.coverprofile
GO_TEST_TOOL_FLAGS ?=
.PHONY: test
test: check-codegen check-weak-random gotest check-devorigin-offline
GO_TEST_PATH ?= $(shell $(GO) list ./... | grep -v v2/integration | tr '\n' ' ')
.PHONY: gotest
gotest:
$(GO) test -timeout=5m -v ${GO_TEST_FLAGS} $(GO_TEST_PATH)
@./hack/filter-coverprofile.sh .coverprofile
@for m in $(TOOL_MODULES); do (cd $$m && $(GO) test -timeout=5m $(GO_TEST_TOOL_FLAGS) ./...) || exit 1; done
@echo
@./hack/coverprofile-summary.sh
@echo "All tests passed successfully."
# The helper modules run with race detection, but their coverage does not overwrite the main profile.
.PHONY: test-cover-race
test-cover-race: GO_TEST_FLAGS = -race -covermode=atomic -coverprofile=.coverprofile
test-cover-race: GO_TEST_TOOL_FLAGS = -race
test-cover-race: test
.PHONY: data-race-test
data-race-test:
GO_TEST_FLAGS="-race" $(MAKE) test | tee race-output.log
.PHONY: data-race-test-inspect
data-race-test-inspect:
./hack/inspect-race-output.sh race-output.log
.PHONY: integration-test-local
integration-test-local:
$(MAKE) -C integration test-no-failfast
.PHONY: integration-test
integration-test:
$(MAKE) -C integration test
$(MAKE) -C integration data-race-test
.PHONY: integration-test-no-failfast
integration-test-no-failfast:
@status=0; \
$(MAKE) -C integration test-no-failfast || status=1; \
$(MAKE) -C integration data-race-test-no-failfast || status=1; \
exit $$status
.PHONY: integration-cover
integration-cover:
$(MAKE) -C integration cover
FUZZ_TIME ?= 30s
.PHONY: fuzz
fuzz:
@for pkg in $$($(GO) list ./... | grep -v /vendor/); do \
fuzz_funcs=$$($(GO) test -list 'Fuzz.*' $$pkg 2>/dev/null | grep '^Fuzz'); \
for fn in $$fuzz_funcs; do \
echo "fuzzing $$fn in $$pkg ($(FUZZ_TIME))"; \
$(GO) test -fuzz=$$fn -fuzztime=$(FUZZ_TIME) $$pkg || exit 1; \
done; \
done
.PHONY: bench
bench:
bash -c "$(GO) test -v -coverprofile=.coverprofile ./... -run=nonthingplease -bench=. | grep -v ' app=trickster '; exit ${PIPESTATUS[0]}"
.PHONY: test-cover
test-cover: test
$(GO) tool cover -html=.coverprofile
.PHONY: clean
clean:
rm -rf ./trickster ./$(BUILD_SUBDIR)
.PHONY: generate
generate: perform-generate insert-license-headers kube-configmap
.PHONY: perform-generate
perform-generate:
$(GO) generate ./pkg/... ./cmd/...
cd integration && $(GO) generate ./...
.PHONY: insert-license-headers
insert-license-headers:
@for file in $$(find ./pkg ./cmd ./integration -name '*.go') ; \
do \
output=$$(grep 'Licensed under the Apache License' $$file) ; \
if [ "$$?" != "0" ]; then \
echo "adding License Header Block to $$file" ; \
cat $(BUMPER_FILE) > /tmp/trktmp.go ; \
cat $$file >> /tmp/trktmp.go ; \
mv /tmp/trktmp.go $$file ; \
fi ; \
done
CODEGEN_PATHS ?= "'./pkg/**_gen.go'" deploy/kube/configmap.yaml
.PHONY: check-codegen
check-codegen:
@$(MAKE) generate > /dev/null
@git diff --name-only --exit-code ${CODEGEN_PATHS}
# deploy/kube/configmap.yaml is the example config wrapped in a ConfigMap;
# it is generated so the two can never drift, and check-codegen enforces it
.PHONY: kube-configmap
kube-configmap:
@hack/gen-kube-configmap.sh examples/conf/example.full.yaml deploy/kube/configmap.yaml
# the dev environment builds hack/devorigin in an offline container, compiling
# pkg/testutil/mocks from the repo, so those packages must stay stdlib-only
.PHONY: check-devorigin-offline
check-devorigin-offline:
@tmp=$$(mktemp -d) && trap 'chmod -R u+w "$$tmp"; rm -rf "$$tmp"' EXIT && \
cd hack/devorigin && GOMODCACHE="$$tmp" GOPROXY=off GOFLAGS=-mod=readonly $(GO) build -o /dev/null . && \
echo "hack/devorigin builds offline"
.PHONY: install-codespell
install-codespell:
# if brew is available, use it to install codespell
@which codespell ; \
if [ "$$?" != "0" ]; then \
if which brew ; then \
brew install codespell ; \
else \
echo "codespell is not installed and brew is not available to install it" ; \
fi ; \
else \
echo "codespell is already installed" ; \
fi
.PHONY: spelling
spelling:
@which codespell ; \
if [ "$$?" != "0" ]; then \
echo "codespell is not installed" ; \
else \
codespell --skip='trickster-data,vendor,bin,*.git,*.png,*.pdf,*.tiff,*.plist,*.pem,rangesim*.go,*.gz,go.sum,go.mod' --ignore-words='./testdata/ignore_words.txt' ; \
fi
.PHONY: serve
serve:
@cd cmd/trickster && go run . -config /etc/trickster/trickster.yaml
.PHONY: serve-debug
serve-debug:
@cd cmd/trickster && go run . -config /etc/trickster/trickster.yaml --log-level debug
.PHONY: serve-info
serve-info:
@cd cmd/trickster && go run . -config /etc/trickster/trickster.yaml --log-level info
.PHONY: serve-cli
serve-cli:
@cd cmd/trickster && go run . -origin-url http://127.0.0.1:9090/ -provider prometheus
# hack/diagstreamer generates a live diagnostic stream (it needs ffmpeg). DIAG_ARGS passes extra flags, such as
# DIAG_ARGS="-renditions 1280x720@3M -title Lab", and the DIAG_* environment variables it reads also apply.
DIAG_ARGS ?=
DIAG_MOQ_URL ?= moqt://localhost:8490/
DIAG_SNAPSHOT ?= diagstreamer.png
DIAG_IMAGE ?= trickster-diagstreamer
# serves live HLS and DASH, with a player page, at http://127.0.0.1:8496/
.PHONY: diagstreamer
diagstreamer:
@$(GO) -C hack/diagstreamer run ./cmd/diagstreamer $(DIAG_ARGS)
# publishes to the MoQ relay at DIAG_MOQ_URL, by default the developer Trickster's moq listener
.PHONY: diagstreamer-moq
diagstreamer-moq:
@$(GO) -C hack/diagstreamer run ./cmd/diagstreamer -out moq -moq-url $(DIAG_MOQ_URL) -moq-insecure $(DIAG_ARGS)
# writes the current frame to DIAG_SNAPSHOT and exits
.PHONY: diagstreamer-snapshot
diagstreamer-snapshot:
@$(GO) -C hack/diagstreamer run ./cmd/diagstreamer -snapshot $(abspath $(DIAG_SNAPSHOT)) $(DIAG_ARGS)
# serves the HTTP stream from the Docker image, which needs no local ffmpeg
.PHONY: diagstreamer-docker
diagstreamer-docker:
@docker build -q -t $(DIAG_IMAGE) hack/diagstreamer >/dev/null
@docker run --rm -it -p 127.0.0.1:8496:8496 -v "$(CURDIR)":/repo:ro $(DIAG_IMAGE) $(DIAG_ARGS)
.PHONY: get-tools
get-tools: get-msgpack
@echo "Installing tools..."
go get -tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANG_CI_LINT_VERSION)
.PHONY: get-msgpack
get-msgpack:
$(GO) get -tool github.com/tinylib/msgp@$(shell go list -m github.com/tinylib/msgp | cut -d' ' -f2)
# --- Developer environment ---------------------------------------------------
# Each TSDB backend in the compose file has its own profile; services without a
# profile (redis, valkey, grafana, prometheus, devorigin, jaeger) always start.
# COMPOSE_PROFILES takes a comma-separated list, e.g. COMPOSE_PROFILES=mysql,clickhouse
COMPOSE_ENV_DIR := docs/developer/environment
COREDNS_ZONES := $(COMPOSE_ENV_DIR)/docker-compose-data/coredns-zones
COMPOSE_PROFILES ?= all-providers
# stop and delete act on every profile, so they reach whatever is running
COMPOSE_ALL_PROFILES := *
DEVELOPER_PROFILES := influxdb clickhouse druid mysql timescaledb greptimedb questdb victoriametrics graphite \
mimir thanos moq cache-cluster
DEVELOPER_START_TARGETS := $(addprefix developer-start-,$(DEVELOPER_PROFILES))
# waits up to $(3)s for URL $(2) to respond, when service $(1) is in the active profiles
define wait_ready
@cd $(COMPOSE_ENV_DIR) && if COMPOSE_PROFILES='$(COMPOSE_PROFILES)' docker compose config --services | grep -qx '$(1)'; then \
echo "Waiting for $(1) to be ready..."; \
timeout $(3) sh -c 'until curl -sf "$(2)" >/dev/null 2>&1; do sleep 2; done'; \
fi
endef
.PHONY: check-docker
check-docker:
@docker info >/dev/null 2>&1 || { \
echo "Docker daemon is unavailable; start Docker and retry." >&2; exit 1; \
}
.PHONY: developer-credentials
developer-credentials:
@sh hack/developer-credentials.sh
.PHONY: developer-start
developer-start: check-docker
@$(MAKE) developer-credentials
@echo "Starting developer environment with profiles: $(COMPOSE_PROFILES)"
@cd $(COMPOSE_ENV_DIR) && COMPOSE_PROFILES='$(COMPOSE_PROFILES)' docker compose up -d
@cd $(COMPOSE_ENV_DIR) && for svc in redis:redis-cli valkey:valkey-cli; do \
name=$${svc%%:*}; cli=$${svc#*:}; attempts=0; ready=; \
echo "Waiting for $$name to be ready..."; \
while [ $$attempts -lt 30 ]; do \
response=$$(docker compose exec -T $$name $$cli ping 2>&1 || true); \
echo "PING -> $${response:-no response}"; \
case "$$response" in *PONG*) ready=1; break;; esac; \
attempts=$$((attempts + 1)); \
sleep 1; \
done; \
[ -n "$$ready" ] || echo "WARNING: timed out waiting for $$name readiness; continuing anyway"; \
done
@echo "Waiting for prometheus to be ready..."
@timeout 120 sh -c 'until curl -sf http://127.0.0.1:9090/-/ready >/dev/null 2>&1; do sleep 2; done'
@# devorigin compiles on start, so its container runs well before it serves
@echo "Waiting for devorigin to be ready..."
@timeout 180 sh -c 'until curl -sf http://127.0.0.1:8482/metrics >/dev/null 2>&1; do sleep 2; done'
$(call wait_ready,graphite,http://127.0.0.1:8081/metrics/find?query=carbon,120)
$(call wait_ready,druid,http://127.0.0.1:8888/status/health,180)
$(call wait_ready,victoriametrics,http://127.0.0.1:8428/health,120)
$(call wait_ready,mimir_east,http://127.0.0.1:11080/ready,120)
$(call wait_ready,mimir_west,http://127.0.0.1:11081/ready,120)
$(call wait_ready,thanos_query,http://127.0.0.1:11903/-/ready,120)
# developer-start-<profile> starts the always-on services plus that one backend
.PHONY: $(DEVELOPER_START_TARGETS)
$(DEVELOPER_START_TARGETS): developer-start-%:
@$(MAKE) developer-start COMPOSE_PROFILES=$*
# developer-moq runs the MoQ harness: moq-dev's relay, diagstreamer publishing into it, and Trickster in the
# foreground serving the player at https://localhost:8583/player/, which developer-moq-browser opens in Chrome
.PHONY: developer-moq
developer-moq: dev-certs
@hack/developer-moq.sh up $(ARGS)
.PHONY: developer-moq-browser
developer-moq-browser: dev-certs
@hack/developer-moq.sh browser
.PHONY: developer-stop
developer-stop:
@cd $(COMPOSE_ENV_DIR) && COMPOSE_PROFILES='$(COMPOSE_ALL_PROFILES)' docker compose stop
# --- Integration environment -------------------------------------------------
# The integration profile is every TSDB backend plus the integration-only
# services (e.g. coredns for the autodiscovery DNS tests).
INTEGRATION_PROFILE := integration
# one-shot loaders no service waits on, so developer-start can return while they still load
INTEGRATION_SEEDERS := clickhouse_seed druid_seed greptimedb_seed influxdb2_seed mysql_seed timescaledb_seed questdb_seed \
victoriametrics_seed
.PHONY: integration-start
integration-start: check-docker
@mkdir -p $(COREDNS_ZONES)
@cp -f $(COMPOSE_ENV_DIR)/docker-compose-data/coredns/trickster.test.db.seed \
$(COREDNS_ZONES)/trickster.test.db
@$(MAKE) developer-start COMPOSE_PROFILES=$(INTEGRATION_PROFILE)
@cd $(COMPOSE_ENV_DIR) && for id in $$(COMPOSE_PROFILES=$(INTEGRATION_PROFILE) docker compose ps -q --status running $(INTEGRATION_SEEDERS)); do \
echo "Waiting for seeder $$id to finish..."; \
status=$$(docker wait $$id); \
if [ "$$status" != 0 ]; then echo "seeder $$id failed (exit $$status)" >&2; exit 1; fi; \
done
.PHONY: integration-stop
integration-stop: developer-stop
.PHONY: integration-delete
integration-delete: developer-delete
.PHONY: developer-delete
developer-delete:
@cd $(COMPOSE_ENV_DIR) && COMPOSE_PROFILES='$(COMPOSE_ALL_PROFILES)' docker compose down -v --remove-orphans
# deletes every profile's containers and volumes, then starts COMPOSE_PROFILES;
# sequential sub-makes keep make -j from running the two concurrently
.PHONY: developer-recreate
developer-recreate:
@$(MAKE) developer-delete
@$(MAKE) developer-start COMPOSE_PROFILES='$(COMPOSE_PROFILES)'
.PHONY: dev-certs
dev-certs:
@hack/dev-certs.sh
.PHONY: h3-client
h3-client:
@$(GO) run ./hack/h3-client $(ARGS)
.PHONY: developer-seed-data
developer-seed-data: developer-credentials
@hack/developer-seed-data.sh
# regenerates the synthetic seed data to memory only and fails if its hash
# differs from the one pinned in hack/seedgen; no network access is needed
.PHONY: seed-verify
seed-verify:
@cd hack/seedgen && $(GO) run . -verify-only
# generates the synthetic seed data natively (no container) into the shared
# seed-data directory the database seeders mount; SEED_PROFILE=small for a
# tenth of the rows, SEED_FORCE=1 to overwrite a valid cached output
.PHONY: seed-generate
seed-generate:
@cd hack/seedgen && $(GO) run . -out ../../docs/developer/environment/docker-compose-data/seed-data \
$(if $(SEED_PROFILE),-profile $(SEED_PROFILE),) $(if $(SEED_FORCE),-force,)
# Read-only direct GreptimeDB acceptance; does not start or reseed services.
.PHONY: developer-greptimedb-check
developer-greptimedb-check:
@GO="$(GO)" sh hack/greptimedb-check.sh
RUN_FLAGS ?=
.PHONY: serve-dev
serve-dev: developer-credentials
@go run $(RUN_FLAGS) cmd/trickster/main.go -config $(if $(TRK_CONFIG),$(TRK_CONFIG),docs/developer/environment/trickster-config/trickster.generated.yaml)
serve-dev-data-race:
RUN_FLAGS=-race $(MAKE) serve-dev 2>&1 | tee race-output.log
# --- Kubernetes autodiscovery integration scenario ---------------------------
# Creates the kind cluster for integration/kind (TestALBDiscoveryKind),
# builds the trickster image, loads it, and deploys the manifests.
KIND_CLUSTER := trickster-it
.PHONY: kind-integration-start
kind-integration-start:
kind create cluster --config integration/kind/kind-config.yaml
docker build -t trickster:integration .
kind load docker-image trickster:integration --name $(KIND_CLUSTER)
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/manifests.yaml
kubectl --context kind-$(KIND_CLUSTER) -n trickster-it rollout status \
deployment/webecho deployment/trickster --timeout=180s
# Deploys the Kubernetes controller scenarios (TestIngressEndpointModeKind,
# TestCachePolicyKind and the manual Ingress smoke test) into the running
# kind cluster. The cache policy CRD goes first: the controller probes for
# it once at startup.
.PHONY: kind-integration-ingress
kind-integration-ingress:
kubectl --context kind-$(KIND_CLUSTER) apply -f deploy/kube/crds/trickstercachepolicies.yaml
kubectl --context kind-$(KIND_CLUSTER) wait --for=condition=Established \
crd/trickstercachepolicies.trickstercache.org --timeout=60s
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/ingress.yaml
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/ingress-endpoint.yaml
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/cache-policy.yaml
kubectl --context kind-$(KIND_CLUSTER) -n trickster-it rollout status \
deployment/trickster-ingress deployment/trickster-ingress-endpoint \
deployment/prometheus --timeout=180s
# Deploys Gateway scenarios with the conformance module's pinned experimental CRDs.
# Install CRDs before startup and expose Gateway ports through the worker node.
.PHONY: kind-integration-gateway
kind-integration-gateway:
@crds=$$(cd integration/conformance && $(GO) mod download sigs.k8s.io/gateway-api && \
$(GO) list -m -f '{{.Dir}}' sigs.k8s.io/gateway-api) && \
kubectl --context kind-$(KIND_CLUSTER) apply --server-side -k "$$crds/config/crd/experimental"
kubectl --context kind-$(KIND_CLUSTER) wait --for=condition=Established crd --all --timeout=60s
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/gateway.yaml
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/gateway-moq.yaml
kubectl --context kind-$(KIND_CLUSTER) apply -f integration/kind/ingress-annotations.yaml
@ip=$$(kubectl --context kind-$(KIND_CLUSTER) get node $(KIND_CLUSTER)-worker \
-o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') && \
kubectl --context kind-$(KIND_CLUSTER) -n trickster-it patch service trickster-gateway \
-p "{\"spec\":{\"externalIPs\":[\"$$ip\"]}}"
kubectl --context kind-$(KIND_CLUSTER) -n trickster-it rollout status \
deployment/trickster-gateway deployment/webecho-canary deployment/httpbin \
deployment/moq-relay --timeout=180s
# Runs every kind scenario against the running cluster, once all three deploy
# targets above have been applied
.PHONY: kind-integration-test
kind-integration-test:
cd integration && TRICKSTER_KIND_TEST=1 $(GO) test -v -timeout 30m -run 'Kind$$' .
# Runs the Gateway API conformance suite (GATEWAY-HTTP profile) against the
# controller kind-integration-gateway deployed and writes the report. The
# suite reaches the Gateways at the worker node's address, which a Linux host
# routes to directly; a macOS host cannot, so kind-conformance-docker runs the
# same command inside a container on the kind network.
CONFORMANCE_REPORT ?= integration/conformance/reports/$(TAGVER).yaml
CONFORMANCE_TEST_ARGS = -v -timeout 90m -run TestConformance . -args -gateway-class=trickster -version=$(TAGVER)
.PHONY: kind-conformance
kind-conformance:
@mkdir -p $(dir $(CONFORMANCE_REPORT))
@tmp=$$(mktemp -d) && kind get kubeconfig --name $(KIND_CLUSTER) > $$tmp/config && \
cd integration/conformance && KUBECONFIG=$$tmp/config TRICKSTER_KIND_TEST=1 \
$(GO) test $(CONFORMANCE_TEST_ARGS) -report-output=$(abspath $(CONFORMANCE_REPORT))
.PHONY: kind-conformance-docker
kind-conformance-docker:
@mkdir -p $(dir $(CONFORMANCE_REPORT))
@tmp=$$(mktemp -d) && kind get kubeconfig --internal --name $(KIND_CLUSTER) > $$tmp/config && \
docker run --rm --network kind -v $$tmp:/kube:ro -v $(CURDIR):/src -w /src/integration/conformance \
-v trickster-conformance-gomod:/go/pkg/mod -v trickster-conformance-gocache:/root/.cache/go-build \
-e KUBECONFIG=/kube/config -e TRICKSTER_KIND_TEST=1 golang:1.27 \
go test $(CONFORMANCE_TEST_ARGS) -report-output=/src/$(CONFORMANCE_REPORT)
.PHONY: kind-integration-stop
kind-integration-stop:
kind delete cluster --name $(KIND_CLUSTER)
# Soak tests run by hand, never on CI runners. TestALBDiscoverySoak needs
# nothing but the host; TestGatewaySoakKind needs the kind cluster with the
# gateway scenarios deployed. SOAK_TIMEOUT must exceed SOAK_DURATION by the
# time the soak takes to set up and wind down.
SOAK_DURATION ?= 60m
SOAK_TIMEOUT ?= 90m
.PHONY: soak
soak:
cd integration && TRICKSTER_SOAK_TEST=1 TRICKSTER_SOAK_DURATION=$(SOAK_DURATION) \
$(GO) test -v -timeout $(SOAK_TIMEOUT) -run 'TestALBDiscoverySoak$$' .
.PHONY: kind-soak
kind-soak:
cd integration && TRICKSTER_SOAK_TEST=1 TRICKSTER_KIND_TEST=1 TRICKSTER_SOAK_DURATION=$(SOAK_DURATION) \
$(GO) test -v -timeout $(SOAK_TIMEOUT) -run 'TestGatewaySoakKind$$' .