Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ansible-playbook -i inventory/hosts --extra-vars "vars_path=vars/dcm.yml" main.y
# Run a single deployment phase
ansible-playbook -i inventory/hosts --extra-vars "role_name=dcm_deploy" --extra-vars "task_name=validate_deployment" run_role_task.yml

# Check quadlet templates match upstream compose.yaml (runs locally, no inventory needed)
# Check quadlet templates match upstream deploy/compose.yaml (runs locally, no inventory needed)
ansible-playbook verify_compose_alignment.yml

# Run Molecule template rendering tests (no inventory needed)
Expand Down Expand Up @@ -81,9 +81,9 @@ The `pull_secret` must be a valid `.dockerconfigjson` JSON string, base64-encode

**Playbook entry points:** `main.yml` runs the full role. `run_role.yml`, `run_role_task.yml`, and `run_task.yml` are helpers for running individual roles or task phases. All four load variables from `vault_path`/`vault_dir` and `vars_path`/`vars_dir` extra-vars before executing.

**The role** (`roles/dcm_deploy/`) deploys 12 containers in six sequential phases defined in `tasks/main.yml`: prerequisites → generate_configs → deploy_quadlet_files → initialize_database → start_services → validate_deployment. An optional `resolve_rootless_vars` phase runs first (when `dcm_rootless: true`) to set internal facts that adapt all paths, ownership, and systemd scope for rootless Podman deployment.
**The role** (`roles/dcm_deploy/`) deploys 8 core containers (plus optional providers) in six sequential phases defined in `tasks/main.yml`: prerequisites → generate_configs → deploy_quadlet_files → initialize_database → start_services → validate_deployment. An optional `resolve_rootless_vars` phase runs first (when `dcm_rootless: true`) to set internal facts that adapt all paths, ownership, and systemd scope for rootless Podman deployment.

**Config source of truth:** Traefik config and PostgreSQL init SQL are NOT templated — they're copied from a clone of the upstream [api-gateway](https://github.com/dcm-project/api-gateway) repo at deploy time. Only `dcm.env.j2` and the quadlet unit files are Jinja2 templates.
**Config source of truth:** PostgreSQL init SQL is NOT templated — it's copied from a clone of the upstream [control-plane](https://github.com/dcm-project/control-plane) repo at deploy time. Only `dcm.env.j2` and the quadlet unit files are Jinja2 templates.

## Critical Naming Convention

Expand All @@ -104,15 +104,17 @@ Mixing these up breaks either systemd dependency ordering or container DNS resol
All quadlet templates are in `roles/dcm_deploy/templates/`. When editing:

- Every container must have `dcm-network-network.service` in both `After=` and `Requires=`
- Manager images use `Pull=always` (important for `main` tags that update)
- DCM images (control-plane, dcm-ui) use `Pull=always` (important for `main` tags that update)
- Volume mounts need SELinux suffixes: `:Z` for private (data volumes), `:z` for shared (read-only configs)
- Environment variables shared across managers go in `dcm.env.j2`; per-service vars go as `Environment=` directives in the container template
- Cross-reference env vars against upstream `compose.yaml` — the compose file is the source of truth
- Environment variables shared across services go in `dcm.env.j2`; per-service vars go as `Environment=` directives in the container template
- Cross-reference env vars against upstream `deploy/compose.yaml` — the compose file is the source of truth

## Provider Templates

Optional providers (kubevirt, k8s-container, acm-cluster, three-tier-demo) are gated by `dcm_provider_*` boolean vars. Each provider's required vars are validated before templating — kubevirt/k8s-container only need a kubeconfig, but ACM needs five additional fields (see "Deploying Providers" above). The kubeconfig env var name differs per provider (`KUBERNETES_KUBECONFIG`, `SP_K8S_KUBECONFIG`, `KUBECONFIG`) — don't copy-paste between templates.

Service providers register with the control-plane monolith at `http://control-plane:8080/api/v1alpha1` (not separate manager services).

The three-tier-demo provider requires `dcm_provider_k8s_container` to be enabled; it shares the `dcm_k8s_container_sp_kubeconfig` path.

## Rootless Support
Expand All @@ -129,13 +131,13 @@ The `dcm_quadlet_dir` and `dcm_config_dir` variables are overridden via `set_fac

Rootless and rootful are mutually exclusive on the same host. There is no migration path between them — data volumes are stored in different Podman storage locations.

## Syncing with Upstream api-gateway
## Syncing with Upstream control-plane

The upstream compose.yaml at `https://github.com/dcm-project/api-gateway` is the source of truth. When it changes, this role must be updated to match. Run `ansible-playbook verify_compose_alignment.yml` first — it checks template file existence, environment variable coverage, dependency ordering (`depends_on` vs `After=`/`Requires=`), port mappings, and image name alignment. If it passes, the templates are in sync with compose.
The upstream `deploy/compose.yaml` at `https://github.com/dcm-project/control-plane` is the source of truth. When it changes, this role must be updated to match. Run `ansible-playbook verify_compose_alignment.yml` first — it checks template file existence, environment variable coverage, dependency ordering (`depends_on` vs `After=`/`Requires=`), port mappings, and image name alignment. If it passes, the templates are in sync with compose.

