COMPOSE ?= docker compose SERVICE_DEV := dev SERVICE_SIM := simulator PYTEST_OFFLINE := -m "not integration and not compatibility and not pve_stub" # Docker Hub release image (runtime target only — not the local bind-mount "dev" image). DOCKERHUB_USER ?= inecs IMAGE_NAME ?= vmware-api-simulator VERSION ?= $(shell sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml) DOCKER_IMAGE ?= $(DOCKERHUB_USER)/$(IMAGE_NAME) PUSH_LATEST ?= 1 COMPOSE_RELEASE ?= $(COMPOSE) -f docker-compose.release.yml HELM_CHART ?= ./helm/vmware-api-simulator # Preferred host ports for `make up-local` (gitignored docker-compose.override.yml). # Override on the CLI if needed: make up-local LOCAL_HTTPS_PORT=9443 COMPOSE_OVERRIDE ?= docker-compose.override.yml LOCAL_HTTP_PORT ?= 18080 LOCAL_HTTPS_PORT ?= 18443 LOCAL_POSTGRES_PORT ?= 15434 .PHONY: help install format lint typecheck test test-unit test-integration test-contract test-compatibility test-surface evidence coverage run dev up up-local down down-local restart logs docker-build docker-up docker-down docker-logs docker-restart db-up db-down db-migrate db-reset api-import api-diff seed clean ci ci-all shell push release release-build release-up release-down release-seed helm-deps helm-template \ pulumi-tests pulumi-tests-smoke test-pulumi-smoke test-pulumi test-smoke-all test-all clean-test-resources vsphere-universe vsphere-param-index vsphere-bundles vsphere-seed-dump help: ## Show available commands @awk 'BEGIN {FS = ":.*## "}; /^[a-zA-Z0-9_-]+:.*## / {printf "%-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST) install: ## Build runtime and development images @test -f .env || cp .env.example .env $(COMPOSE) build simulator $(SERVICE_DEV) format: ## Format Python sources $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) ruff format . lint: ## Run Ruff lint checks $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) ruff check . typecheck: ## Run strict mypy checks $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) mypy test: ## Run offline unit and contract tests $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest $(PYTEST_OFFLINE) test-unit: ## Run unit tests $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest tests/unit test-integration: ## Run tests that require PostgreSQL @test -f .env || cp .env.example .env $(COMPOSE) up -d postgres $(COMPOSE) run --rm $(SERVICE_DEV) pytest -m integration test-contract: ## Run offline API contract tests $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest -m contract test-compatibility: ## Run client smoke flow against the Compose stack @test -f .env || cp .env.example .env $(COMPOSE) up -d --build --wait $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.simulation.seed_cli $(COMPOSE) run --rm $(SERVICE_DEV) pytest -m compatibility test-surface: ## Probe every declared method on majors 6-9 (0x501 / 0xexception) @test -f .env || cp .env.example .env $(COMPOSE) up -d postgres $(COMPOSE) run --rm $(SERVICE_DEV) pytest tests/compatibility/test_api_surface_probe.py -q vsphere-surface: ## Probe native vSphere REST coverage registry against running gateway @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait # From the tools container, hit the simulator service (not host-mapped :443). $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/vsphere_surface_probe.py vsphere-matrix: ## Full REST matrix: all verbs × majors 6–9 (no 5xx) @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/vsphere_full_matrix_probe.py vsphere-seed-dump: ## 100% seed↔live inventory dump audit (PROFILE=small|large|big) @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait SEED_VSPHERE_PROFILE="$${VSPHERE_PROFILE:-$${PROFILE:-large}}" \ SEED_VSPHERE_LARGE_VMS="$${VSPHERE_VMS:-1000}" \ SEED_VSPHERE_LARGE_HOSTS="$${VSPHERE_HOSTS:-10}" \ $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.simulation.seed_cli $(COMPOSE) --profile tools run --rm --no-deps \ -e VSPHERE_BASE=http://simulator:8080 \ -e SEED_VSPHERE_PROFILE="$${VSPHERE_PROFILE:-$${PROFILE:-large}}" \ -e SEED_VSPHERE_LARGE_VMS="$${VSPHERE_VMS:-1000}" \ -e SEED_VSPHERE_LARGE_HOSTS="$${VSPHERE_HOSTS:-10}" \ $(SERVICE_DEV) python scripts/vsphere_seed_dump_audit.py \ --profile "$${VSPHERE_PROFILE:-$${PROFILE:-large}}" vsphere-universe: ## Regenerate Broadcom Automation API universe.json from operations index $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) python scripts/generate_vsphere_universe.py vsphere-param-index: ## Build console param index from official Automation OpenAPI python3 scripts/generate_vsphere_param_index.py vsphere-bundles: ## Regenerate stub OpenAPI matrices + evidence ledgers $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) python scripts/write_vsphere_bundles.py $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) python scripts/write_vsphere_evidence.py test-vsphere: ## Native vSphere unit + integration + surface + majors matrix @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest \ tests/unit/test_vsphere_profiles.py \ tests/unit/test_vsphere_catalog.py \ tests/unit/test_vsphere_matrix.py \ tests/unit/test_vsphere_compatibility.py \ tests/unit/test_vsphere_universe.py \ tests/unit/test_vsphere_mappers.py \ tests/unit/test_property_collector.py \ tests/unit/test_web_assets.py \ tests/unit/test_web_console.py \ tests/integration/test_vsphere_api.py \ tests/integration/test_vsphere_api_surface_data.py \ tests/integration/test_vsphere_soap_depth.py \ tests/integration/test_vsphere_soap_create_vm.py \ tests/integration/test_vsphere_deep_realism.py \ tests/integration/test_vsphere_full_api.py -q $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/vsphere_surface_probe.py $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/vsphere_full_matrix_probe.py $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.simulation.seed_cli $(COMPOSE) --profile tools run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 \ -e SEED_VSPHERE_PROFILE="$${VSPHERE_PROFILE:-$${PROFILE:-large}}" \ $(SERVICE_DEV) python scripts/vsphere_seed_dump_audit.py \ --profile "$${VSPHERE_PROFILE:-$${PROFILE:-large}}" $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/vsphere_real_data_spotcheck.py $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/vsphere_nonempty_probe.py $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/run_client_cookbooks.py client-cookbooks: ## Python/Ansible/Terraform/Pulumi-style cookbooks against gateway or simulator @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.simulation.seed_cli $(COMPOSE) run --rm --no-deps -e VSPHERE_BASE=http://simulator:8080 $(SERVICE_DEV) \ python scripts/run_client_cookbooks.py # Hybrid pulumi-tests suite (pulumi-vsphere + REST matrix + CRUD + SOAP) pulumi-tests: ## Run full hybrid suite (provider + REST×6-9 + CRUD + SOAP) @$(MAKE) -C pulumi-tests test-pulumi pulumi-tests-smoke: ## PU-INV + one-major REST smoke (no VM/tags/CRUD/SOAP) @$(MAKE) -C pulumi-tests test-pulumi-smoke test-pulumi-smoke test-pulumi test-smoke-all test-all clean-test-resources: ## Aliases → pulumi-tests/ @$(MAKE) -C pulumi-tests $@ evidence: ## Regenerate per-major verified surface evidence ledgers $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) python -m app.evidence_gen coverage: ## Run offline tests with coverage enforcement $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest $(PYTEST_OFFLINE) --cov=app --cov-report=term-missing --cov-report=xml run: ## Run the application in the foreground @test -f .env || cp .env.example .env $(COMPOSE) up --build up: ## Start PostgreSQL, simulator, and TLS gateway @test -f .env || cp .env.example .env $(COMPOSE) up -d --build --wait up-local: ## Start on free local ports via gitignored compose override @COMPOSE="$(COMPOSE)" COMPOSE_OVERRIDE="$(COMPOSE_OVERRIDE)" \ LOCAL_HTTP_PORT="$(LOCAL_HTTP_PORT)" \ LOCAL_HTTPS_PORT="$(LOCAL_HTTPS_PORT)" \ LOCAL_POSTGRES_PORT="$(LOCAL_POSTGRES_PORT)" \ bash scripts/up_local.sh down: ## Stop local services $(COMPOSE) down down-local: ## Stop local stack and remove gitignored compose override $(COMPOSE) down @rm -f $(COMPOSE_OVERRIDE) @echo "Stopped local stack and removed $(COMPOSE_OVERRIDE)" restart: ## Rebuild and restart the stack @test -f .env || cp .env.example .env $(COMPOSE) up -d --build --force-recreate --wait logs: ## Follow logs from all services $(COMPOSE) logs -f dev: ## Run the application with auto-reload in Docker @test -f .env || cp .env.example .env $(COMPOSE) up -d postgres migrate $(COMPOSE) up simulator docker-build: ## Build runtime and development images $(MAKE) install docker-up: up ## Alias for up docker-restart: ## Rebuild and recreate simulator and TLS gateway only $(COMPOSE) up -d --build --force-recreate simulator api-gateway docker-down: down ## Alias for down docker-logs: ## Follow simulator logs only $(COMPOSE) logs -f simulator db-up: ## Start PostgreSQL only @test -f .env || cp .env.example .env $(COMPOSE) up -d postgres db-down: ## Stop PostgreSQL $(COMPOSE) stop postgres db-migrate: ## Apply database migrations @test -f .env || cp .env.example .env $(COMPOSE) run --rm migrate db-reset: ## Recreate the local database volume $(COMPOSE) down -v $(COMPOSE) up -d postgres $(COMPOSE) run --rm migrate api-import: ## Import an API snapshot $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) vmware-api-contract import $(ARGS) api-diff: ## Compare API snapshots $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) vmware-api-contract diff $(ARGS) seed: ## Seed simulation data (vSphere; PROFILE= / VSPHERE_PROFILE=small|large|big) @test -f .env || cp .env.example .env SEED_PROFILE="$${PROFILE:-small}" \ SEED_VSPHERE_PROFILE="$${VSPHERE_PROFILE:-$${PROFILE:-large}}" \ SEED_VSPHERE_LARGE_VMS="$${VSPHERE_VMS:-1000}" \ SEED_VSPHERE_LARGE_HOSTS="$${VSPHERE_HOSTS:-10}" \ $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.simulation.seed_cli shell: ## Open an interactive shell in the development container $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) bash push: ## git add ., prompt for commit message, push to both remotes @git add . @if git diff --cached --quiet; then \ echo "Nothing to commit (working tree clean after git add .)."; \ else \ printf "Commit message: "; \ read -r msg &2; \ exit 1; \ fi; \ git commit -m "$$msg"; \ fi @echo "Pushing to both remotes via origin..." @git push -u origin HEAD clean: ## Remove generated local artifacts rm -rf .coverage coverage.xml htmlcov .mypy_cache .pytest_cache .ruff_cache rm -f $(COMPOSE_OVERRIDE) ci: ## Offline quality gate + full API surface probe (Postgres) $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) sh -c '\ ruff format --check . && \ ruff check . && \ mypy && \ pytest $(PYTEST_OFFLINE) --cov=app --cov-report=term-missing --cov-report=xml' $(MAKE) test-surface ci-all: ## Full CI: offline + surface + remaining integration + client compatibility $(MAKE) ci $(MAKE) test-integration $(MAKE) test-compatibility release-build: ## Build the runtime image tagged for Docker Hub (no push) @test -n "$(VERSION)" || (echo "VERSION is empty; set VERSION=... or version in pyproject.toml" >&2; exit 1) @echo "Building $(DOCKER_IMAGE):$(VERSION) (target=runtime)" docker build \ --target runtime \ --build-arg APP_VERSION=$(VERSION) \ -t $(DOCKER_IMAGE):$(VERSION) \ $(if $(filter 1 true yes,$(PUSH_LATEST)),-t $(DOCKER_IMAGE):latest,) \ . release: release-build ## Build and push the runtime image to Docker Hub @echo "Pushing $(DOCKER_IMAGE):$(VERSION)" @docker push $(DOCKER_IMAGE):$(VERSION) @if [ "$(PUSH_LATEST)" = "1" ] || [ "$(PUSH_LATEST)" = "true" ] || [ "$(PUSH_LATEST)" = "yes" ]; then \ echo "Pushing $(DOCKER_IMAGE):latest"; \ docker push $(DOCKER_IMAGE):latest; \ fi @echo "Released $(DOCKER_IMAGE):$(VERSION)$(if $(filter 1 true yes,$(PUSH_LATEST)), and $(DOCKER_IMAGE):latest,)" release-up: ## Pull and start the published Hub stack (docker-compose.release.yml) IMAGE_TAG="$${IMAGE_TAG:-$(VERSION)}" DOCKER_IMAGE="$(DOCKER_IMAGE)" $(COMPOSE_RELEASE) pull IMAGE_TAG="$${IMAGE_TAG:-$(VERSION)}" DOCKER_IMAGE="$(DOCKER_IMAGE)" $(COMPOSE_RELEASE) up -d --wait release-down: ## Stop the published Hub stack $(COMPOSE_RELEASE) down release-seed: ## Seed the published Hub stack (PROFILE=small by default) SEED_PROFILE="$${PROFILE:-small}" \ SEED_VSPHERE_PROFILE="$${VSPHERE_PROFILE:-$${PROFILE:-small}}" \ IMAGE_TAG="$${IMAGE_TAG:-$(VERSION)}" DOCKER_IMAGE="$(DOCKER_IMAGE)" \ $(COMPOSE_RELEASE) run --rm --entrypoint python simulator -m app.simulation.seed_cli helm-deps: ## No-op placeholder (chart has no OCI dependencies) @echo "Chart $(HELM_CHART) vendors PostgreSQL templates; no helm dependency update required." helm-template: ## Render Helm manifests locally (requires helm) helm template vmware-sim $(HELM_CHART) \ -f $(HELM_CHART)/values-ingress-example.yaml \ --set certManager.email=docs@example.com \ --set secret.ticketSigningKey=docs-only-signing-key