$npx -y skills add hashicorp/agent-skills --skill azure-verified-modulesAzure Verified Modules (AVM) requirements and best practices for developing certified Azure Terraform modules. Use when creating or reviewing Azure modules that need AVM certification.
| 1 | # Azure Verified Modules (AVM) Requirements |
| 2 | |
| 3 | This guide covers the mandatory requirements for Azure Verified Modules certification. These requirements ensure consistency, quality, and maintainability across Azure Terraform modules. |
| 4 | |
| 5 | **References:** |
| 6 | - [Azure Verified Modules](https://azure.github.io/Azure-Verified-Modules/) |
| 7 | - [AVM Terraform Requirements](https://azure.github.io/Azure-Verified-Modules/specs/terraform/) |
| 8 | |
| 9 | ## Table of Contents |
| 10 | |
| 11 | - [Module Cross-Referencing](#module-cross-referencing) |
| 12 | - [Azure Provider Requirements](#azure-provider-requirements) |
| 13 | - [Code Style Standards](#code-style-standards) |
| 14 | - [Variable Requirements](#variable-requirements) |
| 15 | - [Output Requirements](#output-requirements) |
| 16 | - [Local Values Standards](#local-values-standards) |
| 17 | - [Terraform Configuration Requirements](#terraform-configuration-requirements) |
| 18 | - [Testing Requirements](#testing-requirements) |
| 19 | - [Documentation Requirements](#documentation-requirements) |
| 20 | - [Breaking Changes & Feature Management](#breaking-changes--feature-management) |
| 21 | - [Contribution Standards](#contribution-standards) |
| 22 | - [Compliance Checklist](#compliance-checklist) |
| 23 | |
| 24 | --- |
| 25 | |
| 26 | ## Module Cross-Referencing |
| 27 | |
| 28 | **Severity:** MUST | **Requirement:** TFFR1 |
| 29 | |
| 30 | When building Resource or Pattern modules, module owners **MAY** cross-reference other modules. However: |
| 31 | |
| 32 | - Modules **MUST** be referenced using HashiCorp Terraform registry reference to a pinned version |
| 33 | - Example: `source = "Azure/xxx/azurerm"` with `version = "1.2.3"` |
| 34 | - Modules **MUST NOT** use git references (e.g., `git::https://xxx.yyy/xxx.git` or `github.com/xxx/yyy`) |
| 35 | - Modules **MUST NOT** contain references to non-AVM modules |
| 36 | |
| 37 | --- |
| 38 | |
| 39 | ## Azure Provider Requirements |
| 40 | |
| 41 | **Severity:** MUST | **Requirement:** TFFR3 |
| 42 | |
| 43 | Authors **MUST** only use the following Azure providers: |
| 44 | |
| 45 | | Provider | Min Version | Max Version | |
| 46 | |----------|-------------|-------------| |
| 47 | | azapi | >= 2.0 | < 3.0 | |
| 48 | | azurerm | >= 4.0 | < 5.0 | |
| 49 | |
| 50 | **Requirements:** |
| 51 | |
| 52 | - Authors **MAY** select either Azurerm, Azapi, or both providers |
| 53 | - **MUST** use `required_providers` block to enforce provider versions |
| 54 | - **SHOULD** use pessimistic version constraint operator (`~>`) |
| 55 | |
| 56 | **Example:** |
| 57 | |
| 58 | ```hcl |
| 59 | terraform { |
| 60 | required_providers { |
| 61 | azurerm = { |
| 62 | source = "hashicorp/azurerm" |
| 63 | version = "~> 4.0" |
| 64 | } |
| 65 | azapi = { |
| 66 | source = "Azure/azapi" |
| 67 | version = "~> 2.0" |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | ``` |
| 72 | |
| 73 | --- |
| 74 | |
| 75 | ## Code Style Standards |
| 76 | |
| 77 | ### Lower snake_casing |
| 78 | |
| 79 | **Severity:** MUST | **Requirement:** TFNFR4 |
| 80 | |
| 81 | **MUST** use lower snake_casing for: |
| 82 | |
| 83 | - Locals |
| 84 | - Variables |
| 85 | - Outputs |
| 86 | - Resources (symbolic names) |
| 87 | - Modules (symbolic names) |
| 88 | |
| 89 | Example: `snake_casing_example` |
| 90 | |
| 91 | ### Resource & Data Source Ordering |
| 92 | |
| 93 | **Severity:** SHOULD | **Requirement:** TFNFR6 |
| 94 | |
| 95 | - Resources that are depended on **SHOULD** come first |
| 96 | - Resources with dependencies **SHOULD** be defined close to each other |
| 97 | |
| 98 | ### Count & for_each Usage |
| 99 | |
| 100 | **Severity:** MUST | **Requirement:** TFNFR7 |
| 101 | |
| 102 | - Use `count` for conditional resource creation |
| 103 | - **MUST** use `map(xxx)` or `set(xxx)` as resource's `for_each` collection |
| 104 | - The map's key or set's element **MUST** be static literals |
| 105 | |
| 106 | **Example:** |
| 107 | |
| 108 | ```hcl |
| 109 | resource "azurerm_subnet" "pair" { |
| 110 | for_each = var.subnet_map # map(string) |
| 111 | name = "${each.value}-pair" |
| 112 | resource_group_name = azurerm_resource_group.example.name |
| 113 | virtual_network_name = azurerm_virtual_network.example.name |
| 114 | address_prefixes = ["10.0.1.0/24"] |
| 115 | } |
| 116 | ``` |
| 117 | |
| 118 | ### Resource & Data Block Internal Ordering |
| 119 | |
| 120 | **Severity:** SHOULD | **Requirement:** TFNFR8 |
| 121 | |
| 122 | **Order within resource/data blocks:** |
| 123 | |
| 124 | 1. **Meta-arguments (top)**: |
| 125 | - `provider` |
| 126 | - `count` |
| 127 | - `for_each` |
| 128 | |
| 129 | 2. **Arguments/blocks (middle, alphabetical)**: |
| 130 | - Required arguments |
| 131 | - Optional arguments |
| 132 | - Required nested blocks |
| 133 | - Optional nested blocks |
| 134 | |
| 135 | 3. **Meta-arguments (bottom)**: |
| 136 | - `depends_on` |
| 137 | - `lifecycle` (with sub-order: `create_before_destroy`, `ignore_changes`, `prevent_destroy`) |
| 138 | |
| 139 | Separate sections with blank lines. |
| 140 | |
| 141 | ### Module Block Ordering |
| 142 | |
| 143 | **Severity:** SHOULD | **Requirement:** TFNFR9 |
| 144 | |
| 145 | **Order within module blocks:** |
| 146 | |
| 147 | 1. **Top meta-arguments**: |
| 148 | - `source` |
| 149 | - `version` |
| 150 | - `count` |
| 151 | - `for_each` |
| 152 | |
| 153 | 2. **Arguments (alphabetical)**: |
| 154 | - Required arguments |
| 155 | - Optional arguments |
| 156 | |
| 157 | 3. **Bottom meta-arguments**: |
| 158 | - `depends_on` |
| 159 | - `providers` |
| 160 | |
| 161 | ### Lifecycle ignore_changes Syntax |
| 162 | |
| 163 | **Severity:** MUST | **Requirement:** TFNFR10 |
| 164 | |
| 165 | The `ignore_changes` attribute **MUST NOT** be enclosed in double quotes. |
| 166 | |
| 167 | **Good:** |
| 168 | |
| 169 | ```hcl |
| 170 | lifecycle { |
| 171 | ignore_changes = [tags] |
| 172 | } |
| 173 | ``` |
| 174 | |
| 175 | **Bad:** |
| 176 | |
| 177 | ```hcl |
| 178 | lifecycle { |
| 179 | ignore_changes = ["tags"] |
| 180 | } |
| 181 | ``` |
| 182 | |
| 183 | ### Null Comparison for Conditional Creation |
| 184 | |
| 185 | **Severity:** SHOULD | **Requirement:** TFNFR11 |
| 186 | |
| 187 | For parameters requiring conditional r |