COMPOSE ?= docker compose SERVICE_DEV := dev SERVICE_SIM := simulator PYTEST_OFFLINE := -m "not integration and not compatibility" # Docker Hub release image (runtime target only — not the local bind-mount "dev" image). DOCKERHUB_USER ?= inecs IMAGE_NAME ?= ovirt-api-simulator VERSION ?= $(shell cat VERSION 2>/dev/null || echo 0.1.0) DOCKER_IMAGE ?= $(DOCKERHUB_USER)/$(IMAGE_NAME) PUSH_LATEST ?= 1 COMPOSE_RELEASE ?= $(COMPOSE) -f docker-compose.release.yml HELM_CHART ?= ./helm/ovirt-api-simulator LAB ?= ./pulumi-tests # Remotes used by `make push` (GitHub + antropoff). GIT_REMOTES ?= origin antropoff # Preferred host ports for `make up-local` (override / .env stay gitignored). LOCAL_ENGINE_PORT ?= 6443 LOCAL_UI_PORT ?= 6080 .PHONY: bump-patch bump-minor version-commit help install format lint typecheck test test-unit test-integration test-contract \ test-surface test-versions coverage up down restart logs seed seed-small seed-large seed-big seed-demo \ audit-seed-dump smoke clean ci shell \ helm-template generate-packs \ release release-build release-up release-down release-seed \ test-pulumi-smoke test-pulumi pulumi-tests \ test-smoke-all test-all clean-test-resources push up-local down-local help: ## Show available commands @awk 'BEGIN {FS = ":.*## "}; /^[a-zA-Z0-9_-]+:.*## / {printf "%-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST) @echo @echo "Version: $$(cat VERSION 2>/dev/null || echo 0.1.0) (make bump-patch | bump-minor)" install: ## Build runtime and development images @test -f .env || cp .env.example .env $(COMPOSE) build simulator $(SERVICE_DEV) generate-packs: ## Regenerate contracts/ovirt series packs $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) python tools/ovirt_api_inventory/generate_packs.py 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 app tests typecheck: ## Run strict mypy checks $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) mypy app/ovirt app/web/routes.py app/main.py app/config.py app/lifespan.py 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 tests/ovirt -q test-integration: ## Run tests that require PostgreSQL @test -f .env || cp .env.example .env $(COMPOSE) up -d postgres migrate $(COMPOSE) run --rm $(SERVICE_DEV) pytest -m integration tests/ovirt -q test-contract: ## Validate oVirt contract packs $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest -m contract tests/ovirt -q test-surface: ## Probe Engine API surface across series @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait $(COMPOSE) run --rm --no-deps --entrypoint python $(SERVICE_SIM) -m pytest tests/ovirt -q --tb=line test-versions: ## All Engine series packs with seeded inventory assertions @test -f .env || cp .env.example .env $(COMPOSE) up -d --wait $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.ovirt.seed_cli --profile minimal $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest -m integration tests/ovirt/test_api_versions.py -q --tb=short coverage: ## Offline coverage $(COMPOSE) run --rm --no-deps $(SERVICE_DEV) pytest $(PYTEST_OFFLINE) --cov=app/ovirt --cov-report=term-missing up: ## Start PostgreSQL, simulator, and Engine gateway @test -f .env || cp .env.example .env $(COMPOSE) up -d --build --wait # Writes gitignored docker-compose.override.yml (+ syncs ports into .env) when # default 443/5000 are busy (common on macOS). Compose auto-merges the override. up-local: ## Start stack with local port override (not committed) @set -e; \ test -f .env || cp .env.example .env; \ pick_port() { \ preferred="$$1"; shift; \ for p in "$$preferred" "$$@"; do \ if ! lsof -nP -iTCP:"$$p" -sTCP:LISTEN >/dev/null 2>&1; then \ echo "$$p"; return 0; \ fi; \ done; \ echo "No free port among: $$preferred $$*" >&2; exit 1; \ }; \ engine_port=$$(pick_port "$(LOCAL_ENGINE_PORT)" 6443 7443 8443 16443); \ ui_port=$$(pick_port "$(LOCAL_UI_PORT)" 6080 7080 8080 16080); \ printf '%s\n' \ '# Generated by `make up-local` — gitignored, do not commit.' \ 'services:' \ ' api-gateway:' \ ' ports:' \ " - \"$${engine_port}:443\"" \ " - \"$${ui_port}:5000\"" \ > docker-compose.override.yml; \ if grep -q '^OVIRT_ENGINE_PORT=' .env; then \ sed -i.bak -e "s/^OVIRT_ENGINE_PORT=.*/OVIRT_ENGINE_PORT=$${engine_port}/" \ -e "s/^OVIRT_UI_PORT=.*/OVIRT_UI_PORT=$${ui_port}/" .env && rm -f .env.bak; \ else \ printf '\nOVIRT_ENGINE_PORT=%s\nOVIRT_UI_PORT=%s\n' "$$engine_port" "$$ui_port" >> .env; \ fi; \ echo "Local ports → Engine https://127.0.0.1:$${engine_port} UI http://127.0.0.1:$${ui_port}"; \ echo "Wrote docker-compose.override.yml (gitignored)"; \ $(COMPOSE) up -d --build --wait; \ echo "up-local ok" down-local: ## Stop stack and remove gitignored local port override @$(COMPOSE) down @rm -f docker-compose.override.yml @if [ -f .env ]; then \ if grep -q '^OVIRT_ENGINE_PORT=' .env; then \ sed -i.bak -e 's/^OVIRT_ENGINE_PORT=.*/OVIRT_ENGINE_PORT=443/' \ -e 's/^OVIRT_UI_PORT=.*/OVIRT_UI_PORT=5000/' .env && rm -f .env.bak; \ fi; \ fi @echo "down-local ok (removed docker-compose.override.yml)" down: ## Stop local services $(COMPOSE) down restart: ## Rebuild and restart the stack @test -f .env || cp .env.example .env $(COMPOSE) up -d --build --force-recreate --wait logs: ## Follow logs $(COMPOSE) logs -f seed: ## Seed minimal lab $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.ovirt.seed_cli --profile minimal seed-small: ## Seed small cluster (3 hosts · 50 VMs) $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.ovirt.seed_cli --profile small seed-large: ## Seed large cluster (10 hosts · 1000 VMs) $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.ovirt.seed_cli --profile large seed-big: ## Seed big cluster (30 hosts · 2000 VMs) $(COMPOSE) run --rm --entrypoint python $(SERVICE_SIM) -m app.ovirt.seed_cli --profile big seed-demo: seed-large ## Alias: large cluster (legacy name) audit-seed-dump: ## Contract dump audit (collections+entities) vs live Engine @set -a; [ -f .env ] && . ./.env; set +a; \ python3 tools/audit_seed_dump.py \ "https://127.0.0.1:$${OVIRT_ENGINE_PORT:-443}" \ "$${OVIRT_SERIES:-4.5}" smoke: ## Quick Engine auth + list VMs smoke @set -a; [ -f .env ] && . ./.env; set +a; \ curl -skf -u 'admin@internal:secret' -H 'Accept: application/json' -H 'Version: 4' \ "https://127.0.0.1:$${OVIRT_ENGINE_PORT:-443}/ovirt-engine/api/vms" >/dev/null @echo "smoke ok" helm-template: ## Render Helm chart helm template ovirt-sim $(HELM_CHART) clean: ## Remove caches rm -rf .pytest_cache .mypy_cache .ruff_cache .coverage coverage.xml find . -type d -name __pycache__ -prune -exec rm -rf {} + ci: format lint test-unit ## Local CI gate shell: ## Dev shell in container $(COMPOSE) run --rm $(SERVICE_DEV) bash 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=minimal by default) SEED_PROFILE="$${PROFILE:-minimal}" IMAGE_TAG="$${IMAGE_TAG:-$(VERSION)}" DOCKER_IMAGE="$(DOCKER_IMAGE)" \ $(COMPOSE_RELEASE) run --rm --entrypoint python simulator -m app.ovirt.seed_cli --profile "$${PROFILE:-minimal}" # --- Pulumi contract-coverage lab (pulumi-tests) --- test-pulumi-smoke: ## Pulumi contract-coverage smoke $(MAKE) -C $(LAB) test-pulumi-smoke test-pulumi: ## Pulumi full contract coverage (all series × all ops) $(MAKE) -C $(LAB) test-pulumi pulumi-tests: test-pulumi ## Alias: full Pulumi contract coverage suite test-smoke-all: ## Client lab smoke $(MAKE) -C $(LAB) test-smoke-all test-all: ## Client lab full suite (Pulumi contract coverage) $(MAKE) -C $(LAB) test-all clean-test-resources: ## Cleanup lab-created resources $(MAKE) -C $(LAB) clean-test-resources # --- Git: stage, commit (prompt), push to both remotes --- # Multi-line commit: paste message, then Ctrl-D (same UX as proxmox-api-simulator). # Optional: make push MSG='one-line message' bump-patch: ## VERSION +0.0.1 (pyproject + Helm) @PYTHONPATH=. python3 scripts/bump_version.py patch @echo "VERSION → $$(cat VERSION)" bump-minor: ## VERSION +0.1.0 (pyproject + Helm) @PYTHONPATH=. python3 scripts/bump_version.py minor @echo "VERSION → $$(cat VERSION)" version-commit: ## Stage version files and commit "Bump version to …" @git add VERSION pyproject.toml helm/ovirt-api-simulator/Chart.yaml helm/ovirt-api-simulator/values.yaml @if git diff --cached --quiet; then \ echo "Nothing to commit for version files."; \ else \ git commit -m "Bump version to $$(cat VERSION)."; \ fi push: ## bump-patch + commit version + push origin @$(MAKE) bump-patch @$(MAKE) version-commit @set -e; \ git add .; \ echo "=== staged ==="; \ git status --short; \ echo; \ if git diff --cached --quiet; then \ echo "Nothing else to commit — pushing current branch."; \ else \ echo "Enter commit message for remaining changes, then Ctrl-D:"; \ msg=$$(cat &2; exit 1; fi; \ git commit -m "$$msg"; \ fi; \ echo "Pushing to both remotes:"; \ git remote get-url --push --all origin | sed 's/^/ - /'; \ git push origin HEAD