.PHONY: help validate fmt fmt-fix test-unit test-unit-docker test-unit-k8s \ test-integration-docker test-integration-k8s test-all clean TOFU ?= tofu TFLINT ?= tflint K3D ?= k3d DOCKER ?= docker # Names used for local integration test clusters / containers TEST_K3D_CLUSTER ?= tofu-test TEST_DOCKER_NET ?= tofu-test-net TEST_APP_NAME ?= testapp ##───────────────────────────────────────────────────────────────────────────── ## help Show this message ##───────────────────────────────────────────────────────────────────────────── help: @grep -E '^##' Makefile | sed 's/^## //' ##───────────────────────────────────────────────────────────────────────────── ## Static analysis (no infrastructure required) ##───────────────────────────────────────────────────────────────────────────── ## validate Run 'tofu validate' on both stacks validate: @echo "── Validating Docker stack ──" $(TOFU) init -backend=false -input=false > /dev/null $(TOFU) validate @echo "── Validating K8s stack ──" cd k8s && $(TOFU) init -backend=false -input=false > /dev/null cd k8s && $(TOFU) validate @echo "All configurations valid." ## fmt-check Check HCL formatting in all .tf files fmt-check: $(TOFU) fmt -check -recursive ## fmt-fix Auto-format all .tf files fmt-fix: $(TOFU) fmt -recursive ## lint Run tflint on both stacks (requires tflint in PATH) lint: @echo "── Linting Docker stack ──" $(TFLINT) --recursive @echo "── Linting K8s stack ──" cd k8s && $(TFLINT) --recursive ##───────────────────────────────────────────────────────────────────────────── ## Unit tests (mocked providers — no real infrastructure) ##───────────────────────────────────────────────────────────────────────────── ## test-unit-docker Run Docker stack unit tests (tofu test) test-unit-docker: @echo "── Docker stack unit tests ──" $(TOFU) init -backend=false -input=false > /dev/null $(TOFU) test ## test-unit-k8s Run K8s stack unit tests (tofu test) test-unit-k8s: @echo "── K8s stack unit tests ──" cd k8s && $(TOFU) init -backend=false -input=false > /dev/null cd k8s && $(TOFU) test ## test-unit Run all unit tests test-unit: test-unit-docker test-unit-k8s ##───────────────────────────────────────────────────────────────────────────── ## Integration tests (real local infrastructure) ##───────────────────────────────────────────────────────────────────────────── ## test-integration-docker Deploy to local Docker, verify, destroy ## Requires: Docker running locally with SSH to localhost ## SSH key: set DOCKER_TEST_KEY env var (default: ~/.ssh/id_ed25519) DOCKER_TEST_KEY ?= $(HOME)/.ssh/id_ed25519 DOCKER_TEST_USER ?= $(shell whoami) test-integration-docker: @echo "── Docker integration test ──" @echo "Using local Docker socket via SSH to localhost" $(TOFU) init -backend=false -input=false > /dev/null $(TOFU) workspace select $(TEST_APP_NAME) 2>/dev/null || $(TOFU) workspace new $(TEST_APP_NAME) $(TOFU) apply -auto-approve \ -var="ssh_host=localhost" \ -var="ssh_user=$(DOCKER_TEST_USER)" \ -var="ssh_key_path=$(DOCKER_TEST_KEY)" \ -var="app_name=$(TEST_APP_NAME)" \ -var="environment=dev" \ -var="openresty_source_type=bind_mount" \ -var="openresty_remote_config_path=/tmp/tofu-test-openresty" \ -var="openresty_external_port=18080" \ -var="db_name=testdb" \ -var="db_user=testuser" \ -var="db_password=testpass" @echo "── Verifying containers are running ──" $(DOCKER) ps --filter "name=$(TEST_APP_NAME)" --format "table {{.Names}}\t{{.Status}}" @echo "── Tearing down ──" $(TOFU) destroy -auto-approve \ -var="ssh_host=localhost" \ -var="ssh_user=$(DOCKER_TEST_USER)" \ -var="ssh_key_path=$(DOCKER_TEST_KEY)" \ -var="app_name=$(TEST_APP_NAME)" \ -var="environment=dev" \ -var="openresty_source_type=bind_mount" \ -var="openresty_remote_config_path=/tmp/tofu-test-openresty" \ -var="openresty_external_port=18080" \ -var="db_name=testdb" \ -var="db_user=testuser" \ -var="db_password=testpass" $(TOFU) workspace select default $(TOFU) workspace delete $(TEST_APP_NAME) @echo "Docker integration test passed." ## test-integration-k8s Deploy to local k3d cluster, verify, destroy ## Requires: k3d and kubectl in PATH test-integration-k8s: _k3d-cluster-create @echo "── K8s integration test ──" $(K3D) kubeconfig get $(TEST_K3D_CLUSTER) > /tmp/test-kubeconfig chmod 600 /tmp/test-kubeconfig cd k8s && $(TOFU) init -backend=false -input=false > /dev/null cd k8s && $(TOFU) workspace select $(TEST_APP_NAME) 2>/dev/null \ || $(TOFU) workspace new $(TEST_APP_NAME) cd k8s && $(TOFU) apply -auto-approve \ -var-file="apps/example-nodered.tfvars" \ -var="kubeconfig_path=/tmp/test-kubeconfig" \ -var="app_name=$(TEST_APP_NAME)" \ -var="rabbitmq_password=testpass" \ -var="loki_auth_token=" @echo "── Verifying K8s resources ──" kubectl --kubeconfig /tmp/test-kubeconfig get all -n $(TEST_APP_NAME) @echo "── Tearing down ──" cd k8s && $(TOFU) destroy -auto-approve \ -var-file="apps/example-nodered.tfvars" \ -var="kubeconfig_path=/tmp/test-kubeconfig" \ -var="app_name=$(TEST_APP_NAME)" \ -var="rabbitmq_password=testpass" \ -var="loki_auth_token=" cd k8s && $(TOFU) workspace select default cd k8s && $(TOFU) workspace delete $(TEST_APP_NAME) $(MAKE) _k3d-cluster-delete rm -f /tmp/test-kubeconfig @echo "K8s integration test passed." _k3d-cluster-create: @if ! $(K3D) cluster list | grep -q $(TEST_K3D_CLUSTER); then \ echo "Creating k3d cluster '$(TEST_K3D_CLUSTER)'..."; \ $(K3D) cluster create $(TEST_K3D_CLUSTER) \ --agents 1 \ --k3s-arg "--disable=traefik@server:0" \ --wait; \ echo "Installing Traefik CRDs..."; \ kubectl --kubeconfig $$($(K3D) kubeconfig get $(TEST_K3D_CLUSTER)) \ apply -f https://raw.githubusercontent.com/traefik/traefik/v2.11/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml; \ else \ echo "k3d cluster '$(TEST_K3D_CLUSTER)' already exists."; \ fi _k3d-cluster-delete: $(K3D) cluster delete $(TEST_K3D_CLUSTER) 2>/dev/null || true ##───────────────────────────────────────────────────────────────────────────── ## Combined targets ##───────────────────────────────────────────────────────────────────────────── ## test-all Run validate + fmt-check + unit tests test-all: validate fmt-check test-unit @echo "All tests passed." ## clean Remove local .terraform dirs and state files clean: find . -name '.terraform' -type d -exec rm -rf {} + 2>/dev/null || true find . -name 'terraform.tfstate*' -type f -delete 2>/dev/null || true find . -name '.terraform.lock.hcl' -type f -delete 2>/dev/null || true @echo "Cleaned."