Governance Standards Iac Terraform - Azure/az-prototype GitHub Wiki

Terraform

Standards for Terraform module layout, variable naming, and resource organization that all terraform-agent output must follow.

Domain: terraform


Checks (10)

Check Description
STAN-TF-001 Standard File Layout: Every Terraform module must use the standard file layout: main.tf (resources only), variables.tf (inputs), outputs.tf (outputs), locals.tf (computed values), providers.tf (terraform {}, required_providers, provider config, and backend). The terraform {} block — including required_providers — MUST appear in exactly ONE file (providers.tf). Do NOT create a separate versions.tf. main.tf must NOT contain terraform {} or provider {} blocks. Additional files are allowed for complex modules but must be logically named (e.g., networking.tf, iam.tf).
STAN-TF-002 Variable Conventions: All variables must have a description and a type constraint. Use snake_case for variable names. Provide defaults for optional values. Use validation blocks for constrained inputs.
STAN-TF-003 Resource Naming via Variables or Locals: Resource names should come from variables with defaults set to the deployment plan computed_name, or from locals that derive names from project variables. Variable defaults from the deployment plan (e.g., "pi-rg-worker-dev-wus3") are acceptable — the naming strategy already computed them. Do NOT force computed_name values into locals when they are already correct as variable defaults.
STAN-TF-004 One Resource Type Per File for Complex Modules: When a module manages more than 5 resources, split logically related resources into separate files (e.g., networking.tf for subnets and NSGs, iam.tf for role assignments).
STAN-TF-005 Use Data Sources for Existing Resources: Reference existing resources (resource groups, VNets, identities) via data sources, not by hardcoding IDs. Pass resource IDs as variables only when the resource is managed outside the module.
STAN-TF-006 Cross-Stage Dependencies via Remote State: Multi-stage deployments MUST use terraform_remote_state data sources to read outputs from prior stages. NEVER hardcode resource names, IDs, or keys that belong to another stage. Each stage reads what it needs from prior stage state files and passes values via data.terraform_remote_state..outputs..
STAN-TF-007 Consistent Backend Configuration: Backend configuration must be consistent across all stages. For POC deployments, use local state (no backend block or backend "local" with a path). For production, use backend "azurerm" with ALL required fields populated with literal values (resource_group_name, storage_account_name, container_name, key). NEVER use variable references (var.*) in backend config — Terraform does not support them. NEVER leave required backend fields empty. If using remote backend, all stages must reference the same storage account and container, differing only in key.
STAN-TF-008 Complete Stage Outputs: Every stage's outputs.tf MUST export all resource names, IDs, and endpoints that ANY downstream stage or application needs. At minimum: resource group name(s), managed identity client_id/principal_id, service endpoints, workspace IDs. NEVER output sensitive values (keys, connection strings) — if local auth is disabled, omit keys entirely.
STAN-TF-009 Complete and Robust deploy.sh: Every stage MUST include a deploy.sh that is syntactically complete and runnable. It must use set -euo pipefail, include Azure login verification, run terraform init/plan/apply, export outputs to JSON, and include error handling via trap. NEVER truncate the script or leave strings unclosed.
STAN-TF-010 Companion Resources for Disabled Auth: When disabling local/key-based authentication on any service (local_authentication_disabled = true, shared_access_key_enabled = false), the SAME stage MUST also create: (1) a user-assigned managed identity, (2) RBAC role assignments granting that identity access, (3) outputs for the identity's client_id and principal_id. Without these companion resources, applications cannot authenticate and the deployment is broken.

STAN-TF-001

Standard File Layout: Every Terraform module must use the standard file layout: main.tf (resources only), variables.tf (inputs), outputs.tf (outputs), locals.tf (computed values), providers.tf (terraform {}, required_providers, provider config, and backend). The terraform {} block — including required_providers — MUST appear in exactly ONE file (providers.tf). Do NOT create a separate versions.tf. main.tf must NOT contain terraform {} or provider {} blocks. Additional files are allowed for complex modules but must be logically named (e.g., networking.tf, iam.tf).

Rationale: Consistent file organization makes code reviewable and prevents merge conflicts across stages.
Agents: terraform-agent

Examples

  • main.tf — resource definitions only (no terraform {} or provider {} blocks)
  • variables.tf — all input variable declarations with descriptions and types
  • outputs.tf — all output value declarations
  • locals.tf — computed local values and naming conventions
  • providers.tf — terraform {}, required_providers, backend, and provider configuration (ONE file, never duplicated)

STAN-TF-002

Variable Conventions: All variables must have a description and a type constraint. Use snake_case for variable names. Provide defaults for optional values. Use validation blocks for constrained inputs.

Rationale: Parameterized configurations allow reuse across environments and prevent hardcoded values.
Agents: terraform-agent

Examples

  • variable "location" { type = string; description = "Azure region" }
  • Use validation blocks for SKU names, IP ranges, etc.

STAN-TF-003

Resource Naming via Variables or Locals: Resource names should come from variables with defaults set to the deployment plan computed_name, or from locals that derive names from project variables. Variable defaults from the deployment plan (e.g., "pi-rg-worker-dev-wus3") are acceptable — the naming strategy already computed them. Do NOT force computed_name values into locals when they are already correct as variable defaults.