### New service added to compose.yaml

First classify it: **core manager** (needs DB, always deployed) or **optional provider** (behind a feature flag, needs kubeconfig). If unclear, check the `dcm-project` GitHub org — a service image `quay.io/dcm-project/<name>` typically has its source at `https://github.com/dcm-project/<name>`. Read the repo's README and Dockerfile for env vars, health endpoints, and dependencies.
First classify it: **core service** (always deployed) or **optional provider** (behind a feature flag, needs kubeconfig). If unclear, check the `dcm-project` GitHub org — a service image `quay.io/dcm-project/<name>` typically has its source at `https://github.com/dcm-project/<name>`. Read the repo's README and Dockerfile for env vars, health endpoints, and dependencies.

Files to update (in order):

Expand All @@ -144,11 +146,10 @@ Files to update (in order):
3. `roles/dcm_deploy/tasks/deploy_quadlet_files.yml` — TWO places: add to the `dcm_expected_quadlets` set_fact (used for stale file detection) AND to the core deploy loop (or as a conditional block with provider validation for optional providers). These lists overlap but serve different purposes — `dcm_expected_quadlets` includes all enabled services, the deploy loop only includes unconditional core services
4. `roles/dcm_deploy/tasks/start_services.yml` — add to startup sequence and running-state verification loop
5. `roles/dcm_deploy/tasks/validate_deployment.yml` — add to container assertions (and health check if it has one)
6. `roles/dcm_deploy/tasks/initialize_database.yml` — add database name if the service needs one. Core manager DBs go in the unconditional loop; optional provider DBs get their own conditional check/create tasks gated by `dcm_provider_*`
7. `roles/dcm_deploy/templates/dcm-gateway.container.j2` — if it's a core manager routed through Traefik, add to `After=`/`Requires=`
8. `verify_compose_alignment.yml` — add to `expected_core_services` or `expected_optional_services`, `service_to_template` mapping, and `infra_image_mapping` if it's an infrastructure image (not a DCM manager)
9. `molecule/default/verify.yml` — add to the appropriate container list (`core_containers` or `provider_containers`) and any relevant assertion groups (`manager_containers`, `env_file_containers`)
10. `README.md` — update stack components table and variable reference
6. `roles/dcm_deploy/tasks/initialize_database.yml` — add database name if the service needs one. Core DBs go in the unconditional loop; optional provider DBs get their own conditional check/create tasks gated by `dcm_provider_*`
7. `verify_compose_alignment.yml` — add to `expected_core_services` or `expected_optional_services`, `service_to_template` mapping, and `infra_image_mapping` if it's an infrastructure image (not a DCM image)
8. `molecule/default/verify.yml` — add to the appropriate container list (`core_containers` or `provider_containers`) and any relevant assertion groups (`pull_always_containers`, `env_file_containers`)
9. `README.md` — update stack components table and variable reference

### Existing service changed (env vars, dependencies, ports)

Expand All @@ -160,11 +161,11 @@ Run `ansible-playbook verify_compose_alignment.yml` — it will catch most drift

### Image version bumps

Only update `defaults/main.yml` if compose pins a new major/minor (e.g., `postgres:16→17`, `traefik:v3.4→v3.5`). Manager versions default to `main` — don't change unless compose pins a specific tag.
Only update `defaults/main.yml` if compose pins a new major/minor (e.g., `postgres:16→17`). Control-plane and UI versions default to `main` — don't change unless compose pins a specific tag.

### Config file changes (traefik.yml, routes.yml, init SQL)
### Config file changes (postgres init SQL)

These are copied at deploy time from the cloned repo — usually no action needed here. But if new config files are added upstream (e.g., additional Traefik middleware), update `tasks/generate_configs.yml` to copy them.
These are copied at deploy time from the cloned control-plane repo — usually no action needed here unless new init files are added upstream, in which case update `tasks/generate_configs.yml` to copy them.

## CI

Expand All @@ -173,7 +174,7 @@ GitHub Actions runs six jobs on every PR targeting `main`:
- **check-clean-commits** — shared workflow that checks for merge commits, fixup/squash/WIP markers, vague commit messages
- **Lint** — `yamllint` + `ansible-lint`
- **Syntax check** — `ansible-playbook --syntax-check` on all playbooks
- **Compose-to-quadlet alignment** — runs `verify_compose_alignment.yml` against live upstream compose.yaml
- **Compose-to-quadlet alignment** — runs `verify_compose_alignment.yml` against live upstream `deploy/compose.yaml`
- **Quadlet template rendering** — `molecule test` renders all core quadlet templates locally and asserts naming conventions, dependency symmetry, SELinux suffixes, Pull policy, env file references, and network membership
- **Quadlet template rendering (rootless)** — `molecule test -s rootless` renders templates with `dcm_rootless: true` and asserts rootless-specific paths, `WantedBy=default.target`, and systemd scope

Expand Down
Loading
Loading