|
1 | 1 | # Copyright (c) 2025 Erick Bourgeois, RBC Capital Markets |
2 | 2 | # SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
4 | | -.PHONY: help install build build-debug build-linux-amd64 build-linux-arm64 build-macos-arm64 prepare-binaries-linux-amd64 prepare-binaries-linux-arm64 test test-lib lint format clean crds crddoc docs docs-serve docs-clean docs-rustdoc calm-diagrams calm-validate run-local docker-build docker-build-amd64 docker-build-arm64 docker-build-chainguard docker-push docker-buildx docker-buildx-chainguard gitleaks gitleaks-install install-git-hooks security-scan-local sbom audit |
| 4 | +.PHONY: help install build build-debug build-linux-amd64 build-linux-arm64 build-macos-arm64 prepare-binaries-linux-amd64 prepare-binaries-linux-arm64 test test-lib lint format clean crds crddoc docs docs-serve docs-clean docs-rustdoc calm-diagrams calm-validate run-local docker-build docker-build-amd64 docker-build-arm64 docker-build-chainguard docker-push docker-buildx docker-buildx-chainguard gitleaks gitleaks-install install-git-hooks security-scan-local sbom audit kind-install kind-create kind-delete kind-load kind-deploy kind-example kind-setup kind-status |
5 | 5 |
|
6 | 6 | # CALM (FINOS Common Architecture Language Model) configuration |
7 | 7 | CALM_CLI_VERSION ?= 1.37.0 |
@@ -37,6 +37,17 @@ CONTAINER_TOOL ?= docker |
37 | 37 | # Security tool versions |
38 | 38 | GITLEAKS_VERSION ?= 8.21.2 |
39 | 39 |
|
| 40 | +# Kind (local Kubernetes) configuration |
| 41 | +KIND_VERSION ?= 0.24.0 |
| 42 | +KIND_CLUSTER_NAME ?= 5spot-dev |
| 43 | +KIND_NODE_IMAGE ?= kindest/node:v1.31.0 |
| 44 | +# Image reference used for the locally built kind image. The `local-dev` tag |
| 45 | +# makes it unambiguous that the image is a developer build (not a released |
| 46 | +# version). `kind-deploy` applies the checked-in Deployment (pinned to |
| 47 | +# ghcr.io/finos/5-spot:v0.1.0) and then overrides the container image to |
| 48 | +# $(KIND_IMAGE) via `kubectl set image` so the locally loaded image is used. |
| 49 | +KIND_IMAGE ?= ghcr.io/finos/5-spot:local-dev |
| 50 | + |
40 | 51 | # Python/Poetry package index configuration (for corporate environments) |
41 | 52 | # Set PYPI_INDEX_URL to use a custom PyPI mirror (e.g., Artifactory) |
42 | 53 | # Example: export PYPI_INDEX_URL=https://artifactory.example.com/api/pypi/pypi/simple |
@@ -437,3 +448,154 @@ sbom: ## Generate CycloneDX SBOM (Software Bill of Materials) |
437 | 448 | audit: ## Check dependencies for security vulnerabilities (installs cargo-audit if missing) |
438 | 449 | @command -v cargo-audit >/dev/null 2>&1 || { echo "Installing cargo-audit..."; cargo install cargo-audit; } |
439 | 450 | @cargo audit |
| 451 | + |
| 452 | +# ============================================================ |
| 453 | +# Kind Cluster (local testing for ScheduledMachine) |
| 454 | +# ============================================================ |
| 455 | + |
| 456 | +kind-install: ## Install kind CLI if missing (verifies checksum) |
| 457 | + @if ! command -v kind >/dev/null 2>&1; then \ |
| 458 | + echo "Installing kind v$(KIND_VERSION)..."; \ |
| 459 | + OS=$$(uname -s | tr '[:upper:]' '[:lower:]'); \ |
| 460 | + ARCH=$$(uname -m); \ |
| 461 | + case "$$ARCH" in \ |
| 462 | + x86_64) ARCH="amd64" ;; \ |
| 463 | + aarch64|arm64) ARCH="arm64" ;; \ |
| 464 | + esac; \ |
| 465 | + BIN="kind-$${OS}-$${ARCH}"; \ |
| 466 | + BASE_URL="https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)"; \ |
| 467 | + echo "Downloading $$BIN..."; \ |
| 468 | + curl -sSLf -o /tmp/$$BIN "$$BASE_URL/$$BIN"; \ |
| 469 | + echo "Downloading checksum..."; \ |
| 470 | + curl -sSLf -o /tmp/$$BIN.sha256sum "$$BASE_URL/$$BIN.sha256sum"; \ |
| 471 | + echo "Verifying checksum..."; \ |
| 472 | + cd /tmp && \ |
| 473 | + EXPECTED=$$(awk '{print $$1}' $$BIN.sha256sum) && \ |
| 474 | + if command -v sha256sum >/dev/null 2>&1; then \ |
| 475 | + ACTUAL=$$(sha256sum $$BIN | awk '{print $$1}'); \ |
| 476 | + else \ |
| 477 | + ACTUAL=$$(shasum -a 256 $$BIN | awk '{print $$1}'); \ |
| 478 | + fi && \ |
| 479 | + if [ "$$EXPECTED" != "$$ACTUAL" ]; then \ |
| 480 | + echo "ERROR: checksum mismatch (expected $$EXPECTED, got $$ACTUAL)"; \ |
| 481 | + rm -f /tmp/$$BIN /tmp/$$BIN.sha256sum; \ |
| 482 | + exit 1; \ |
| 483 | + fi; \ |
| 484 | + chmod +x /tmp/$$BIN; \ |
| 485 | + sudo mv /tmp/$$BIN /usr/local/bin/kind; \ |
| 486 | + rm -f /tmp/$$BIN.sha256sum; \ |
| 487 | + echo "✓ kind v$(KIND_VERSION) installed"; \ |
| 488 | + else \ |
| 489 | + echo "✓ kind already installed: $$(kind version)"; \ |
| 490 | + fi |
| 491 | + @command -v kubectl >/dev/null 2>&1 || { echo "ERROR: kubectl not found on PATH. Install kubectl and retry."; exit 1; } |
| 492 | + |
| 493 | +kind-create: kind-install ## Create local kind cluster for testing ScheduledMachine |
| 494 | + @if kind get clusters 2>/dev/null | grep -qx $(KIND_CLUSTER_NAME); then \ |
| 495 | + echo "✓ kind cluster '$(KIND_CLUSTER_NAME)' already exists"; \ |
| 496 | + else \ |
| 497 | + echo "Creating kind cluster '$(KIND_CLUSTER_NAME)' using $(KIND_NODE_IMAGE)..."; \ |
| 498 | + kind create cluster --name $(KIND_CLUSTER_NAME) --image $(KIND_NODE_IMAGE) --wait 120s; \ |
| 499 | + echo "✓ cluster '$(KIND_CLUSTER_NAME)' ready"; \ |
| 500 | + fi |
| 501 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) cluster-info |
| 502 | + |
| 503 | +kind-delete: ## Delete the local kind cluster |
| 504 | + @if kind get clusters 2>/dev/null | grep -qx $(KIND_CLUSTER_NAME); then \ |
| 505 | + kind delete cluster --name $(KIND_CLUSTER_NAME); \ |
| 506 | + echo "✓ cluster '$(KIND_CLUSTER_NAME)' deleted"; \ |
| 507 | + else \ |
| 508 | + echo "✓ no cluster named '$(KIND_CLUSTER_NAME)' — nothing to delete"; \ |
| 509 | + fi |
| 510 | + |
| 511 | +kind-load: ## Build image for host arch (native cross-compile, bypassing cross) and load it into the kind cluster |
| 512 | + @if ! kind get clusters 2>/dev/null | grep -qx $(KIND_CLUSTER_NAME); then \ |
| 513 | + echo "ERROR: kind cluster '$(KIND_CLUSTER_NAME)' does not exist. Run: make kind-create"; \ |
| 514 | + exit 1; \ |
| 515 | + fi |
| 516 | + @HOST_ARCH=$$(uname -m); \ |
| 517 | + case "$$HOST_ARCH" in \ |
| 518 | + arm64|aarch64) TRIPLE=aarch64-unknown-linux-gnu; DOCKER_ARCH=arm64; LINKER=aarch64-unknown-linux-gnu-gcc ;; \ |
| 519 | + x86_64|amd64) TRIPLE=x86_64-unknown-linux-gnu; DOCKER_ARCH=amd64; LINKER=x86_64-unknown-linux-gnu-gcc ;; \ |
| 520 | + *) echo "ERROR: unsupported host arch: $$HOST_ARCH"; exit 1 ;; \ |
| 521 | + esac; \ |
| 522 | + echo "Host $$HOST_ARCH -> building linux/$$DOCKER_ARCH image"; \ |
| 523 | + if ! command -v $$LINKER >/dev/null 2>&1; then \ |
| 524 | + echo "ERROR: linker '$$LINKER' not found on PATH."; \ |
| 525 | + echo " On macOS: brew tap messense/macos-cross-toolchains && brew install $$TRIPLE"; \ |
| 526 | + echo " On Linux: install the matching gcc cross toolchain for your distro."; \ |
| 527 | + exit 1; \ |
| 528 | + fi; \ |
| 529 | + if ! rustup target list --installed | grep -qx "$$TRIPLE"; then \ |
| 530 | + echo "Adding rustup target $$TRIPLE..."; \ |
| 531 | + rustup target add "$$TRIPLE"; \ |
| 532 | + fi; \ |
| 533 | + echo "Compiling 5spot for $$TRIPLE..."; \ |
| 534 | + cargo build --release --target "$$TRIPLE"; \ |
| 535 | + echo "Staging binary at binaries/$$DOCKER_ARCH/5spot..."; \ |
| 536 | + mkdir -p "binaries/$$DOCKER_ARCH"; \ |
| 537 | + cp "target/$$TRIPLE/release/5spot" "binaries/$$DOCKER_ARCH/5spot"; \ |
| 538 | + echo "Building docker image $(KIND_IMAGE) (linux/$$DOCKER_ARCH)..."; \ |
| 539 | + $(CONTAINER_TOOL) build \ |
| 540 | + --build-arg TARGETARCH=$$DOCKER_ARCH \ |
| 541 | + --build-arg VERSION="$(VERSION)" \ |
| 542 | + --build-arg GIT_SHA="$(GIT_SHA)" \ |
| 543 | + --build-arg BASE_IMAGE="$(BASE_IMAGE)" \ |
| 544 | + -t $(KIND_IMAGE) .; \ |
| 545 | + echo "Loading $(KIND_IMAGE) into kind cluster '$(KIND_CLUSTER_NAME)'..."; \ |
| 546 | + kind load docker-image $(KIND_IMAGE) --name $(KIND_CLUSTER_NAME); \ |
| 547 | + echo "✓ image loaded" |
| 548 | + |
| 549 | +kind-deploy: ## Apply CRDs and controller manifests to the kind cluster |
| 550 | + @if ! kind get clusters 2>/dev/null | grep -qx $(KIND_CLUSTER_NAME); then \ |
| 551 | + echo "ERROR: kind cluster '$(KIND_CLUSTER_NAME)' does not exist. Run: make kind-create"; \ |
| 552 | + exit 1; \ |
| 553 | + fi |
| 554 | + @echo "Applying CRDs to kind cluster '$(KIND_CLUSTER_NAME)'..." |
| 555 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) apply -f deploy/crds/ |
| 556 | + @echo "Applying namespace first (avoids race with namespace-scoped resources)..." |
| 557 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) apply -f deploy/deployment/namespace.yaml |
| 558 | + @for i in 1 2 3 4 5 6 7 8 9 10; do \ |
| 559 | + if kubectl --context kind-$(KIND_CLUSTER_NAME) get namespace 5spot-system >/dev/null 2>&1; then \ |
| 560 | + break; \ |
| 561 | + fi; \ |
| 562 | + echo " waiting for namespace 5spot-system ($$i/10)..."; \ |
| 563 | + sleep 1; \ |
| 564 | + done |
| 565 | + @echo "Applying controller manifests (rbac, configmap, deployment, service, ...)..." |
| 566 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) apply -R -f deploy/deployment/ |
| 567 | + @echo "Overriding controller image to $(KIND_IMAGE) (locally built)..." |
| 568 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) -n 5spot-system set image deployment/5spot-controller controller=$(KIND_IMAGE) |
| 569 | + @echo "Waiting for the controller Deployment to become available..." |
| 570 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) -n 5spot-system rollout status deployment/5spot-controller --timeout=180s |
| 571 | + @echo "✓ controller deployed" |
| 572 | + |
| 573 | +kind-example: ## Apply the basic ScheduledMachine example to the kind cluster |
| 574 | + @if ! kind get clusters 2>/dev/null | grep -qx $(KIND_CLUSTER_NAME); then \ |
| 575 | + echo "ERROR: kind cluster '$(KIND_CLUSTER_NAME)' does not exist. Run: make kind-create"; \ |
| 576 | + exit 1; \ |
| 577 | + fi |
| 578 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) apply -f examples/scheduledmachine-basic.yaml |
| 579 | + @echo "✓ example ScheduledMachine applied" |
| 580 | + @echo "Inspect with:" |
| 581 | + @echo " kubectl --context kind-$(KIND_CLUSTER_NAME) get scheduledmachines -A" |
| 582 | + @echo " kubectl --context kind-$(KIND_CLUSTER_NAME) describe scheduledmachine business-hours-worker" |
| 583 | + |
| 584 | +kind-status: ## Show kind cluster, controller, and ScheduledMachine status |
| 585 | + @echo "=== kind clusters ===" |
| 586 | + @kind get clusters 2>/dev/null || echo "(none)" |
| 587 | + @echo "" |
| 588 | + @echo "=== controller pods (namespace 5spot-system) ===" |
| 589 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) -n 5spot-system get pods 2>/dev/null || echo "(cluster unreachable)" |
| 590 | + @echo "" |
| 591 | + @echo "=== ScheduledMachines (all namespaces) ===" |
| 592 | + @kubectl --context kind-$(KIND_CLUSTER_NAME) get scheduledmachines -A 2>/dev/null || echo "(cluster unreachable)" |
| 593 | + |
| 594 | +kind-setup: kind-create kind-load kind-deploy ## One-shot: create cluster, build+load image, deploy CRDs & controller |
| 595 | + @echo "" |
| 596 | + @echo "✓ kind setup complete" |
| 597 | + @echo "" |
| 598 | + @echo "Next steps:" |
| 599 | + @echo " make kind-example # apply the example ScheduledMachine" |
| 600 | + @echo " make kind-status # see cluster & controller state" |
| 601 | + @echo " make kind-delete # tear down" |
0 commit comments