feat(multi-env): Phase D3 — erp iac creates erp-sandbox Vault auth + creds + KV #12

Merged
arcodange merged 1 commits from claude/phaseD-erp-sandbox-iac into main 2026-06-28 17:31:04 +02:00
Owner

Summary

Phase D3 of ADR-0002 — the erp-repo Vault layer. iac/main.tf iterates envs = ["prod", "sandbox"], so the app_roles module + the admin-bootstrap resources are materialised per environment.

What the apply adds (sandbox) — expected ~5 add, 0 change, 0 destroy

new resource path
module.app_roles["sandbox"].vault_kubernetes_auth_backend_role.role auth/kubernetes/role/erp-sandbox (token_policies ["default","erp-sandbox"] — the policy from tools#3)
module.app_roles["sandbox"].vault_database_secret_backend_role.role[0] postgres/creds/erp-sandbox — dynamic role GRANTs erp_sandbox_role (the snake owner role from factory#17), REVOKEs on DATABASE erp-sandbox
random_password.admin_initial_password["sandbox"] sandbox Dolibarr admin password
random_uuid.dolibarr_id["sandbox"] sandbox encryption id
vault_kv_secret_v2.dolibarr_admin_setup["sandbox"] kvv2/erp-sandbox/config

The moved blocks — why nothing is destroyed

Introducing for_each changes every resource's address (XX["prod"]). Without migration that's a destroy+create — and random_uuid.dolibarr_id carries prevent_destroy = true (it is the prod Dolibarr encryption id + paid-module binding), so a wrong/absent moved would hard-fail the apply rather than silently lose it. The four moved blocks re-key the existing prod resources into the map under "prod". The module's own internal moved (rolerole[0]) chains with the module re-key.

I verified the exact compound scenario (state on the pre-Phase-A module → new module's count+internal-moved and the for_each re-key landing in one apply) with two standalone tofu plan runs:

random_uuid.u has moved to random_uuid.u["prod"]
module.m.random_integer.r has moved to module.m["prod"].random_integer.r[0]
... ["sandbox"] will be created
Plan: 2 to add, 0 to change, 0 to destroy.

env=prod renders byte-identical to the single-env baseline (module elision rule), so the prod erp auth role, dynamic creds, admin secret + KV are unchanged.

Merge gate

CI tofu apply must show: prod resources moved (not destroyed/replaced), sandbox resources created, 0 destroyed. (prevent_destroy on dolibarr_id is the ultimate backstop — a bad plan errors instead of touching prod data.)

⚠️ Opening this PR may auto-trigger a duplicate pull_request CI run. I'll complete the OIDC handoff only for my manual dispatch and let the duplicate time out, to avoid state-lock contention.

Phase D sequence

  • D1 — factory postgres DB+role → factory#17
  • D2 — tools Vault policies → tools#3
  • D3 (this PR) — erp Vault auth + dynamic creds + KV config
  • D4 — factory ArgoCD erp-sandbox Application

🤖 Generated with Claude Code

## Summary **Phase D3** of [ADR-0002](https://gitea.arcodange.lab/arcodange-org/factory/src/branch/main/vibe/ADR/0002-per-application-environments.md) — the erp-repo Vault layer. `iac/main.tf` iterates `envs = ["prod", "sandbox"]`, so the `app_roles` module + the admin-bootstrap resources are materialised per environment. ### What the apply adds (sandbox) — expected ~5 add, 0 change, 0 destroy | new resource | path | |---|---| | `module.app_roles["sandbox"].vault_kubernetes_auth_backend_role.role` | `auth/kubernetes/role/erp-sandbox` (token_policies `["default","erp-sandbox"]` — the policy from tools#3) | | `module.app_roles["sandbox"].vault_database_secret_backend_role.role[0]` | `postgres/creds/erp-sandbox` — dynamic role GRANTs `erp_sandbox_role` (the snake owner role from factory#17), REVOKEs on DATABASE `erp-sandbox` | | `random_password.admin_initial_password["sandbox"]` | sandbox Dolibarr admin password | | `random_uuid.dolibarr_id["sandbox"]` | sandbox encryption id | | `vault_kv_secret_v2.dolibarr_admin_setup["sandbox"]` | `kvv2/erp-sandbox/config` | ### The `moved` blocks — why nothing is destroyed Introducing `for_each` changes every resource's address (`X` → `X["prod"]`). Without migration that's a destroy+create — and `random_uuid.dolibarr_id` carries `prevent_destroy = true` (it is the **prod Dolibarr encryption id + paid-module binding**), so a wrong/absent `moved` would *hard-fail* the apply rather than silently lose it. The four `moved` blocks re-key the existing prod resources into the map under `"prod"`. The module's own internal `moved` (`role` → `role[0]`) chains with the module re-key. I verified the **exact compound scenario** (state on the pre-Phase-A module → new module's `count`+internal-`moved` *and* the `for_each` re-key landing in one apply) with two standalone `tofu plan` runs: ``` random_uuid.u has moved to random_uuid.u["prod"] module.m.random_integer.r has moved to module.m["prod"].random_integer.r[0] ... ["sandbox"] will be created Plan: 2 to add, 0 to change, 0 to destroy. ``` `env=prod` renders byte-identical to the single-env baseline (module elision rule), so the prod erp auth role, dynamic creds, admin secret + KV are unchanged. ### Merge gate CI `tofu apply` must show: prod resources **moved** (not destroyed/replaced), sandbox resources **created**, **0 destroyed**. (`prevent_destroy` on `dolibarr_id` is the ultimate backstop — a bad plan errors instead of touching prod data.) > ⚠️ Opening this PR may auto-trigger a duplicate `pull_request` CI run. I'll complete the OIDC handoff only for my manual dispatch and let the duplicate time out, to avoid state-lock contention. ### Phase D sequence - D1 — factory postgres DB+role → [factory#17](https://gitea.arcodange.lab/arcodange-org/factory/pulls/17) ✅ - D2 — tools Vault policies → [tools#3](https://gitea.arcodange.lab/arcodange-org/tools/pulls/3) ✅ - **D3 (this PR)** — erp Vault auth + dynamic creds + KV config - D4 — factory ArgoCD `erp-sandbox` Application 🤖 Generated with [Claude Code](https://claude.com/claude-code)
arcodange added 1 commit 2026-06-28 17:27:27 +02:00
ADR-0002 Phase D, erp-repo layer. iac/main.tf iterates `envs = ["prod",
"sandbox"]` so the app_roles module + the admin-bootstrap resources are
materialised per environment:
  - module.app_roles["sandbox"] → auth/kubernetes/role/erp-sandbox +
    postgres/creds/erp-sandbox (dynamic role GRANTs erp_sandbox_role — the
    snake-case owner role created in factory#17 — and REVOKEs on DATABASE
    erp-sandbox; token_policies ["default","erp-sandbox"] = the policy from
    tools#3).
  - random_password.admin_initial_password["sandbox"], random_uuid.dolibarr_id
    ["sandbox"], and vault_kv_secret_v2.dolibarr_admin_setup["sandbox"]
    (kvv2/erp-sandbox/config) → the sandbox Dolibarr's own admin password +
    encryption id.

State migration via `moved` blocks: the pre-existing single-env resources are
re-keyed into the for_each map under "prod" so introducing for_each does NOT
destroy+recreate them. Critical for random_uuid.dolibarr_id (prevent_destroy =
true — prod encryption id + paid-module binding): a wrong/absent moved would
HARD-FAIL the apply rather than lose it. The module's internal moved
(role -> role[0]) chains with the module re-key. Verified the exact compound
(old-module state → new module count+moved + for_each, one apply) with two
standalone tofu plans: both show "X moved", sandbox created, 0 destroyed.

env=prod renders byte-identical to the single-env baseline (module elision
rule), so the prod erp Vault auth role, dynamic creds, admin secret + KV are
unchanged.

D3 of Phase D. D1 = factory#17 (DB+role, merged). D2 = tools#3 (Vault
policies, merged). D4 (ArgoCD Application) is next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
arcodange merged commit 354b40549d into main 2026-06-28 17:31:04 +02:00
arcodange deleted branch claude/phaseD-erp-sandbox-iac 2026-06-28 17:31:05 +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/erp#12