ADR-0006 · IaC y CI/CD: Terraform monorepo + GitHub Actions OIDC + trunk-based¶
- Status: Accepted (revisado 2026-05-13)
- Date: 2026-04-30 · Revisado: 2026-05-13
- Deciders: Cristian Fernández (Zerviz Group)
- Related: ADR-0002 (tenancy),
docs/discovery/00-phase1-prerequisites.md4.1–4.4 + Anexo A-05.
Revisión 2026-05-13 — Cuenta única + prefijo zen-¶
Cambio de alcance aprobado: Fase 2 se construye sobre la cuenta existente 450972188274 (no se crea cuenta ze-dev aún). Promoción a cuenta dedicada se hace después de la primera versión revisada.
- Naming:
zen-{env}-<resource-type>-<purpose>para todos los recursos nuevos (diferenciado del prefijo legacyze-*que no se toca). - Tags obligatorios:
Project=zengine-v2,ManagedBy=terraform,Environment={dev|qa|prod},Service=<bounded-context>,Tenant={shared|<uuid>},CostCenter,DataClassification. - Aislamiento lógico: IAM policies de los roles
zen-{env}-github-deployerconCondition: aws:ResourceTag/Project=zengine-v2, para impedir tocar recursosze-*legacy. - AWS Organizations / OUs: diferido a fase posterior; el bloque "Cuentas AWS" de abajo se mantiene como referencia para la promoción futura.
- Backend Terraform:
zen-terraform-state-{env}+zen-terraform-locks-{env}(S3+DDB en la misma cuenta). - Branch protection diferida (2026-05-13): la org
zervizestá en plan Free y GitHub no permite branch protection ni rulesets sobre repos privados Free. Compensación temporal: PRs por convención + revisión humana antes del merge. El scriptscripts/setup-branch-protection.shqueda listo para correr apenas se upgrade a GitHub Team (requerimiento antes de tocar prod). - Dominios públicos (sobre
zervizdev.compropiedad del owner; Route53 hosted zone gestionada eninfra/global/dns/): - Frontal cliente:
zen.zervizdev.com(prod),dev.zen.zervizdev.com,qa.zen.zervizdev.com→ SPA cliente, APIs públicas y webhooks externos. - Admin interno:
zen-admin.zervizdev.com(prod),dev.zen-admin.zervizdev.com,qa.zen-admin.zervizdev.com→ consola admin (builder, reporting). - Cognito Hosted UI:
auth.{env}.zen.zervizdev.com(ACM certs en us-east-1 para CloudFront/Cognito). - Los dominios legacy
*.zengine.onlinesiguen sirviendo al legacy y no se tocan.
Context¶
Asentado: Terraform como IaC, backend S3+DDB lock (patrón Lab golden path), GitHub Actions con OIDC, trunk-based con envs dev|qa|prod, monorepo único zerviz/zengine-platform con infra/envs/{dev,qa,prod}/.
Decision¶
Repositorio¶
- Único monorepo
zerviz/zengine-platform. Origen actualZEngine_unificadose publica allí en cuanto Fase 1 cierre. - Workspaces npm:
services/*,packages/*,apps/*. Build viaturbo run builden CI.
Terraform layout¶
infra/
├── modules/ # Reutilizable, sin estado propio
├── envs/{dev,qa,prod}/ # Stacks por env, cada uno con su backend
├── global/ # IAM, KMS keys de plataforma, Route53, OIDC
└── tenants/<id>/ # Stacks tier físico, aplicados con var tenant_id
- Backend por stack: S3
ze-terraform-state-<env>+ DDB lockze-terraform-locks-<env>(réplica del patrón Lablab-ms-terraform-state/lab-ms-terraform-locks). - Provider versions locked en
versions.tfpor stack. - Workspace strategy: un workspace por stack (no
terraform workspacemezclado). Cadainfra/envs/<env>/tiene subackend.tfpropio. - Naming/Tags estándar (asentado checklist R-09..R-14):
- Tags obligatorios (ver Revisión 2026-05-13):
Project=zengine-v2,Environment={dev|qa|prod},Tenant={shared|control-plane|<uuid>},Service=<bounded-context>,CostCenter,DataClassification,ManagedBy=terraform. - Naming:
zen-{env}-<resource-type>-<purpose>(prefijozen-para diferenciar del legacyze-*). - Linters obligatorios en PR:
terraform fmt -check,terraform validate,tflint,tfsec,checkov. Falla bloquea merge.
Cuentas AWS (diferido a fase posterior — ver Revisión 2026-05-13)¶
- Crear cuentas dedicadas vía AWS Organizations:
ze-platform(root org, sólo billing/policies).ze-dev(cuenta dev, primera prioridad).ze-qa,ze-prod(creadas cuando dev quede consolidado).ze-prod-tenant-<id>para cada tenant físico.- Stack
infra/global/organizations/define OUsWorkloads/{Dev,QA,Prod,Tenants}con SCPs (Service Control Policies) que prohíben acciones destructivas en prod sin break-glass role.
CI/CD (GitHub Actions + OIDC)¶
- OIDC trust: rol
ze-{env}-github-deployeren cada cuenta AWS, asumido por GH Actions víaaws-actions/configure-aws-credentials@v4con conditiontoken.actions.githubusercontent.com:sub = repo:zerviz/zengine-platform:ref:refs/heads/main(o tag específico para prod). - Workflows:
ci.yml(PR): lint TS + unit tests (Jest) + tfsec + checkov + semgrepp/owasp-top-ten,p/nodejs+npm audit --omit=dev. Sin AWS access.cd-dev.yml(merge a main): plan + apply Terraforminfra/envs/dev/+ build + deploy Lambdas + run integration tests LocalStack. Auto.cd-qa.yml: aprobación manual + plan + apply qa + smoke tests + parity tests fixture-based (gate del cutover).cd-prod.yml: aprobación manual de 2 personas + plan + apply prod + canary 10% → 50% → 100% por servicio + rollback automático si error rate > 1%.tenant-onboard.yml(workflow_dispatch): aprovisiona stackinfra/tenants/<id>/para tier físico. Inputs:tenant_id,tier=physical,aws_account_id.parity-gate.yml(scheduled + manual): corretests/parity/contra el ambiente nuevo y emite report. Bloquea cutover si falla.
Branching (asentado 4.4)¶
- Trunk-based:
mainsiempre desplegable. - Feature branches
feat/<scope>-<desc>con PR mandatorio. Merge sólo via squash. Lint/CI mandatorio. - Hotfix: branch
hotfix/<issue>desde el tag prod, deploy directo a prod con aprobación de emergencia. - Tags:
v<major>.<minor>.<patch>SemVer en commits que llegan a prod. Release notes auto-generadas porrelease-please.
Migración legacy → nuevo (asentado 5.1 cutover total)¶
- Ambiente actual
450972188274queda frozen desde el día 1 de Fase 2 (no se aceptan PRs que toquenaws/step-1-read-api/). - Cutover: cuando
parity-gate.ymlesté verde + smoke tests qa + aprobación humana, se hace switch DNS / API GW custom domain en una ventana de mantenimiento. - Rollback plan: DNS revierte al endpoint legacy; el ambiente legacy permanece intacto durante 90 días post-cutover.
Consequences¶
Positive¶
- Drift IaC eliminado (R-04). Todo cambio pasa por PR + plan + apply automatizado.
- OIDC elimina secrets de larga vida en GH (no más
AWS_ACCESS_KEY_IDen secrets de repo). - Trunk-based + canary + auto-rollback reducen riesgo en prod.
Negative¶
- Inversión inicial alta (~2 sprints sólo para módulos Terraform + workflows).
- Devs deben aprender Terraform + workflow gating.
Alternatives considered¶
- AWS CDK: evaluado, descartado por preferencia explícita por Terraform (consistencia con Lab + ecosistema).
- GitFlow: rechazado, complica releases en SaaS multi-tenant.
- Terraform Cloud: rechazado por costo + ya tenemos S3+DDB pattern probado.
- CodePipeline: rechazado, GH Actions ya es la fuente y OIDC es first-class.
Tenant-isolation impact¶
- Stack tier físico aplicado en cuenta del tenant ⇒ aislamiento Terraform-level.
tenant-onboard.ymlrequiere aprobación manual + audit log.
Blast radius¶
- SCPs en OU prod previenen
iam:DeleteRole,s3:DeleteBucket,kms:ScheduleKeyDeletionsalvo break-glass role. - Apply prod requiere 2-person approval.
Cost note¶
- GitHub Actions: free para org pequeño; > 2k min/mes ~ USD 0.008/min.
- AWS Organizations: gratis. Cuentas adicionales: gratis (paga su propio uso).
- Terraform state S3+DDB: < USD 1/mes por env.
ISO 27001 controls touched¶
- A.12.1.2 (gestión de cambios): PR + plan obligatorio.
- A.12.1.4 (separación de entornos): cuentas AWS distintas dev/qa/prod.
- A.12.6.1 (gestión de vulnerabilidades técnicas): tfsec + checkov + semgrep + dependabot.
- A.14.2.2 (procedimientos de control de cambios): trunk-based + canary + rollback.
Sources¶
docs/discovery/00-phase1-prerequisites.md4.1, 4.2, 4.3, 4.4, 5.1, 5.3 + Anexo A-05.docs/discovery/07-lab-golden-path.md(patrón Terraform).- AWS docs: AWS Organizations, OIDC for GitHub Actions, SCPs.