-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathMakefile
More file actions
623 lines (528 loc) · 27.5 KB
/
Makefile
File metadata and controls
623 lines (528 loc) · 27.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
###----------------------------------------
## Variables
#------------------------------------------
# Version from git tags (for root module - operator binary)
# Root module uses tags like v0.1.0 (without prefix)
VERSION ?= $(shell git describe --tags --match "v*" --always --dirty 2>/dev/null || echo "v0.0.1-dev")
VERSION_SHORT ?= $(shell echo $(VERSION) | sed 's/^v//')
# Image configuration
# IMG is pinned to the last-built tag (written by the container target) when available.
# This prevents tag drift between docker build and kind load caused by git state
# changes (staging files, new commits) between make invocations.
IMG_PREFIX ?= ghcr.io/numtide
IMG_REPO ?= multigres-operator
IMG_TAG_FILE ?= .last-built-tag
IMG ?= $(if $(wildcard $(IMG_TAG_FILE)),$(IMG_PREFIX)/$(IMG_REPO):$(shell cat $(IMG_TAG_FILE)),$(IMG_PREFIX)/$(IMG_REPO):$(VERSION_SHORT))
# Observer tool configuration
OBSERVER_IMG ?= $(if $(wildcard $(IMG_TAG_FILE)),$(IMG_PREFIX)/multigres-observer:$(shell cat $(IMG_TAG_FILE)),$(IMG_PREFIX)/multigres-observer:$(VERSION_SHORT))
.PHONY: print-img
print-img: ## Print the full operator container image reference
@echo $(IMG)
# Images required by MultigresCluster pods (must match pkg/testutil/e2e.go MultigresImages)
E2E_IMAGES ?= ghcr.io/multigres/multigres:main ghcr.io/multigres/pgctld:main ghcr.io/multigres/multiadmin-web:main gcr.io/etcd-development/etcd:v3.6.7
.PHONY: pull-e2e-images
pull-e2e-images: ## Pull container images needed by e2e tests
@for img in $(E2E_IMAGES); do \
echo "Pulling $$img..."; \
docker pull $$img; \
done
# Build metadata
BUILD_DATE ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
GIT_COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null || echo "unknown")
# LDFLAGS for version info
LDFLAGS := -X main.version=$(VERSION) \
-X main.buildDate=$(BUILD_DATE) \
-X main.gitCommit=$(GIT_COMMIT)
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
# CONTAINER_TOOL defines the container tool to be used for building images.
# Be aware that the target commands are only tested with Docker which is
# scaffolded by default. However, you might want to replace it to use other
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker
# Kind cluster name for local development
KIND_CLUSTER ?= multigres-operator-dev
# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
# CertManager is installed by default; skip with:
# - CERT_MANAGER_INSTALL_SKIP=true
# Local kubeconfig for kind cluster (doesn't modify user's ~/.kube/config)
KIND_KUBECONFIG ?= $(shell pwd)/kubeconfig.yaml
# Upstream images used by MultigresCluster data plane components.
# Pre-pulled to local Docker cache and loaded into kind to avoid slow pulls
# inside the cluster on every kind-deploy cycle.
MULTIGRES_IMAGES ?= $(shell sed -n 's/.*= "\(.*\)"$$/\1/p' api/v1alpha1/image_defaults.go | sort -u)
# Observability stack images, pre-loaded only for kind-deploy-observability.
OBSERVABILITY_IMAGES ?= \
grafana/grafana:11.4.0 \
grafana/tempo:2.7.2 \
otel/opentelemetry-collector-contrib:0.120.0
# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
KUBECTL ?= kubectl
KIND ?= kind
KUSTOMIZE ?= $(LOCALBIN)/kustomize
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
## Tool Versions
# renovate: datasource=github-releases depName=kubernetes-sigs/kustomize
KUSTOMIZE_VERSION ?= v5.6.0
# renovate: datasource=github-releases depName=kubernetes-sigs/controller-tools
CONTROLLER_TOOLS_VERSION ?= v0.18.0
# renovate: datasource=github-releases depName=golangci/golangci-lint
GOLANGCI_LINT_VERSION ?= v2.9.0
CERT_MANAGER_VERSION ?= v1.19.2
## Envtest
#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
# NOTE: This version should match the version defined in devshell.nix
ENVTEST_K8S_VERSION ?= 1.33
###----------------------------------------
## Comamnds
#------------------------------------------
.PHONY: all
all: build
##@ General
# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk command is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI control characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ Development
.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./api/...;./pkg/webhook/..." output:crd:artifacts:config=config/crd/bases output:rbac:artifacts:config=config/rbac output:webhook:artifacts:config=config/webhook
.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object paths="./api/...;./pkg/webhook/..."
# $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
.PHONY: fmt
fmt: ## Run go fmt against code
go fmt ./...
.PHONY: vet
vet: ## Run go vet against code
go vet ./...
.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
$(GOLANGCI_LINT) run
.PHONY: lint-fix
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
$(GOLANGCI_LINT) run --fix
.PHONY: lint-config
lint-config: golangci-lint ## Verify golangci-lint linter configuration
$(GOLANGCI_LINT) config verify
##@ Code Quality
.PHONY: check
check: lint test ## Run all checks before committing (lint + test)
@echo "==> All checks passed!"
.PHONY: verify
verify: manifests generate ## Verify generated files are up to date
@echo "==> Verifying generated files are committed"
@git diff --exit-code config/crd api/ || { \
echo "ERROR: Generated files are out of date."; \
echo "Run 'make manifests generate' and commit the changes."; \
exit 1; \
}
@echo "==> Verification passed!"
.PHONY: verify-warn
verify-warn: manifests generate ## Verify generated files are up to date (non-blocking, warns only)
@echo "==> Verifying generated files are committed"
@if ! git diff --exit-code config/crd api/ >/dev/null 2>&1; then \
echo ""; \
echo "========================================"; \
echo "WARNING: Generated files are out of date"; \
echo "========================================"; \
echo ""; \
echo "The following files have changes:"; \
git diff --stat config/crd api/; \
echo ""; \
echo "This is not blocking the build, but you should run"; \
echo "'make manifests generate' and commit the changes"; \
echo "before merging to ensure CRDs are synchronized."; \
echo ""; \
echo "========================================"; \
exit 0; \
else \
echo "==> Verification passed!"; \
fi
.PHONY: pre-commit
pre-commit: fmt vet lint test ## Run full pre-commit checks (fmt, vet, lint, test)
@echo "==> Pre-commit checks passed!"
##@ Build
.PHONY: build
build: manifests generate fmt vet ## Build manager binary with version metadata
@echo "==> Building operator binary (version: $(VERSION))"
go build -ldflags="$(LDFLAGS)" -o bin/multigres-operator cmd/multigres-operator/main.go
.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/multigres-operator/main.go
# Cross-platform builds are handled natively via --platform=$BUILDPLATFORM in the Dockerfile.
# Use docker-buildx target or pass --platform to docker build for multi-arch images.
.PHONY: container
container: ## Build container image
$(eval BUILT_TAG := $(VERSION_SHORT))
$(CONTAINER_TOOL) build \
--build-arg VERSION=$$(git rev-parse --short HEAD) \
--build-arg GIT_COMMIT=$$(git rev-parse HEAD) \
--build-arg BUILD_DATE=$$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t $(IMG_PREFIX)/$(IMG_REPO):$(BUILT_TAG) .
@echo $(BUILT_TAG) > $(IMG_TAG_FILE)
.PHONY: minikube-load
minikube-load:
minikube image load ${IMG}
.PHONY: container-push
container-push: ## Push container image
$(CONTAINER_TOOL) push ${IMG}
# PLATFORMS defines the target platforms for the manager image.
PLATFORMS ?= linux/arm64,linux/amd64
.PHONY: docker-buildx
docker-buildx: ## Build and push docker image for cross-platform support
- $(CONTAINER_TOOL) buildx create --name multigres-operator-builder
$(CONTAINER_TOOL) buildx use multigres-operator-builder
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) \
--build-arg VERSION=$$(git rev-parse --short HEAD) \
--build-arg GIT_COMMIT=$$(git rev-parse HEAD) \
--build-arg BUILD_DATE=$$(date -u +%Y-%m-%dT%H:%M:%SZ) \
--tag ${IMG} .
- $(CONTAINER_TOOL) buildx rm multigres-operator-builder
.PHONY: build-installer
build-installer: manifests generate kustomize ## Generate consolidated install YAMLs under dist/.
mkdir -p dist
# kustomize has no build-time image override, so we mutate then restore.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default > dist/install.yaml
$(KUSTOMIZE) build config/deploy-certmanager > dist/install-certmanager.yaml
$(KUSTOMIZE) build config/deploy-observability > dist/install-observability.yaml
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
##@ Test
.PHONY: test
test: manifests generate fmt vet ## Run tests (no integration testing)
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \
go test -p 1 $$(go list ./... | grep -v /e2e) -coverprofile=cover.out
.PHONY: test-integration
test-integration: manifests generate fmt vet setup-envtest ## Run integration tests
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \
go test -p 1 -tags=integration,verbose $$(go list ./... | grep -v /e2e) -coverprofile=cover.out
.PHONY: test-coverage
test-coverage: manifests generate fmt vet setup-envtest ## Generate coverage report with HTML
@mkdir -p coverage
@echo "==> Generating coverage..."
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" \
go test -p 1 -tags=integration,verbose ./... -coverprofile=coverage/combined.out -covermode=atomic
@echo "==> Generating HTML report..."
@go tool cover -html=coverage/combined.out -o=coverage/combined.html
@echo "Generated: coverage/combined.html"
@echo "==> Calculating total coverage..."
@go tool cover -func=coverage/combined.out | tail -1
@echo ""
@echo "Coverage reports in coverage/"
@echo " - Data: coverage/combined.out (for CI/codecov)"
@echo " - HTML: coverage/combined.html"
##@ Test End-to-End
# Each e2e test creates its own ephemeral Kind cluster via e2e-framework.
# The Makefile only needs to build the operator image — the Go tests handle
# cluster lifecycle, image loading, CRD installation, and operator deployment.
#
# Set E2E_KEEP_CLUSTERS to control cluster cleanup:
# never (default) always destroy clusters
# on-failure keep clusters from failed tests for debugging
# always never destroy clusters
# E2E_TEST_SPEC: comma-separated list of test names to run (default: all).
# Example: make test-e2e E2E_TEST_SPEC="minimal,deletion"
E2E_TEST_SPEC ?=
E2E_PACKAGES_SHARED = $(if $(E2E_TEST_SPEC),$(foreach t,$(subst $(comma),$(space),$(E2E_TEST_SPEC)),./test/e2e/shared/$(t)/),./test/e2e/shared/...)
E2E_PACKAGES_DEDICATED = $(if $(E2E_TEST_SPEC),$(foreach t,$(subst $(comma),$(space),$(E2E_TEST_SPEC)),./test/e2e/dedicated/$(t)/),./test/e2e/dedicated/...)
comma := ,
space := $(empty) $(empty)
.PHONY: test-e2e
test-e2e: manifests generate fmt vet container ## Run e2e tests (shared cluster, fast)
OPERATOR_IMG=$(IMG) \
REPO_ROOT=$(shell pwd) \
go test -tags=e2e $(E2E_PACKAGES_SHARED) -p 3 -v -count=1 -timeout=20m
.PHONY: test-e2e-keep
test-e2e-keep: manifests generate fmt vet container ## Run e2e tests; keep cluster on failure
OPERATOR_IMG=$(IMG) \
REPO_ROOT=$(shell pwd) \
E2E_KEEP_CLUSTERS=on-failure \
go test -tags=e2e $(E2E_PACKAGES_SHARED) -p 3 -v -count=1 -timeout=20m
.PHONY: test-e2e-full
test-e2e-full: manifests generate fmt vet container ## Run e2e tests (dedicated cluster per test, full isolation)
OPERATOR_IMG=$(IMG) \
REPO_ROOT=$(shell pwd) \
go test -tags=e2e $(E2E_PACKAGES_DEDICATED) -v -count=1 -timeout=30m -parallel 2
##@ Deployment
ifndef ignore-not-found
ignore-not-found = false
endif
.PHONY: install
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUSTOMIZE) build config/crd | $(KUBECTL) apply --server-side -f -
.PHONY: uninstall
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
$(KUSTOMIZE) build config/default | $(KUBECTL) apply --server-side -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
.PHONY: undeploy
undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
##@ Kind Cluster (Local Development)
.PHONY: kind-up
kind-up: ## Create a kind cluster for local development
@command -v $(KIND) >/dev/null 2>&1 || { \
echo "ERROR: kind is not installed."; \
echo "Install it from: https://kind.sigs.k8s.io/docs/user/quick-start/"; \
exit 1; \
}
@if $(KIND) get clusters | grep -q "^$(KIND_CLUSTER)$$"; then \
echo "Kind cluster '$(KIND_CLUSTER)' already exists."; \
echo "==> Exporting kubeconfig to $(KIND_KUBECONFIG)"; \
$(KIND) get kubeconfig --name $(KIND_CLUSTER) > $(KIND_KUBECONFIG); \
else \
echo "Creating kind cluster '$(KIND_CLUSTER)'..."; \
$(KIND) create cluster --name $(KIND_CLUSTER) --kubeconfig $(KIND_KUBECONFIG); \
fi
@echo "==> Cluster ready. Use: export KUBECONFIG=$(KIND_KUBECONFIG)"
.PHONY: kind-up-topology
kind-up-topology: ## Create a multi-node kind cluster with topology zone labels
@command -v $(KIND) >/dev/null 2>&1 || { \
echo "ERROR: kind is not installed."; \
echo "Install it from: https://kind.sigs.k8s.io/docs/user/quick-start/"; \
exit 1; \
}
@if $(KIND) get clusters | grep -q "^$(KIND_CLUSTER)$$"; then \
echo "Kind cluster '$(KIND_CLUSTER)' already exists."; \
echo "==> Exporting kubeconfig to $(KIND_KUBECONFIG)"; \
$(KIND) get kubeconfig --name $(KIND_CLUSTER) > $(KIND_KUBECONFIG); \
else \
echo "Creating kind cluster '$(KIND_CLUSTER)' with topology zones..."; \
$(KIND) create cluster --name $(KIND_CLUSTER) --kubeconfig $(KIND_KUBECONFIG) \
--config config/kind/kind-config-topology.yaml; \
fi
@echo "==> Cluster ready (3 workers with zone labels). Use: export KUBECONFIG=$(KIND_KUBECONFIG)"
.PHONY: kind-deploy-topology
kind-deploy-topology: kind-up-topology manifests kustomize kind-load kind-load-images ## Deploy operator to multi-node kind cluster with topology zones
@echo "==> Installing CRDs..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/crd | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@echo "==> Deploying operator..."
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/default | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
@echo "==> Deployment complete!"
@echo "Check status: KUBECONFIG=$(KIND_KUBECONFIG) kubectl get pods -n multigres-operator"
$(MAKE) kind-deploy-observer
.PHONY: kind-load
kind-load: container ## Build and load image into kind cluster
@echo "==> Loading image $(IMG) into kind cluster..."
$(KIND) load docker-image $(IMG) --name $(KIND_CLUSTER)
.PHONY: kind-load-images
kind-load-images: ## Pull latest upstream images and load into kind (incremental, skips unchanged)
@echo "==> Pulling latest upstream images (only downloads changes)..."
@for img in $(MULTIGRES_IMAGES); do \
echo " Pulling $$img..."; \
$(CONTAINER_TOOL) pull $$img; \
done
@echo "==> Loading upstream images into kind cluster..."
@for img in $(MULTIGRES_IMAGES); do \
echo " Loading $$img..."; \
$(KIND) load docker-image $$img --name $(KIND_CLUSTER); \
done
@echo "==> All upstream images loaded"
.PHONY: kind-load-observability-images
kind-load-observability-images: ## Pull and load observability stack images into kind
@echo "==> Pulling observability images..."
@for img in $(OBSERVABILITY_IMAGES); do \
echo " Pulling $$img..."; \
$(CONTAINER_TOOL) pull $$img; \
done
@echo "==> Loading observability images into kind cluster..."
@for img in $(OBSERVABILITY_IMAGES); do \
echo " Loading $$img..."; \
$(KIND) load docker-image $$img --name $(KIND_CLUSTER); \
done
@echo "==> All observability images loaded"
.PHONY: kind-deploy
kind-deploy: kind-up manifests kustomize kind-load kind-load-images ## Deploy operator to kind cluster using webhook with self-signed certificates
@echo "==> Installing CRDs..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/crd | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@echo "==> Deploying operator..."
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/default | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
@echo "==> Deployment complete!"
@echo "Check status: KUBECONFIG=$(KIND_KUBECONFIG) kubectl get pods -n multigres-operator"
$(MAKE) kind-deploy-observer
.PHONY: kind-deploy-certmanager
kind-deploy-certmanager: kind-up install-certmanager manifests kustomize kind-load kind-load-images ## Deploy operator to kind cluster using cert manager
@echo "==> Installing CRDs..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/crd | \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@echo "==> Deploying operator (Cert-Manager Mode)..."
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/deploy-certmanager | \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
@echo "==> Deployment complete!"
@echo "Check status: KUBECONFIG=$(KIND_KUBECONFIG) kubectl get pods -n multigres-operator"
$(MAKE) kind-deploy-observer
.PHONY: kind-deploy-no-webhook
kind-deploy-no-webhook: kind-up manifests kustomize kind-load kind-load-images ## Deploy controller to Kind without the webhook enabled.
@echo "==> Installing CRDs..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/crd | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@echo "==> Deploying operator..."
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/no-webhook | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
@echo "==> Deployment complete!"
@echo "Check status: KUBECONFIG=$(KIND_KUBECONFIG) kubectl get pods -n multigres-operator"
$(MAKE) kind-deploy-observer
.PHONY: kind-deploy-observability
kind-deploy-observability: kind-up manifests kustomize kind-load kind-load-images kind-load-observability-images ## Deploy operator with full observability stack (Prometheus Operator, OTel Collector, Tempo, Grafana)
@echo "==> Installing CRDs..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/crd | \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@echo "==> Installing Prometheus Operator..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f \
https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.80.0/bundle.yaml
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) wait --for=condition=Available \
deployment/prometheus-operator -n default --timeout=120s
@echo "==> Deploying operator with observability stack..."
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/deploy-observability | \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
@echo "==> Waiting for observability stack..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) wait --for=condition=Available \
deployment/otel-collector deployment/tempo deployment/grafana \
-n multigres-operator --timeout=180s
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) rollout status \
statefulset/prometheus-multigres -n multigres-operator --timeout=180s
@echo "==> Deployment complete!"
@echo "Run 'make kind-portforward' to port-forward Grafana, Prometheus, and Tempo"
$(MAKE) kind-deploy-observer
.PHONY: kind-portforward
kind-portforward: ## Port-forward Grafana (3000), Prometheus (9090), Tempo (3200). Re-run if connection drops.
@echo "==> Starting port-forwards (Ctrl+C to stop)..."
@echo " Grafana: http://localhost:3000"
@echo " Prometheus: http://localhost:9090"
@echo " Tempo: http://localhost:3200"
@KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) port-forward svc/grafana 3000:3000 -n multigres-operator & \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) port-forward svc/prometheus 9090:9090 -n multigres-operator & \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) port-forward svc/tempo 3200:3200 -n multigres-operator & \
wait
.PHONY: kind-redeploy
kind-redeploy: container manifests kustomize ## Rebuild image, reload to kind, and redeploy
@echo "==> Clearing cached image from kind node..."
docker exec $(KIND_CLUSTER)-control-plane crictl rmi $(IMG) 2>/dev/null || true
$(KIND) load docker-image $(IMG) --name $(KIND_CLUSTER)
@echo "==> Installing CRDs..."
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/crd | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@echo "==> Deploying operator..."
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build config/default | KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side --force-conflicts -f -
@git checkout -- config/manager/kustomization.yaml 2>/dev/null || true
@echo "==> Redeploy complete!"
$(MAKE) kind-deploy-observer
.PHONY: kind-down
kind-down: ## Delete the kind cluster
@echo "==> Deleting kind cluster '$(KIND_CLUSTER)'..."
$(KIND) delete cluster --name $(KIND_CLUSTER)
@rm -f $(KIND_KUBECONFIG) $(IMG_TAG_FILE)
@echo "==> Cluster and kubeconfig deleted"
##@ Observer
.PHONY: observer-build
observer-build: ## Build the observer container image
$(CONTAINER_TOOL) build -t $(OBSERVER_IMG) -f tools/observer/Dockerfile tools/observer/
.PHONY: kind-load-observer
kind-load-observer: observer-build ## Build and load observer image into kind
$(KIND) load docker-image $(OBSERVER_IMG) --name $(KIND_CLUSTER)
.PHONY: kind-deploy-observer
kind-deploy-observer: kind-load-observer ## Deploy observer alongside the operator
@echo "==> Deploying multigres observer..."
cd tools/observer/deploy/base && $(KUSTOMIZE) edit set image observer=$(OBSERVER_IMG)
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build tools/observer/deploy/base | \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply --server-side -f -
@git checkout -- tools/observer/deploy/base/kustomization.yaml 2>/dev/null || true
.PHONY: kind-undeploy-observer
kind-undeploy-observer: ## Remove observer
KUBECONFIG=$(KIND_KUBECONFIG) $(KUSTOMIZE) build tools/observer/deploy/base | \
KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) delete --ignore-not-found -f -
##@ Dependencies
.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
.PHONY: setup-envtest
setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.
@echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..."
@$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \
echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \
exit 1; \
}
.PHONY: envtest
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
$(ENVTEST): $(LOCALBIN)
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
.PHONY: install-certmanager
install-certmanager: ## Install Cert-Manager into the cluster
@echo "==> Installing Cert-Manager $(CERT_MANAGER_VERSION)..."
$(KUBECTL) apply -f https://github.com/cert-manager/cert-manager/releases/download/$(CERT_MANAGER_VERSION)/cert-manager.yaml
@echo "==> Waiting for Cert-Manager to be ready..."
$(KUBECTL) wait --for=condition=Available deployment --all -n cert-manager --timeout=300s
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f "$(1)-$(3)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)" ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
rm -f $(1) ;\
GOBIN=$(LOCALBIN) go install $${package} ;\
mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $$(realpath $(1)-$(3)) $(1)
endef
## Non kubebuilder setup
.PHONY: check-coverage
check-coverage:
go test ./... -coverprofile=./cover.out -covermode=atomic -coverpkg=./...
go tool cover -html=cover.out -o=cover.html
echo now open cover.html
##@ Backward Compatibility Aliases
.PHONY: docker-build
docker-build: container ## Alias for container (backward compatibility)
.PHONY: docker-push
docker-push: container-push ## Alias for container-push (backward compatibility)