feat(multi-env): Phase B — factory machinery env-capable (no activation) #16

Merged
arcodange merged 1 commits from claude/multi-env-phaseb into main 2026-06-28 16:53:42 +02:00
Owner

Summary

Phase B of ADR-0002. Makes postgres/iac, argocd, and the conventions docs multi-environment-capable without activating any sandbox. Every app stays prod-only, so this PR is behaviour-neutral — the machinery is in place but inert until Phase D flips a switch.

⚠️ The correctness gate for this PR is the CI tofu plan showing zero changes on postgres/iac — not the textual diff. postgres/iac provisions production databases; the design guarantees a no-op plan, but please confirm the plan is empty before merging.

Why it's a no-op (proven, not asserted)

postgres/iac — the applications variable becomes set(object({name, envs=optional([\"prod\"])})), and a local.app_instances flatten maps applications × envs → instances keyed by the elided id. For a prod-only app the key is the bare name, database=<app>, role=<app>_role — character-for-character identical to the old set(string) for_each. I verified the flatten output with a standalone tofu apply:

erp                 → { database = "erp",                 role = "erp_role" }
webapp              → { database = "webapp",              role = "webapp_role" }
crowdsec            → { database = "crowdsec",            role = "crowdsec_role" }
plausible           → { database = "plausible",           role = "plausible_role" }
dance-lessons-coach → { database = "dance-lessons-coach", role = "dance-lessons-coach_role" }

So postgresql_database.app_db[\"erp\"], postgresql_role.app_role[\"erp\"] (name erp_role), etc. keep their exact Terraform addresses and attributes → no resource is dropped or recreated.

argocdapps.yaml gains a range $app_attr.envs loop after the prod Application. Since no app declares envs, it renders nothing extra: helm template before vs after diffs to empty (verified). When an app does declare envs (Phase D), it additionally renders <app>-<env> Applications (shared repoURL, values-<env>.yaml overlay, own namespace) — I previewed erp.envs.sandbox and it produced a correct erp-sandbox Application alongside the unchanged erp one.

Files

File Change
postgres/iac/variables.tf applicationsset(object({name, envs}))
postgres/iac/main.tf local.app_instances elision flatten; per-app resources iterate it via each.key / each.value.{database,role}. Also a full tofu fmt pass (cosmetic — the pgbouncer function block reindents 4→2 spaces).
postgres/iac/terraform.tfvars string entries → { name = "..." }
argocd/templates/apps.yaml per-env Application loop (inert until an app sets envs)
doc/runbooks/new-web-app/conventions.md FR: new "Plusieurs environnements" section (elision rule, suffix, owner-role exception, erp/erp-sandbox table)
vibe/guidebooks/lab-ecosystem/naming-conventions.md EN mirror: env-coordinate section + "Two sandbox models" reconciling ADR-0001 (names repeat behind a cluster fence) vs ADR-0002 (<env> suffix in-cluster); Last Updated bumped; ADR-0002 cross-links

Note on the guidebook reconciliation

The existing naming-conventions guidebook argued names repeat across environments (and disparaged "erp-staging-style aliases"). That holds for the separate-cluster sandbox (ADR-0001). ADR-0002's in-cluster sibling has no cluster fence, so the <env> suffix is the disambiguator. I reframed that section to present both models cleanly rather than leave the contradiction.

Verification done locally

  • tofu validate (postgres/iac) passes; tofu fmt -check clean on my 3 tf files
  • flatten keys proven byte-identical to prod via standalone tofu apply
  • argocd helm template diff empty (prod render unchanged); Phase-D preview renders erp-sandbox correctly
  • all new doc cross-links resolve; Last Updated stamps bumped
  • CI tofu plan no-op on postgres/iac ← the merge gate (please confirm)

Not in this PR

  • Phase D — activate erp-sandbox (add envs=[\"prod\",\"sandbox\"] to erp in postgres tfvars + argocd values + erp iac/main.tf for_each). Real resources appear there, gated by its own plan review.
  • Phase E — DNS erp-sandbox.arcodange.lab + write-scoped ai_agent_sandbox Dolibarr user.

🤖 Generated with Claude Code

## Summary Phase B of [ADR-0002](https://gitea.arcodange.lab/arcodange-org/factory/src/branch/main/vibe/ADR/0002-per-application-environments.md). Makes `postgres/iac`, `argocd`, and the conventions docs **multi-environment-capable without activating any sandbox**. Every app stays prod-only, so this PR is **behaviour-neutral** — the machinery is in place but inert until Phase D flips a switch. > ⚠️ **The correctness gate for this PR is the CI `tofu plan` showing zero changes** on `postgres/iac` — not the textual diff. `postgres/iac` provisions production databases; the design guarantees a no-op plan, but please confirm the plan is empty before merging. ### Why it's a no-op (proven, not asserted) **postgres/iac** — the `applications` variable becomes `set(object({name, envs=optional([\"prod\"])}))`, and a `local.app_instances` flatten maps `applications × envs` → instances keyed by the elided id. For a prod-only app the key is the bare name, `database=<app>`, `role=<app>_role` — character-for-character identical to the old `set(string)` for_each. I verified the flatten output with a standalone `tofu apply`: ``` erp → { database = "erp", role = "erp_role" } webapp → { database = "webapp", role = "webapp_role" } crowdsec → { database = "crowdsec", role = "crowdsec_role" } plausible → { database = "plausible", role = "plausible_role" } dance-lessons-coach → { database = "dance-lessons-coach", role = "dance-lessons-coach_role" } ``` So `postgresql_database.app_db[\"erp\"]`, `postgresql_role.app_role[\"erp\"]` (name `erp_role`), etc. keep their exact Terraform addresses and attributes → no resource is dropped or recreated. **argocd** — `apps.yaml` gains a `range $app_attr.envs` loop after the prod Application. Since no app declares `envs`, it renders nothing extra: `helm template` before vs after diffs to **empty** (verified). When an app *does* declare `envs` (Phase D), it additionally renders `<app>-<env>` Applications (shared repoURL, `values-<env>.yaml` overlay, own namespace) — I previewed `erp.envs.sandbox` and it produced a correct `erp-sandbox` Application alongside the unchanged `erp` one. ### Files | File | Change | |---|---| | `postgres/iac/variables.tf` | `applications` → `set(object({name, envs}))` | | `postgres/iac/main.tf` | `local.app_instances` elision flatten; per-app resources iterate it via `each.key` / `each.value.{database,role}`. Also a full `tofu fmt` pass (cosmetic — the pgbouncer function block reindents 4→2 spaces). | | `postgres/iac/terraform.tfvars` | string entries → `{ name = "..." }` | | `argocd/templates/apps.yaml` | per-env Application loop (inert until an app sets `envs`) | | `doc/runbooks/new-web-app/conventions.md` | FR: new "Plusieurs environnements" section (elision rule, suffix, owner-role exception, erp/erp-sandbox table) | | `vibe/guidebooks/lab-ecosystem/naming-conventions.md` | EN mirror: env-coordinate section + "Two sandbox models" reconciling ADR-0001 (names repeat behind a cluster fence) vs ADR-0002 (`<env>` suffix in-cluster); `Last Updated` bumped; ADR-0002 cross-links | ### Note on the guidebook reconciliation The existing naming-conventions guidebook argued names *repeat* across environments (and disparaged "erp-staging-style aliases"). That holds for the **separate-cluster** sandbox (ADR-0001). ADR-0002's **in-cluster sibling** has no cluster fence, so the `<env>` suffix *is* the disambiguator. I reframed that section to present both models cleanly rather than leave the contradiction. ### Verification done locally - [x] `tofu validate` (postgres/iac) passes; `tofu fmt -check` clean on my 3 tf files - [x] flatten keys proven byte-identical to prod via standalone `tofu apply` - [x] argocd `helm template` diff empty (prod render unchanged); Phase-D preview renders `erp-sandbox` correctly - [x] all new doc cross-links resolve; `Last Updated` stamps bumped - [ ] **CI `tofu plan` no-op on postgres/iac** ← the merge gate (please confirm) ### Not in this PR - **Phase D** — activate `erp-sandbox` (add `envs=[\"prod\",\"sandbox\"]` to erp in postgres tfvars + argocd values + erp `iac/main.tf` for_each). Real resources appear there, gated by its own plan review. - **Phase E** — DNS `erp-sandbox.arcodange.lab` + write-scoped `ai_agent_sandbox` Dolibarr user. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
arcodange added 1 commit 2026-06-28 16:29:04 +02:00
ADR-0002 Phase B. Makes postgres/iac, argocd, and the conventions docs
multi-environment-capable WITHOUT activating any sandbox yet — every app
stays prod-only, so this change is behaviour-neutral:
  - postgres/iac `tofu plan` is a no-op (proven: the elision flatten keys
    are bare app names, db=<app>, role=<app>_role — identical addresses)
  - the argocd apps.yaml render is byte-identical (181→181 lines, diff
    empty) since no app declares `envs`

postgres/iac:
- variables.tf: `applications` becomes set(object({name, envs=optional(["prod"])}))
- main.tf: a `local.app_instances` flatten of applications × envs keyed by the
  elided instance id (env=prod → "<app>"); per-app resources iterate it and
  reference each.key / each.value.{database,role}. For prod-only apps every
  resource address + attribute is unchanged. (main.tf also got a full
  `tofu fmt` pass — the pgbouncer function block reindents 4→2 spaces, which
  is cosmetic; the correctness gate is the CI tofu plan, not the text diff.)
- terraform.tfvars: string entries → { name = "..." } objects.

argocd/templates/apps.yaml:
- after the prod Application, a `range $app_attr.envs` loop renders one extra
  Application per non-prod env: name/namespace `<app>-<env>`, shared repoURL,
  helm.valueFiles [values.yaml, values-<env>.yaml], per-env syncPolicy override.
  Renders nothing while no app sets `envs` → prod render unchanged.

docs:
- doc/runbooks/new-web-app/conventions.md (FR, authoritative): new section
  "Plusieurs environnements pour une même app" — elision rule, suffix rule,
  snake-case owner-role exception, erp/erp-sandbox table, ADR-0002 link.
- vibe/guidebooks/lab-ecosystem/naming-conventions.md (EN mirror): the env
  coordinate section + a "Two sandbox models" section reconciling the
  separate-cluster (ADR-0001, names repeat) vs in-cluster sibling (ADR-0002,
  <env> suffix) strategies; Last Updated bumped; ADR-0002 cross-links.

Activation (erp gets envs=["prod","sandbox"] in postgres tfvars + argocd
values + erp/iac) is Phase D, gated by its own plan review.

Refs ADR-0002 (factory#15). Phase A = tools#2 (merged). Phase C = erp#11 (merged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arcodange merged commit 235ff72ac0 into main 2026-06-28 16:53:42 +02:00
arcodange deleted branch claude/multi-env-phaseb 2026-06-28 16:53:44 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: arcodange-org/factory#16