modules: Phase A of multi-env — add env/envs parameter to app_roles and app_policy #2
Reference in New Issue
Block a user
Delete Branch "claude/multi-env-modules"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase A of the multi-environment evolution agreed in the erp design thread. Both Hashicorp Vault modules gain an optional env coordinate that defaults to
"prod"; by the elision rule, env=prod produces the existing single-env derived names character-for-character.Every existing app's
tofu planshould be a no-op. Backwards-compat reasoning detailed below.What changes
app_roles(called per-instance by each app'siac/main.tf):variables.tf: add optionalenv = "prod"main.tf: computelocal.instancevia the elision rule (env == "prod" ? local.name : "${local.name}-${local.env}") +local.owner_role(snake-case<name>_<env>_rolefor the Postgres owner)main.tf: substitutelocal.name→local.instancein all derived names (dynamic role name, k8s role name, SA bindings, token_policies)outputs.tf: addenv+instanceoutputs;kvv2_path_prefixnow derives fromlocal.instance(==local.namewhen env=prod → backwards-compat)app_policy(called per-repo with a list of envs):variables.tf: add optionalenvs = ["prod"]main.tf: computelocal.instances(one per env, with elision) andlocal.non_prod_instancesmain.tf: refactor the kvv2 ops rules to dynamic blocks iteratinglocal.instances. Preserves the original rule order (data → delete → undelete → destroy → metadata), so a prod-only app renders a byte-identical policy documentmain.tf:allowed_parameterblocks for the k8s role'sbound_service_account_names,bound_service_account_namespaces, andtoken_policiesnow use comprehensions overlocal.instances. For prod-only withinstances=[local.name], output matches the original 1-element arraysmain.tf: keepvault_policy.app(the env=prod runtime policy) at its original address; addvault_policy.app_non_prodviafor_eachovernon_prod_instancesfor the other envs. For prod-only apps, the for_each set is empty → no new resourcesTop-level wiring (
hashicorp-vault/iac/main.tf+variables.tf):variables.tf: addenvs = optional(list(string), ["prod"])to theapplicationsset-of-objects typemain.tf: passenvs = each.value.envsthrough to theapp_policiesmoduleBackwards-compatibility guarantee (per-app)
For an app
{name = "erp"}(noenvsdeclared → defaults to["prod"]):app_roles(called asmodule "app_roles" { name = "erp" }with noenv→ defaults to"prod"):local.instance == local.name == "erp"→ everylocal.namesubstitution produces the same stringlocal.owner_role == "erp_role"→ unchangedapp_policy(called from this repo'smain.tfwithenvs = ["prod"]):local.instances == ["erp"]→ all comprehensions produce 1-element arrays equal to the old static valueslocal.non_prod_instances == []→for_eachcreates noapp_non_prodresourcesdynamic "rule"blocks for kvv2 paths iterate once withrule.value == "erp", producing the original 5 rules in the original orderallowed_parameterforbound_service_account_nameswas[jsonencode(concat([var.name], var.service_account_names))]. Now:[for inst in local.instances : jsonencode(local.per_instance_sa_names[inst])]. For prod-only withinstances=[local.name]andvar.namelowercase: same single jsonencoded string. ✓allowed_parameterfortoken_policieswas[jsonencode(["default", local.name]), jsonencode([local.name, "default"])]. Now:flatten([for inst in local.instances : [jsonencode(["default", inst]), jsonencode([inst, "default"])]]). For prod-only: same 2-element list. ✓Verification
tofu validatepasses locallytofu planshows no diff against any existing app (this PR's CI run is the gate — please verify before merging)What's NOT in this PR
arcodange-org/factorychanges (postgres/iacschema,argocd/templates, runbook docs). Will follow once this PR is merged.arcodange-org/erp/iac/main.tffor_each + actually activating the sandbox. Follows Phases A + B.erp-sandbox. Follows Phase D.Reviewer guidance
The high-leverage check is the CI
tofu plan— every existing app's policies must be unchanged. If the plan reports any diff, please share so we can either tighten the dynamic-block behavior or add amovedblock.🤖 Generated with Claude Code
399cf38fb4toa3e121b468