Files
Metamorph/Makefile

215 lines
7.5 KiB
Makefile
Raw Permalink Normal View History

2026-05-11 06:16:00 +02:00
.DEFAULT_GOAL := help
SHELL := /bin/bash
# Load .env if present so targets can use the same variables as compose.
ifneq (,$(wildcard ./.env))
include .env
export
endif
# === Container engine detection (docker OR podman) ============================
#
# Auto-detects on PATH, with docker preferred when both are installed.
# Override either variable from the environment or the command line:
# make up ENGINE=podman
# make up COMPOSE="podman compose"
#
ENGINE ?= $(shell \
if command -v docker >/dev/null 2>&1; then echo docker; \
elif command -v podman >/dev/null 2>&1; then echo podman; \
fi)
ifeq ($(strip $(ENGINE)),)
$(error Neither docker nor podman found in PATH. Install one, or set ENGINE=...)
endif
# Pick the right compose driver based on the chosen engine.
# - docker → "docker compose" (compose v2 plugin)
# - podman 4.0+ → "podman compose"
# - older podman → "podman-compose" (legacy Python wrapper)
ifndef COMPOSE
ifeq ($(ENGINE),docker)
COMPOSE := docker compose
else
COMPOSE := $(shell \
if podman compose version >/dev/null 2>&1; then echo "podman compose"; \
elif command -v podman-compose >/dev/null 2>&1; then echo "podman-compose"; \
else echo "podman compose"; fi)
endif
endif
# Project name is mostly used to look up volumes / containers via raw engine calls.
PROJECT ?= metamorph
# Suppress the noisy `>>>> Executing external compose provider …` banner that
# `podman compose` emits on every invocation (harmless, but spammy in logs).
export PODMAN_COMPOSE_WARNING_LOGS = false
.PHONY: help env engine up down build rebuild logs logs-api ps health shell-api shell-db psql \
dev dev-api dev-front lint lint-api lint-front fmt test test-api test-front \
e2e e2e-install e2e-report e2e-up wait-healthy \
migrate migrate-down migrate-revision migrate-status \
seed-mitre print-install-token print-install-token-force \
volumes inspect-health clean
help: ## Show this help
@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_0-9-]+:.*##/ {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
@printf "\n Container engine in use: \033[33m%s\033[0m | compose: \033[33m%s\033[0m\n" "$(ENGINE)" "$(COMPOSE)"
engine: ## Print the detected container engine and compose driver
@echo "ENGINE=$(ENGINE)"
@echo "COMPOSE=$(COMPOSE)"
env: ## Bootstrap a local .env from .env.example if missing
@test -f .env || (cp .env.example .env && echo "Created .env — edit it before 'make up'")
# === Compose lifecycle ========================================================
up: env ## Build (if needed) and start all services
$(COMPOSE) up -d --build
down: ## Stop and remove containers (keep volumes)
$(COMPOSE) down
build: ## Build images without starting
$(COMPOSE) build
rebuild: ## Force rebuild without cache
$(COMPOSE) build --no-cache
logs: ## Tail logs from all services
$(COMPOSE) logs -f --tail=200
logs-api: ## Tail only the api container logs (useful to inspect JSON log lines)
$(COMPOSE) logs -f --tail=200 api
ps: ## List running services
$(COMPOSE) ps
shell-api: ## Shell into the api container
$(COMPOSE) exec api bash
shell-db: ## Shell into the db container
$(COMPOSE) exec db sh
psql: ## Open psql in the db container
$(COMPOSE) exec db psql -U $(POSTGRES_USER) -d $(POSTGRES_DB)
# === Container introspection (engine-agnostic) ================================
volumes: ## List the named volumes created by this project
@$(ENGINE) volume ls --filter "name=$(PROJECT)_"
inspect-health: ## Print the health status of every container in the project
@for c in $(PROJECT)-db $(PROJECT)-api $(PROJECT)-front; do \
printf "%-30s " "$$c"; \
$(ENGINE) inspect --format '{{.State.Health.Status}}' "$$c" 2>/dev/null || echo "(no-healthcheck or stopped)"; \
done
# === Local dev (no container) =================================================
dev: ## Run api + front locally in parallel (Ctrl-C stops both)
@$(MAKE) -j2 --no-print-directory dev-api dev-front
dev-api: ## Run Flask in dev mode
cd backend && APP_ENV=dev uv run flask --app app.main run --debug --host 0.0.0.0 --port 8000
dev-front: ## Run Vite dev server
cd frontend && npm run dev
# === Quality ==================================================================
lint: lint-api lint-front ## Lint everything
lint-api:
cd backend && uv run ruff check . && uv run ruff format --check .
lint-front:
cd frontend && npm run lint && npm run typecheck
fmt: ## Auto-format
cd backend && uv run ruff format .
cd frontend && npm run format
test: test-api test-front ## Run all tests
test-api: ## Run backend pytest in an ephemeral container against the live DB
@echo "Building backend test image (target: test)…"
@$(ENGINE) build -q --target test -t metamorph-api-test ./backend > /dev/null
@echo "Running pytest…"
$(ENGINE) run --rm \
--network $(PROJECT)_metamorph \
-e APP_ENV=test \
-e POSTGRES_DB=$(POSTGRES_DB) \
-e POSTGRES_USER=$(POSTGRES_USER) \
-e POSTGRES_PASSWORD=$(POSTGRES_PASSWORD) \
-e POSTGRES_HOST=db \
-e POSTGRES_PORT=5432 \
-e JWT_SECRET=test-only-secret-not-checked-in-this-mode \
-e LOG_LEVEL=WARNING \
-e FRONT_ORIGIN=http://localhost:8080 \
metamorph-api-test
test-front:
cd frontend && npm test --if-present
# === End-to-end tests (Playwright) ============================================
e2e-install: ## Install Playwright deps + chromium browser (use sudo on Debian/Ubuntu)
cd e2e && npm install && npx playwright install --with-deps chromium
e2e: wait-healthy ## Run the e2e suite against the running stack
cd e2e && BASE_URL=http://localhost:$(or $(HOST_FRONT_PORT),8080) npm test
e2e-report: ## Open the latest Playwright HTML report
cd e2e && npx playwright show-report
e2e-up: ## Bring the stack up, wait healthy, then run e2e
$(MAKE) up
$(MAKE) e2e
health: ## Curl the health endpoint via the front nginx (one shot)
@curl -sSf "http://localhost:$(or $(HOST_FRONT_PORT),8080)/api/v1/health" \
&& echo "" \
|| (echo " unreachable — is 'make up' done?"; exit 1)
wait-healthy: ## Wait until the front+api are reachable (60s timeout)
@port=$(or $(HOST_FRONT_PORT),8080); \
echo "Waiting for http://localhost:$$port/api/v1/health …"; \
for i in $$(seq 1 30); do \
if curl -sf "http://localhost:$$port/api/v1/health" > /dev/null; then \
echo " ready after $$((i*2))s"; exit 0; \
fi; \
sleep 2; \
done; \
echo " timeout after 60s — check 'make ps' and 'make logs'"; exit 1
# === App-specific commands (placeholders for later milestones) ================
migrate: ## Apply DB migrations (alembic upgrade head, runs inside the api container)
$(COMPOSE) exec api alembic upgrade head
migrate-down: ## Roll back the latest migration
$(COMPOSE) exec api alembic downgrade -1
migrate-revision: ## Generate a new autogenerated migration: make migrate-revision MSG="my message"
@test -n "$(MSG)" || (echo "Usage: make migrate-revision MSG=\"short description\""; exit 1)
$(COMPOSE) exec api alembic revision --autogenerate -m "$(MSG)"
migrate-status: ## Show current revision and any pending migrations
$(COMPOSE) exec api alembic current
@echo "---"
$(COMPOSE) exec api alembic heads
seed-mitre: ## Seed MITRE ATT&CK Enterprise dataset (M4)
$(COMPOSE) exec api flask --app app.cli metamorph seed-mitre
print-install-token: ## Print the bootstrap install token (M2)
$(COMPOSE) exec api flask --app app.cli metamorph print-install-token
print-install-token-force: ## Force-mint a fresh install token (M2, --force)
$(COMPOSE) exec api flask --app app.cli metamorph print-install-token --force
clean: ## Remove containers, networks AND volumes (DESTRUCTIVE)
$(COMPOSE) down -v