Compare commits

...

2 Commits

Author SHA1 Message Date
Mike Kell 4a021a5711 feat: conditional Keycloak via compose profile + Makefile probe 2025-06-13 12:33:26 +00:00
Mike Kell 36976a3876 feat: conditional Keycloak via compose profile + Makefile probe 2025-06-13 12:32:16 +00:00
5 changed files with 119 additions and 27 deletions

9
.env Normal file
View File

@ -0,0 +1,9 @@
# .env ──────────────────────────────────────────
# Values you want to override permanently
# If an external Keycloak exists, point here;
# leave empty to fall back to the embedded container.
KEYCLOAK_URL=https://kc.kellsupport.com
# Custom project name (avoids long folder names in ~/.local/share/containers)
COMPOSE_PROJECT_NAME=cmmc-platform

View File

@ -1,19 +1,40 @@
# Root Makefile for CMMC-Platform local dev
compose = podman-compose -f dev-compose.yaml compose = podman-compose -f dev-compose.yaml
up: ## start local stack # ----------------------------------------------------------
$(compose) up -d # Helpers
# ----------------------------------------------------------
create-proxy-net:
@podman network exists nginx-proxy || podman network create nginx-proxy
down: ## stop stack # probe external Keycloak once and cache the flag
$(compose) down check-idp:
@echo "🔍 Probing $(KEYCLOAK_URL) for existing Keycloak..."
@if curl -sf "${KEYCLOAK_URL:-http://keycloak.local:8080}/realms/master" >/dev/null ; \
then echo "🥳 External Keycloak detected!"; echo 1 >.idp_flag ; \
else echo "🛠 No external Keycloak found."; echo 0 >.idp_flag ; fi
logs: ## follow logs # ----------------------------------------------------------
$(compose) logs -f # Lifecycle targets
# ----------------------------------------------------------
build: ## build FastAPI image locally build: ## Build FastAPI image
podman build -t cmmc-fastapi:latest -f .container-images/fastapi.Dockerfile . podman build -t cmmc-fastapi:latest -f .container-images/fastapi.Dockerfile .
test: ## run pytest quietly up: create-proxy-net check-idp ## Start stack (auto-starts Keycloak only if needed)
pytest -q @if [ "`cat .idp_flag`" = "1" ]; then \
$(compose) up -d ; \
else \
$(compose) --profile idp up -d ; \
fi
down: ## Stop stack
$(compose) down
.PHONY: up down logs build test logs: ## Tail logs
$(compose) logs -f
test: ## Run pytest
PYTHONPATH=. pytest -q
.PHONY: build up down logs test create-proxy-net check-idp

View File

@ -3,7 +3,11 @@ version: "3.9"
x-common-env: &common-env x-common-env: &common-env
TZ: "UTC" TZ: "UTC"
############################################################
# SERVICES
############################################################
services: services:
# ──────────────────────────────
kong: kong:
image: docker.io/library/kong:3.7 image: docker.io/library/kong:3.7
container_name: kong container_name: kong
@ -16,35 +20,51 @@ services:
volumes: volumes:
- ./kong/kong.yml:/config/kong.yml:ro - ./kong/kong.yml:/config/kong.yml:ro
ports: ports:
- "8000:8000" # proxy - "8000:8000" # proxy (handy for localhost curl)
- "8001:8001" # admin api - "8001:8001" # admin
networks: [internal, nginx-proxy]
# ──────────────────────────────
fastapi:
image: cmmc-fastapi:latest
container_name: fastapi
restart: unless-stopped
environment:
<<: *common-env
# Default to local container; overridden by external URL in Makefile/CI
KEYCLOAK_URL: "${KEYCLOAK_URL:-http://keycloak:8080}"
KEYCLOAK_REALM: "cmmc-platform-dev"
KEYCLOAK_CLIENT_ID: "frontend"
ports:
# keep reachable only from localhost, not LAN
- "127.0.0.1:8008:8000"
networks: [internal] networks: [internal]
# ──────────────────────────────
# Starts ONLY when profile `idp` is requested
keycloak: keycloak:
image: quay.io/keycloak/keycloak:25.0.0 image: quay.io/keycloak/keycloak:25.0.0
container_name: keycloak container_name: keycloak
command: start-dev command: start-dev
profiles: ["idp"] # ← optional profile flag
restart: unless-stopped restart: unless-stopped
environment: environment:
<<: *common-env <<: *common-env
KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin KEYCLOAK_ADMIN_PASSWORD: admin
KC_PROXY_HEADERS: xforwarded KC_PROXY_HEADERS: xforwarded
ports: healthcheck:
- "8080:8080" test: ["CMD", "curl", "-f", "http://localhost:8080/realms/master"]
networks: [internal] interval: 10s
retries: 5
fastapi: networks: [internal, nginx-proxy]
image: cmmc-fastapi:latest # built by CI or `make build`
container_name: fastapi
restart: unless-stopped
environment:
<<: *common-env
APP_ENV: dev
ports:
- "8008:8000"
networks: [internal]
############################################################
# NETWORKS
############################################################
networks: networks:
internal: internal:
driver: bridge driver: bridge
nginx-proxy: # external bridge Nginx-Proxy-Manager already uses
external: true
name: nginx-proxy

View File

@ -0,0 +1,40 @@
_format_version: "3.0"
_transform: true
#########################################################
# Upstream — FastAPI service running in podman-compose
#########################################################
services:
- name: fastapi-svc
host: fastapi # container alias on the internal network
port: 8000
protocol: http
routes:
# Public API
- name: api-root
paths: ["/api/"]
strip_path: true
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]
plugins:
# Enable CORS for local testing
- name: cors
config:
origins: ["*"]
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]
headers: ["Accept", "Content-Type", "Authorization"]
credentials: false
max_age: 3600
# Health probe exposed at /gateway-health
- name: gateway-health
paths: ["/gateway-health"]
strip_path: true
methods: ["GET"]
plugins:
# Global rate-limit (optional; remove if you dont need it yet)
- name: rate-limiting
config:
second: 25
policy: local

View File

@ -1,2 +1,4 @@
[pytest] [pytest]
pythonpath = . pythonpath = .
filterwarnings =
ignore:Please use `import python_multipart`:PendingDeprecationWarning