Rationale: The CLI naming module computes resource names per the project naming strategy. Variable defaults carry these computed names and are overridable at deploy time.
Agents: terraform-agent

Examples

  • variable "resource_group_name" { default = "pi-rg-worker-dev-wus3" }
  • locals { identity_name = "${var.zone_prefix}-id-worker-${var.environment}-${var.region_short}" }
  • resource "azapi_resource" "resource_group" { name = var.resource_group_name }

STAN-TF-004

One Resource Type Per File for Complex Modules: When a module manages more than 5 resources, split logically related resources into separate files (e.g., networking.tf for subnets and NSGs, iam.tf for role assignments).

Rationale: Modular Bicep templates promote reuse and simplify testing of individual components.
Agents: terraform-agent

Examples

  • networking.tf — VNet, subnets, NSGs, route tables
  • iam.tf — role assignments, managed identities
  • monitoring.tf — diagnostic settings, alerts

STAN-TF-005

Use Data Sources for Existing Resources: Reference existing resources (resource groups, VNets, identities) via data sources, not by hardcoding IDs. Pass resource IDs as variables only when the resource is managed outside the module.

Rationale: Parameterized configurations allow reuse across environments and prevent hardcoded values.
Agents: terraform-agent

Examples

  • data "azapi_client_config" "current" {} for tenant/subscription IDs
  • data "azapi_resource" "existing_rg" { type = "Microsoft.Resources/resourceGroups@2024-03-01"; name = var.rg_name }

STAN-TF-006

Cross-Stage Dependencies via Remote State: Multi-stage deployments MUST use terraform_remote_state data sources to read outputs from prior stages. NEVER hardcode resource names, IDs, or keys that belong to another stage. Each stage reads what it needs from prior stage state files and passes values via data.terraform_remote_state..outputs..

Rationale: Proper state management prevents cross-stage conflicts and ensures reliable deployments.
Agents: terraform-agent, cloud-architect

Examples

  • data "terraform_remote_state" "stage1" { backend = "azurerm"; config = { key = "stage1.tfstate" } }
  • data "azapi_resource" "resource_group" { type = "Microsoft.Resources/resourceGroups@2024-03-01"; name = data.terraform_remote_state.stage1.outputs.resource_group_name }

STAN-TF-007

Consistent Backend Configuration: Backend configuration must be consistent across all stages. For POC deployments, use local state (no backend block or backend "local" with a path). For production, use backend "azurerm" with ALL required fields populated with literal values (resource_group_name, storage_account_name, container_name, key). NEVER use variable references (var.*) in backend config — Terraform does not support them. NEVER leave required backend fields empty. If using remote backend, all stages must reference the same storage account and container, differing only in key.

Rationale: Proper state management prevents cross-stage conflicts and ensures reliable deployments.
Agents: terraform-agent

Examples

  • POC: backend "local" {} — default terraform.tfstate in stage directory
  • Cross-stage ref: ../stage-1-managed-identity/terraform.tfstate
  • Production: backend "azurerm" { resource_group_name = "terraform-state-rg"; storage_account_name = "tfstate12345"; container_name = "tfstate"; key = "stage1.tfstate" }

STAN-TF-008

Complete Stage Outputs: Every stage's outputs.tf MUST export all resource names, IDs, and endpoints that ANY downstream stage or application needs. At minimum: resource group name(s), managed identity client_id/principal_id, service endpoints, workspace IDs. NEVER output sensitive values (keys, connection strings) — if local auth is disabled, omit keys entirely.

Rationale: Complete outputs enable downstream stages to reference resources without hardcoding.
Agents: terraform-agent

Examples

  • output "resource_group_name" { value = azapi_resource.resource_group.name }
  • output "managed_identity_client_id" { value = azapi_resource.managed_identity.output.properties.clientId }
  • Do NOT output primary_key when local auth is disabled


STAN-TF-009

Complete and Robust deploy.sh: Every stage MUST include a deploy.sh that is syntactically complete and runnable. It must use set -euo pipefail, include Azure login verification, run terraform init/plan/apply, export outputs to JSON, and include error handling via trap. NEVER truncate the script or leave strings unclosed.

Rationale: Complete outputs enable downstream stages to reference resources without hardcoding.
Agents: terraform-agent

Examples

  • #!/bin/bash\nset -euo pipefail\ntrap 'echo Deploy failed' ERR
  • terraform output -json > stage-1-outputs.json

STAN-TF-010

Companion Resources for Disabled Auth: When disabling local/key-based authentication on any service (local_authentication_disabled = true, shared_access_key_enabled = false), the SAME stage MUST also create: (1) a user-assigned managed identity, (2) RBAC role assignments granting that identity access, (3) outputs for the identity's client_id and principal_id. Without these companion resources, applications cannot authenticate and the deployment is broken.

Rationale: Complete outputs enable downstream stages to reference resources without hardcoding.
Agents: terraform-agent, cloud-architect

Examples

  • resource "azapi_resource" "managed_identity" { type = "Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31"; ... }
  • resource "azapi_resource" "cosmosdb_role_assignment" { type = "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15"; body = { properties = { principalId = azapi_resource.managed_identity.output.properties.principalId } } }

⚠️ **GitHub.com Fallback** ⚠️