Skip to content

Commit 1dd4108

Browse files
fix: validate Flex Consumption zone redundancy region support (#133) (#134)
* fix: validate Flex Consumption zone redundancy region support (#133) FC1 (Flex Consumption) with zone_balancing_enabled = true is only supported in Azure regions that advertise the FCZONEREDUNDANCY capability. Previously the module forwarded the request unchanged and Azure returned an opaque 400 in unsupported regions. This adds a region-aware precondition: when FC1 is combined with zone balancing, the module queries the Microsoft.Web geoRegions API for regions advertising FCZONEREDUNDANCY and fails early with an actionable message listing the supported regions. Deployments in supported regions (e.g. East US 2) are unaffected, and the data source is not evaluated for any other SKU or when zone balancing is disabled. Closes #133 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: pre-commit governance sync Apply current AVM governance template updates (AGENTS.md spec index section and avm-terraform-module-development skill content) generated by ./avm pre-commit, keeping the repo in sync with the governance baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8d5c6d1 commit 1dd4108

14 files changed

Lines changed: 964 additions & 977 deletions

File tree

.agents/skills/avm-terraform-module-development/SKILL.md

Lines changed: 119 additions & 34 deletions
Large diffs are not rendered by default.

.agents/skills/avm-terraform-module-development/references/AzAPI.md

Lines changed: 206 additions & 69 deletions
Large diffs are not rendered by default.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# AVM Standard Interfaces
2+
3+
AVM defines a small, fixed set of **interfaces** that resource modules expose where the underlying Azure resource supports them. They standardise variable names, types, and behaviour across every module so consumers learn one shape and reuse it everywhere.
4+
5+
All interfaces are implemented in the canonical utility module **`Azure/avm-utl-interfaces/azure`** (version `~> 0.6`). Resource modules should compose that utility module rather than redefining variable shapes by hand. For the authoritative interface text, fetch each interface page via `https://azure.github.io/Azure-Verified-Modules/llms.txt`.
6+
7+
## Interfaces
8+
9+
### Diagnostic settings
10+
11+
Exposes `diagnostic_settings` (map of objects) so consumers can route resource logs and metrics to Log Analytics, Storage, Event Hub, or partner solutions. Apply on every resource that produces diagnostic logs or metrics.
12+
13+
**Modules MUST use the v2 schema.** The v2 shape models `logs` and `metrics` as sets of objects with `category` / `category_group`, `enabled`, and optional `retention_policy`, instead of the legacy flat sets of category strings. Do not specify allowed values for category names — the module must stay evergreen as resource providers add new categories.
14+
15+
Wire it through the utility module via its `diagnostic_settings_v2` input and consume the `diagnostic_settings_azapi_v2` output — the legacy `diagnostic_settings` input/output on the utility module is the old shape and **must not** be used in new code.
16+
17+
### Role assignments
18+
19+
Exposes `role_assignments` (map of objects) so consumers can attach Azure RBAC role assignments scoped to the resource. The key becomes a stable map key for plan-time stability; the object specifies `role_definition_id_or_name`, `principal_id`, `description`, etc.
20+
21+
### Locks
22+
23+
Exposes a `lock` (single object: `kind` = `None | CanNotDelete | ReadOnly`, `name` optional). Apply to top-level resources that support management locks.
24+
25+
### Managed identities
26+
27+
Exposes `managed_identities` (object with `system_assigned` bool and `user_assigned_resource_ids` set of strings). Apply on every resource that supports managed identity.
28+
29+
### Private endpoints
30+
31+
Exposes `private_endpoints` (map of objects) so consumers can deploy private endpoints into a target subnet, register them in private DNS zones, and configure custom IP addressing. Apply on every resource that supports Private Link.
32+
33+
### Customer-managed keys
34+
35+
Exposes `customer_managed_key` (single object: `key_vault_resource_id`, `key_name`, optional `key_version`, optional `user_assigned_identity` reference). Apply on every resource that supports CMK encryption.
36+
37+
### Tags
38+
39+
Exposes `tags` (map of strings). Required on every resource that supports tags. The pattern-module equivalent typically forwards a shared `tags` input to every composed module.
40+
41+
### AzAPI resource types
42+
43+
Exposes `resource_types` (object) so consumers can pin the API version used for each `azapi_resource` the module owns. The keys are **module-specific** — declare one `optional(string, "<api-version>")` field per `azapi_resource` (or equivalent) the module declares, defaulting each to the latest tested API version. Defaults **MUST** be a stable (non-preview) API version unless the resource only ships preview.
44+
45+
Parent modules **MUST** cascade the relevant subset of `resource_types` to each submodule (see TFFR6 and TFRMNFR1). Submodules **MUST** declare their own `resource_types` variable using the same pattern.
46+
47+
### AzAPI retry
48+
49+
Exposes `retry` (single object: `error_message_regex`, `interval_seconds`, `max_interval_seconds`, all optional). MUST be applied to every `azapi_resource` (and equivalent AzAPI resources) declared by the module and cascaded unchanged into every submodule. `retry` is an attribute on `azapi_resource`, so it is assigned directly.
50+
51+
### AzAPI timeouts
52+
53+
Exposes `timeouts` (single object: `create`, `read`, `update`, `delete`, all optional Go duration strings). MUST be applied to every `azapi_resource` (and equivalent AzAPI resources) declared by the module and cascaded unchanged into every submodule. `timeouts` is a **block** on `azapi_resource` (not an attribute), so a `dynamic "timeouts"` block is required to honour the variable's `null` default.
54+
55+
For the full `retry` and `timeouts` variable schemas, the resource-side wiring (including the `dynamic` block), module-level defaults guidance, and submodule cascade pattern, read [AzAPI.md](./AzAPI.md#retry-and-timeouts).
56+
57+
## Implementation pattern
58+
59+
1. Take a dependency on `Azure/avm-utl-interfaces/azure` in `terraform.tf`.
60+
2. Call the utility module in `main.tf` to translate the standard inputs into the AzAPI body shape. Note the diagnostic-settings input field is `diagnostic_settings_v2` (the legacy `diagnostic_settings` field on the utility module is the old shape):
61+
62+
```hcl
63+
module "avm_interfaces" {
64+
source = "Azure/avm-utl-interfaces/azure"
65+
version = "~> 0.6"
66+
67+
diagnostic_settings_v2 = var.diagnostic_settings
68+
diagnostic_settings_scope = azapi_resource.this.id
69+
managed_identities = var.managed_identities
70+
# …
71+
}
72+
```
73+
74+
3. Reference the utility module's outputs in the relevant `azapi_resource` blocks. For diagnostic settings iterate `module.avm_interfaces.diagnostic_settings_azapi_v2`:
75+
76+
```hcl
77+
resource "azapi_resource" "diagnostic_settings" {
78+
for_each = module.avm_interfaces.diagnostic_settings_azapi_v2
79+
80+
type = each.value.type
81+
name = each.value.name
82+
parent_id = each.value.parent_id
83+
body = each.value.body
84+
}
85+
```
86+
87+
4. Implement supporting child resources (private endpoints, diagnostic settings, role assignments, locks) as separate `azapi_resource` blocks driven by the same input maps — never collapse them into the parent `body` (TFRMNFR1).
88+
89+
## When NOT to expose an interface
90+
91+
If the underlying Azure resource does not support a capability (e.g. no private endpoints, no managed identity, no diagnostic settings), do **not** expose the corresponding variable. Adding a no-op variable creates a misleading contract.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# AVM Terraform Module Composition
2+
3+
Concise summary of how an AVM Terraform Resource or Pattern Module fits together. For the authoritative text, fetch the relevant spec via `https://azure.github.io/Azure-Verified-Modules/llms.txt`.
4+
5+
## File layout
6+
7+
The root of every AVM Terraform module looks like this (TFNFR39):
8+
9+
```
10+
.
11+
├── _header.md # Source of README intro; edit this, NOT README.md
12+
├── _footer.md # Source of README footer
13+
├── README.md # Auto-generated by terraform-docs; do not edit
14+
├── main.tf # Primary resource(s)
15+
├── main.<feature>.tf # Feature-grouped sub-files (optional)
16+
├── main.telemetry.tf # modtm telemetry block (managed by mapotf)
17+
├── variables.tf # All inputs
18+
├── outputs.tf # All outputs
19+
├── locals.tf # Local values (if used)
20+
├── terraform.tf # required_version + required_providers
21+
├── examples/ # One folder per example, each runnable on its own
22+
│ └── <example_name>/
23+
│ ├── main.tf
24+
│ ├── variables.tf
25+
│ ├── outputs.tf
26+
│ └── README.md
27+
├── modules/ # Internal sub-modules called only by this module
28+
│ └── <sub_module>/
29+
└── tests/
30+
├── unit/ # mock_provider tests
31+
└── integration/ # real-deploy tests
32+
```
33+
34+
Rules of thumb:
35+
36+
- One `azapi_resource` block per logical resource — never collapse parent + child into a single `body`.
37+
- Group related resources into `main.<feature>.tf` (e.g. `main.privateendpoint.tf`, `main.diagnostics.tf`) once `main.tf` exceeds ~200 lines.
38+
- Sub-modules in `modules/` are private to the module and must follow the same file layout in miniature.
39+
- Examples are independent root modules; each must `terraform init && terraform plan && terraform apply && terraform plan` cleanly (idempotency check).
40+
41+
## Resource modules (`res`)
42+
43+
A resource module deploys one **primary** Azure resource (RMFR1). Child resources that have no useful lifecycle outside the primary (e.g. blob services for a storage account, network security rules for an NSG) belong in the same module. Anything you would reasonably manage independently belongs in its own resource module and is consumed via `module "..."`.
44+
45+
Key obligations:
46+
47+
- Implement the resource with the AzAPI provider (TFFR3).
48+
- Expose standard interfaces where applicable (diagnostic settings, locks, role assignments, private endpoints, managed identities, customer-managed keys, tags) — see [interfaces.md](interfaces.md).
49+
- Outputs include the required AVM outputs (RMFR7). For Terraform-specific additional outputs, prefer discrete computed attributes over whole resource object outputs (TFFR2).
50+
- Provide at least a `default` example demonstrating the simplest valid usage.
51+
52+
## Pattern modules (`ptn`)
53+
54+
A pattern module composes multiple AVM resource modules (and optionally other pattern modules) into an opinionated workload — for example, "AKS production baseline" or "regulated landing zone hub". It generally does **not** declare `azapi_resource` blocks directly; it wires resource modules together.
55+
56+
Key obligations:
57+
58+
- Re-use existing AVM resource modules instead of re-implementing resources.
59+
- Expose only the inputs the workload needs; opinionated defaults are encouraged.
60+
- Telemetry, naming, and tagging interfaces still apply to the pattern module itself.
61+
- Examples demonstrate the workload end-to-end, not just the wiring.
62+
63+
## Telemetry
64+
65+
`main.telemetry.tf` declares a `modtm_telemetry` resource so AVM can measure module usage (SFR3 / SFR4). It is generated/maintained by the mapotf configuration in this governance repo — do not hand-edit it. The user can opt out with `enable_telemetry = false`.
66+
67+
## Required providers & versions
68+
69+
`terraform.tf` pins `required_version` and `required_providers` (TFNFR26 / TFNFR27 / TFNFR39). At minimum:
70+
71+
- `azapi` within the TFFR3-permitted major range (`>= 2.0, < 3.0`)
72+
- `modtm` if telemetry is implemented
73+
- `random` when the module directly uses it
74+
75+
Provider version bounds are enforced by the mapotf `required_provider_versions.mptf.hcl` config in this governance repo.

.agents/skills/avm-terraform-module-development/references/terraform-test.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Submodules under `./modules/` follow the same pattern — each can have its own
3434

3535
## Unit Test Template
3636

37-
Every AVM module uses the AzAPI provider. Unit tests MUST mock **all** providers declared in `terraform.tf` `required_providers`. AVM modules always include `azapi`, `modtm`, and `random`.
37+
Unit tests MUST mock **all** providers declared in `terraform.tf` `required_providers` for the module under test.
3838

3939
```hcl
4040
# tests/unit/unit.tftest.hcl
@@ -387,10 +387,10 @@ For debugging, the `terraform test -no-cleanup` flag prevents automatic destruct
387387

388388
## Best Practices
389389

390-
1. **Always mock all providers** in unit tests — check `terraform.tf` `required_providers` for the full list. AVM modules always have at least `azapi`, `modtm`, and `random`.
390+
1. **Always mock all providers** in unit tests — check `terraform.tf` `required_providers` for the full list for the module under test.
391391
2. **Use `command = apply`** for unit tests (not `plan`) — mocked providers make apply safe and allow testing resource creation.
392392
3. **Write clear error messages** — assertion messages should describe the expected behavior, not restate the condition.
393-
4. **Set `location`** in the `variables` block — it is a required variable in all AVM modules with no default.
393+
4. **Set all required inputs** in the `variables` block — use the module's `variables.tf` to determine which variables are required and have no default.
394394
5. **Test validation rules** — use `expect_failures` to verify that invalid inputs are rejected.
395395
6. **Test conditional logic** — verify that optional features create resources when enabled and skip them when disabled.
396396
7. **Keep tests focused** — each run block should test one scenario or behavior.

0 commit comments

Comments
 (0)