Milestone 3
This commit is contained in:
214
Makefile
Normal file
214
Makefile
Normal file
@@ -0,0 +1,214 @@
|
||||
.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
|
||||
Reference in New Issue
Block a user