.PHONY: build install clean dev frontend backend test test-changed test-e2e test-chart lint help restart restart-fe kill watch-backend watch-frontend loadtest .PHONY: calico-demo calico-demo-down calico-demo-status .PHONY: kueue-demo kueue-demo-down kueue-demo-status kueue-demo-verify .PHONY: cilium-demo cilium-demo-down cilium-demo-status .PHONY: gpu-ecosystem-demo gpu-ecosystem-demo-down gpu-ecosystem-demo-status .PHONY: kubecost-demo kubecost-demo-down kubecost-demo-status .PHONY: jobset-demo jobset-demo-down jobset-demo-reset jobset-demo-status jobset-demo-verify .PHONY: kuberay-demo kuberay-demo-down kuberay-demo-reset kuberay-demo-status kuberay-demo-verify .PHONY: release release-binaries-dry docker docker-test docker-multiarch docker-push .PHONY: desktop desktop-binary desktop-dev desktop-package-darwin desktop-package-windows desktop-package-linux VERSION ?= $(shell git describe --tags --match 'v*' --always --dirty 2>/dev/null || echo "dev") LDFLAGS := -X main.version=$(VERSION) DOCKER_REPO ?= ghcr.io/skyhook-io/radar RADAR_FLAGS ?= PORT ?= 9280 ## Quick in-cluster test deploy (full build: frontend + embed + Go binary) # Usage: make deploy-test (or make deploy-test TEST_IMAGE=... CLUSTER_NS=... CLUSTER_DEPLOY=...) TEST_IMAGE ?= gcr.io/koalabackend/radar:auth-rbac CLUSTER_NS ?= radar CLUSTER_DEPLOY ?= radar deploy-test: frontend embed @echo "=== Fast test deploy: Go build → push → rollout ===" @echo "Building Go binary for linux/amd64..." GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o /tmp/radar-linux ./cmd/explorer @echo "Building minimal Docker image..." @echo 'FROM gcr.io/distroless/static-debian12:nonroot' > /tmp/Dockerfile.test @echo 'COPY radar-linux /app/radar' >> /tmp/Dockerfile.test @echo 'ENTRYPOINT ["/app/radar"]' >> /tmp/Dockerfile.test docker build -t $(TEST_IMAGE) -f /tmp/Dockerfile.test /tmp docker push $(TEST_IMAGE) kubectl rollout restart deploy/$(CLUSTER_DEPLOY) -n $(CLUSTER_NS) kubectl rollout status deploy/$(CLUSTER_DEPLOY) -n $(CLUSTER_NS) --timeout=60s @echo "=== Done. Tail logs: kubectl logs -n $(CLUSTER_NS) -l app.kubernetes.io/name=$(CLUSTER_DEPLOY) -f ===" # Build a probe image from the CURRENT code and load it into a kind cluster, for # developing the in-cluster reachability probe. A -dirty local build's version is # not a published tag, so the default image won't exist - load a local one and run # radar with --reachability-image $(PROBE_IMAGE). Binary path /radar + ENTRYPOINT # match the official image so the probe Job's `["/radar","probe",...]` command works. KIND_CLUSTER ?= test PROBE_IMAGE ?= radar-probe:dev kind-load-probe: @echo "Building probe binary for linux/$$(go env GOARCH)..." GOOS=linux GOARCH=$$(go env GOARCH) CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o /tmp/radar-probe-bin ./cmd/explorer @echo 'FROM gcr.io/distroless/static-debian12:nonroot' > /tmp/Dockerfile.probe @echo 'COPY radar-probe-bin /radar' >> /tmp/Dockerfile.probe @echo 'ENTRYPOINT ["/radar"]' >> /tmp/Dockerfile.probe docker build -t $(PROBE_IMAGE) -f /tmp/Dockerfile.probe /tmp kind load docker-image $(PROBE_IMAGE) --name $(KIND_CLUSTER) @echo "Loaded $(PROBE_IMAGE) into kind/$(KIND_CLUSTER). Run radar with: --reachability-image $(PROBE_IMAGE)" ## Build targets # Build the complete application (frontend + embedded binary) build: frontend embed backend @echo "Build complete: ./radar" # Build and install to /usr/local/bin install: build @echo "Installing to /usr/local/bin/kubectl-radar..." @cp radar /usr/local/bin/kubectl-radar || sudo cp radar /usr/local/bin/kubectl-radar @echo "Installed! Run 'kubectl radar' or 'kubectl-radar'" # Build Go backend with embedded frontend backend: @echo "Building Go backend..." go build -ldflags "$(LDFLAGS)" -o radar ./cmd/explorer # Build frontend (auto-installs deps if needed) frontend: @echo "Building frontend..." @test -f node_modules/typescript-7/bin/tsc || (echo "Installing npm dependencies..." && npm install) cd web && npm run build # Copy built frontend to embed directory embed: @echo "Copying frontend to static..." rm -rf internal/static/dist @mkdir -p internal/static/dist cp -r web/dist/* internal/static/dist/ ## Development targets # Quick rebuild and restart restart: frontend embed backend kill @sleep 1 ./radar --kubeconfig ~/.kube/config --no-browser --port $(PORT) $(RADAR_FLAGS) & @sleep 4 @echo "Server running at http://localhost:$(PORT)" # Frontend-only rebuild and restart (faster - no Go recompile, serves from web/dist via --dev) restart-fe: frontend kill @sleep 1 ./radar --dev --kubeconfig ~/.kube/config --no-browser --port $(PORT) $(RADAR_FLAGS) & @sleep 4 @echo "Server running at http://localhost:$(PORT)" # Hot reload development (run both in separate terminals) # Terminal 1: make watch-frontend # Terminal 2: make watch-backend dev: @echo "=== Development Mode ===" @echo "" @echo "Run these in separate terminals:" @echo " Terminal 1: make watch-frontend (Vite dev server on :9273)" @echo " Terminal 2: make watch-backend (Go with air on :9280)" @echo "" @echo "Frontend proxies API calls to backend automatically." # Frontend with Vite hot reload watch-frontend: cd web && npm run dev # Backend with air hot reload # Pass extra flags: make watch-backend RADAR_FLAGS="--fake-in-cluster" watch-backend: @command -v air >/dev/null 2>&1 || { echo "Installing air..."; go install github.com/air-verse/air@latest; } air -- $(RADAR_FLAGS) # Run built binary run: ./radar --kubeconfig ~/.kube/config # Run in dev mode (serve frontend from web/dist instead of embedded) run-dev: ./radar --kubeconfig ~/.kube/config --dev ## Utility targets # Kill any running radar process (on configured port and by process name) kill: @lsof -ti:$(PORT) | xargs kill -9 2>/dev/null || true @pkill -9 -f './radar' 2>/dev/null || true # Install all dependencies deps: go mod download go mod tidy npm install # Install dev tools install-tools: go install github.com/air-verse/air@latest npm install # Clean build artifacts clean: rm -f radar radar-desktop rm -rf web/dist rm -f internal/static/dist/index.html rm -rf internal/static/dist/assets # Run tests test: go test -v ./... cd pkg && go test -v ./... # Fast dev loop: vet/test only the changed Go packages, tsc, and `vitest related` # for changed frontend files. BASE defaults to origin/main. Not a completion check. test-changed: ./scripts/test-changed.sh $(BASE) # Run e2e tests against the current kubeconfig cluster (on-demand, not in CI) test-e2e: go test -tags e2e -v -timeout 5m ./internal/k8s/ # Smoke-test the Helm chart's template rendering (requires `helm` on PATH) test-chart: ./scripts/test-chart.sh # Bootstrap a kind cluster pre-loaded with curated GitOps scenarios # (Argo CD + Flux + healthy/suspended/app-of-apps/ApplicationSet/etc). # Useful for visual-testing GitOps UI changes against realistic state. # See scripts/gitops-demo/README.md for the full coverage matrix. gitops-demo: ./scripts/gitops-demo.sh up gitops-demo-down: ./scripts/gitops-demo.sh down gitops-demo-status: ./scripts/gitops-demo.sh status gitops-demo-drift: ./scripts/gitops-demo.sh drift # Bootstrap a kind cluster pre-loaded with Kyverno 1.18.2 and curated policy # fixtures (both API families, the four enforcement-posture cases, dual-API # PolicyExceptions, and ~40 real PolicyReports across several sources). # Useful for visual-testing policy UI changes against realistic state. # See scripts/kyverno-demo/README.md for the coverage matrix — and read the # 1.20-simulation warning before running `kyverno-demo.sh modern-only`. kyverno-demo: ./scripts/kyverno-demo.sh up kyverno-demo-down: ./scripts/kyverno-demo.sh down kyverno-demo-status: ./scripts/kyverno-demo.sh status # Bootstrap a kind cluster pre-loaded with Argo Rollouts scenarios # (paused canary, inconclusive analysis, blue-green, aborted, workloadRef). # Useful for visual-testing the Rollout control surface against real state. # See scripts/rollouts-demo/README.md for the full coverage matrix. rollouts-demo: ./scripts/rollouts-demo.sh up rollouts-demo-down: ./scripts/rollouts-demo.sh down rollouts-demo-status: ./scripts/rollouts-demo.sh status rollouts-demo-roll: ./scripts/rollouts-demo.sh roll # Bootstrap a kind cluster pre-loaded with curated Crossplane fixtures # (core + provider-kubernetes + function-patch-and-transform + XRD/Composition/XRs). # Useful for visual-testing Crossplane UI changes against realistic state. # See scripts/crossplane-demo/README.md for the full coverage matrix. crossplane-demo: ./scripts/crossplane-demo.sh up crossplane-demo-down: ./scripts/crossplane-demo.sh down crossplane-demo-status: ./scripts/crossplane-demo.sh status # Load-test the UI at high object counts against a synthetic, resource-free fake # cluster (no real workloads, no kubelet, no etcd). Radar treats the fakes as a # real cluster. Seeds a topology-realistic Deployment->ReplicaSet->Pod population # with matching Services/ConfigMaps/Secrets, spread across nodes and namespaces. # The pod count is live-controllable via the admin listener (default +1): # curl -XPOST localhost:9282/loadtest/scale -d '{"pods":10000}' # curl localhost:9282/loadtest/status # Override the count with PODS and the port with LOADTEST_PORT. LOADTEST_PORT ?= 9281 PODS ?= 50000 loadtest: frontend embed go run ./cmd/testserver -port $(LOADTEST_PORT) -pods $(PODS) # Bootstrap a kind cluster pre-loaded with curated Velero fixtures (all 13 # Backup phases, the supersession series, a paused+invalid Schedule, an # unavailable BSL, a restic repository, and the rancher plural collision). # The Velero controller is deliberately scaled to 0, so the fixtures' own # status survives and all thirteen phases are on screen at once. Run # ./scripts/velero-demo.sh live for the other half: MinIO plus a running # controller, which earns the states instead of asserting them. # See scripts/velero-demo/README.md for the coverage matrix. velero-demo: ./scripts/velero-demo.sh up velero-demo-live: ./scripts/velero-demo.sh live velero-demo-down: ./scripts/velero-demo.sh down velero-demo-status: ./scripts/velero-demo.sh status # Bootstrap a kind cluster pre-loaded with curated CloudNativePG fixtures # (four clusters all 2/2 Ready with four different badges, a real WAL-archiving # failure, Pooler/ScheduledBackup/Backups, plus Velero + KubeBlocks CRs for the # shared `backups`/`clusters` plurals). # See scripts/cnpg-demo/README.md for the coverage matrix and, more importantly, # why the WAL failure is induced rather than patched. cnpg-demo: ./scripts/cnpg-demo.sh up cnpg-demo-down: ./scripts/cnpg-demo.sh down cnpg-demo-status: ./scripts/cnpg-demo.sh status # Live CNPG operator for real failovers and backup runs. Frozen-only terminal # phases and Backup rows are omitted because the controller owns them. cnpg-demo-live: ./scripts/cnpg-demo.sh live # Runtime fixtures on the EXISTING CNPG demo cluster (operator thawed if frozen): # MinIO + real plugin WAL archiving and backups, a restored cluster, a Pooler, # pgbench load, a blocked lock chain and a Prometheus Radar auto-discovers. # `./scripts/cnpg-demo.sh runtime-lag on|off` induces replica lag. cnpg-demo-runtime: ./scripts/cnpg-demo.sh runtime cnpg-demo-runtime-down: ./scripts/cnpg-demo.sh runtime-down # Grafana Beyla on kind: eBPF loaded, a minimal Prometheus scraping it, and two # conversations to observe. Which labels Beyla exports depends on configuration — # dst_port and transport are off by default, direction is on and doubles every # edge — so read scripts/beyla-demo/README.md before changing the Beyla source. # `attrs` and `no-network` switch between the three configurations that matter. beyla-demo: ./scripts/beyla-demo.sh up beyla-demo-down: ./scripts/beyla-demo.sh down beyla-demo-status: ./scripts/beyla-demo.sh status .PHONY: workload-metrics-demo workload-metrics-demo-check workload-metrics-demo: bash scripts/workload-metrics-demo.sh up workload-metrics-demo-check: bash scripts/workload-metrics-demo.sh check # Bootstrap a kind cluster with Cilium + Hubble Relay and traffic workloads, # for exercising every Hubble connection lane: direct in-cluster dial # (plaintext and TLS/SAN-discovery via `tls`), and the port-forward fallback # when `netpol` blocks the direct path. `install-radar` builds the current # tree into the cluster with DEFAULT chart RBAC to prove pods/portforward # stays unneeded. See scripts/cilium-demo/README.md. cilium-demo: ./scripts/cilium-demo.sh up cilium-demo-down: ./scripts/cilium-demo.sh down cilium-demo-status: ./scripts/cilium-demo.sh status # Pinned upstream CRDs + deterministic fixtures for Radar's 37 curated GPU, # batch, distributed-training, and inference resource identities. gpu-ecosystem-demo: ./scripts/gpu-ecosystem-demo.sh up gpu-ecosystem-demo-down: ./scripts/gpu-ecosystem-demo.sh down gpu-ecosystem-demo-status: ./scripts/gpu-ecosystem-demo.sh status # Kubecost 3 on kind with ephemeral storage, deterministic prices, accelerated # FinOps Agent exports, and Deployment/StatefulSet/DaemonSet fixtures. Exercises # the real allocation/asset API plus Radar's local port-forward and in-cluster # Service-DNS lanes. See scripts/kubecost-demo/README.md. kubecost-demo: ./scripts/kubecost-demo.sh up kubecost-demo-down: ./scripts/kubecost-demo.sh down kubecost-demo-status: ./scripts/kubecost-demo.sh status # Focused live Kueue controller lane. Complements the controller-free GPU # breadth fixtures with admitted, quota-blocked, and held-queue reconciliation. # See scripts/kueue-demo/README.md for the proof boundary. kueue-demo: ./scripts/kueue-demo.sh up kueue-demo-down: ./scripts/kueue-demo.sh down kueue-demo-status: ./scripts/kueue-demo.sh status kueue-demo-verify: ./scripts/kueue-demo.sh verify # Focused live JobSet controller lane. Complements the controller-free GPU # breadth fixtures with role/index lineage, dependency gating, and a terminal # failure. See scripts/jobset-demo/README.md for the proof boundary. jobset-demo: ./scripts/jobset-demo.sh up jobset-demo-down: ./scripts/jobset-demo.sh down jobset-demo-reset: ./scripts/jobset-demo.sh reset jobset-demo-status: ./scripts/jobset-demo.sh status jobset-demo-verify: ./scripts/jobset-demo.sh verify # Real KubeRay reconciliation for one head-only RayService: a healthy active # Serve revision plus an intentionally non-runnable pending NewCluster revision. kuberay-demo: ./scripts/kuberay-demo.sh up kuberay-demo-down: ./scripts/kuberay-demo.sh down kuberay-demo-reset: ./scripts/kuberay-demo.sh reset kuberay-demo-status: ./scripts/kuberay-demo.sh status kuberay-demo-verify: ./scripts/kuberay-demo.sh verify # Bootstrap a kind cluster running real Calico with its aggregated API server, # plus the policy shapes the Calico surfaces render (both API groups serving the # same objects, all three staged kinds including a staged deletion, a non-default # tier, IP pools and a HostEndpoint). # See scripts/calico-demo/README.md for the coverage matrix and the two Calico # behaviours that are easy to get wrong without a cluster to check against. calico-demo: ./scripts/calico-demo.sh up calico-demo-down: ./scripts/calico-demo.sh down calico-demo-status: ./scripts/calico-demo.sh status # Run linter lint: go vet ./... cd pkg && go vet ./... # Type check frontend tsc: cd web && npm run tsc # Format code fmt: go fmt ./... # ============================================================================ # Docker & Helm # ============================================================================ # Docker build (single arch, for local testing) # Uses --target full to build from source (the default 'release' target requires pre-built binaries) docker: docker build --target full -t $(DOCKER_REPO):$(VERSION) -t $(DOCKER_REPO):latest . # Test Docker image with read-only filesystem (simulates in-cluster with readOnlyRootFilesystem) # Requires ~/.kube/config for cluster access; runs on port 9280 docker-test: docker @echo "Starting Radar with read-only filesystem (simulating in-cluster)..." @echo "Press Ctrl+C to stop" docker run --rm \ --read-only \ --tmpfs /tmp \ -e HELM_CACHE_HOME=/tmp/helm/cache \ -e HELM_CONFIG_HOME=/tmp/helm/config \ -e HELM_DATA_HOME=/tmp/helm/data \ -v $(HOME)/.kube/config:/home/nonroot/.kube/config:ro \ -p 9280:9280 \ $(DOCKER_REPO):$(VERSION) --no-browser # Docker build multi-arch (amd64 + arm64, for production) docker-multiarch: @docker buildx inspect radar-builder &>/dev/null || docker buildx create --name radar-builder --use docker buildx use radar-builder docker buildx build \ --platform linux/amd64,linux/arm64 \ --build-arg VERSION=$(VERSION) \ -t $(DOCKER_REPO):$(VERSION) \ -t $(DOCKER_REPO):latest \ --push \ . docker-push: docker push $(DOCKER_REPO):$(VERSION) docker push $(DOCKER_REPO):latest # ============================================================================ # Desktop (Wails) Targets # ============================================================================ # Build desktop app: frontend + Go desktop binary desktop: frontend embed desktop-binary @echo "Desktop build complete: ./radar-desktop" # Build desktop binary only (assumes frontend is already in internal/static/dist) desktop-binary: @echo "Building desktop binary..." CGO_ENABLED=1 CGO_LDFLAGS="-framework UniformTypeIdentifiers" go build -tags production -ldflags "$(LDFLAGS)" -o radar-desktop ./cmd/desktop # Run desktop app in Wails dev mode with Go hot reload. # wails.json lives in cmd/desktop/ (Wails requires it next to the main package). # Requires wails CLI: go install github.com/wailsapp/wails/v2/cmd/wails@latest desktop-dev: @command -v wails >/dev/null 2>&1 || { echo "Error: wails CLI not found. Install with: go install github.com/wailsapp/wails/v2/cmd/wails@latest"; exit 1; } cd cmd/desktop && wails dev -ldflags "$(LDFLAGS)" # Package macOS .app bundle desktop-package-darwin: @command -v wails >/dev/null 2>&1 || { echo "Error: wails CLI not found"; exit 1; } cd cmd/desktop && wails build -platform darwin/universal -ldflags "$(LDFLAGS)" # Package Windows .exe desktop-package-windows: @command -v wails >/dev/null 2>&1 || { echo "Error: wails CLI not found"; exit 1; } cd cmd/desktop && wails build -platform windows/amd64 -ldflags "$(LDFLAGS)" # Package Linux binary desktop-package-linux: @command -v wails >/dev/null 2>&1 || { echo "Error: wails CLI not found"; exit 1; } cd cmd/desktop && wails build -platform linux/amd64 -ldflags "$(LDFLAGS)" # ============================================================================ # Release Targets # ============================================================================ # Dry run goreleaser (no publish) release-binaries-dry: @command -v goreleaser >/dev/null 2>&1 || { echo "Error: goreleaser not found"; exit 1; } goreleaser release --snapshot --clean # Interactive release (remote via CI or local) release: ./scripts/release.sh # ============================================================================ # Help # ============================================================================ help: @echo "Radar - Kubernetes Cluster Visualization" @echo "" @echo "Development:" @echo " make build - Build CLI binary (frontend + embedded)" @echo " make watch-frontend - Vite dev server with HMR (port 9273)" @echo " make watch-backend - Go with air hot reload (port 9280)" @echo " make run - Run built binary" @echo " make test - Run tests" @echo " make test-changed - Fast loop: only what the change touches (BASE=origin/main)" @echo "" @echo "Demo clusters:" @echo " make gitops-demo - GitOps fixtures (Argo CD + Flux)" @echo " make crossplane-demo - Crossplane fixtures" @echo " make kyverno-demo - Live Kyverno policy + report fixtures" @echo " make rollouts-demo - Argo Rollouts progression fixtures" @echo " make cnpg-demo - Frozen CNPG rendering fixtures" @echo " make cnpg-demo-live - CNPG fixtures with the operator running" @echo " make cnpg-demo-runtime - CNPG runtime: backups/restore, load, locks, Prometheus" @echo " make velero-demo - Velero fixtures, all 13 backup phases at once" @echo " make velero-demo-live - Velero with real object storage; states produced by the controller" @echo " make beyla-demo - Grafana Beyla eBPF traffic fixtures" @echo " make cilium-demo - Cilium + Hubble Relay, all Radar connection lanes" @echo " make gpu-ecosystem-demo - 37 GPU, batch, and AI/ML resource fixtures" @echo " make kubecost-demo - Kubecost 3 current-cost API and Radar connection lanes" @echo " make kueue-demo - Live Kueue admission and pre-Pod blocker fixtures" @echo " make jobset-demo - Live JobSet role, dependency, and failure fixtures" @echo " make kuberay-demo - Real RayService active/pending revision lifecycle" @echo " make calico-demo - Real Calico, both API groups, staged policies" @echo "" @echo "Desktop:" @echo " make desktop - Build desktop app (frontend + Wails binary)" @echo " make desktop-binary - Build desktop binary only" @echo " make desktop-dev - Run desktop in Wails dev mode" @echo " make desktop-package-darwin - Package macOS .app bundle" @echo "" @echo "Docker & In-Cluster:" @echo " make docker - Build Docker image (local arch)" @echo " make docker-test - Build and run with read-only filesystem (simulates in-cluster)" @echo " make docker-multiarch - Build multi-arch image (amd64+arm64) and push" @echo " make docker-push - Push to GHCR" @echo "" @echo "Release:" @echo " make release - Interactive release (remote via CI or local)" @echo " make release-binaries-dry - Dry run goreleaser (no publish)" @echo "" @echo "Utility:" @echo " make deps - Install all dependencies" @echo " make install - Install CLI to /usr/local/bin" @echo " make clean - Clean build artifacts" @echo " make kill - Kill running server"