# Terraform Pattern Module Specifications Terraform Pattern Module Specifications for the Azure Verified Modules (AVM) program Contribution / Support The content below is listed based on the following tags  Category-Contribution/SupportClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SNFR8 Module Owner(s) GitHub MUST Owner Initial 2 SNFR20 GitHub Teams Only MUST Owner Initial 3 SNFR9 AVM & PG Teams GitHub Repo Permissions MUST Owner Initial 4 SNFR10 MIT Licensing MUST Owner Initial 5 SNFR11 Issues Response Times MUST OwnerContributor BAU 6 SNFR12 Versions Supported MUST Owner BAU 7 SNFR23 GitHub Repo Labels MUST Owner BAU 8 TFNFR3 GitHub Repo Branch Protection MUST OwnerContributor BAU βž• See Specifications for this category Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR8 - Category: Contribution/Support - Module Owner(s) GitHub A module MUST have an owner that is defined and managed by a GitHub Team in the Azure GitHub organization. Today this is only Microsoft FTEs, but everyone is welcome to contribute. The module just MUST be owned by a Microsoft FTE (today) so we can enforce and provide the long-term support required by this initiative. Note The names for the GitHub teams for each approved module are already defined in the respective Module Indexes. These teams MUST be created (and used) for each module. Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR20 - Category: Contribution/Support - GitHub Teams Only All GitHub repositories that AVM module are published from and hosted within MUST only assign GitHub repository permissions to GitHub teams only. Each module MUST have separate GitHub teams assigned for module owners AND module contributors respectively. These GitHub teams MUST be created in the Azure organization in GitHub. There MUST NOT be any GitHub repository permissions assigned to individual users. Note The names for the GitHub teams for each approved module are already defined in the respective Module Indexes. These teams MUST be created (and used) for each module. Bicep Resource Modules Bicep Pattern Modules Terraform Resource Modules Terraform Pattern Modules The @Azure prefix in the last column of the tables linked above represents the “Azure” GitHub organization all AVM-related repositories exist in. DO NOT include this segment in the team’s name! Important Non-FTE / external contributors (subject matter experts that aren’t Microsoft employees) can’t be members of the teams described in this chapter, hence, they won’t gain any extra permissions on AVM repositories, therefore, they need to work in forks. Naming Convention The naming convention for the GitHub teams MUST follow the below pattern: <hyphenated module name>-module-owners-<bicep/tf> - to be assigned as the GitHub repository’s Module Owners team <hyphenated module name>-module-contributors-<bicep/tf> - to be assigned as the GitHub repository’s Module Contributors team Note The naming convention for Bicep modules is slightly different than the naming convention for their respective GitHub teams. Segments: <hyphenated module name> == the AVM Module’s name, with each segment separated by dashes, i.e., avm-res-<resource provider>-<ARM resource type> See RMNFR1 for AVM Resource Module Naming See PMNFR1 for AVM Pattern Module Naming module-owners or module-contributors == the role the GitHub Team is assigned to <bicep/tf> == the language the module is written in Examples: avm-res-compute-virtualmachine-module-owners-bicep avm-res-compute-virtualmachine-module-contributors-tf Add Team Members All officially documented module owner(s) MUST be added to the -module-owners- team. The -module-owners- team MUST NOT have any other members. Any additional module contributors whom the module owner(s) agreed to work with MUST be added to the -module-contributors- team. Unless explicitly requested and agreed, members of the AVM core team or any PG teams MUST NOT be added to the -module-owners- or -module-contributors- teams as permissions for them are granted through the teams described in SNFR9. Grant Permissions - Bicep Team memberships Note In case of Bicep modules, permissions to the BRM repository (the repo of the Bicep Registry) are granted via assigning the -module-owners- and -module-contributors- teams to parent teams that already have the required level access configured. While it is the module owner’s responsibility to initiate the addition of their teams to the respective parents, only the AVM core team can approve this parent-child relationship. Module owners MUST create their -module-owners- and -module-contributors- teams and as part of the provisioning process, they MUST request the addition of these teams to their respective parent teams (see the table below for details). GitHub Team Name Description Permissions Permissions granted through Where to work? <hyphenated module name>-module-owners-bicep AVM Bicep Module Owners - <module name> Write Assignment to the avm-technical-reviewers-bicep parent team. Need to work in a fork. <hyphenated module name>-module-contributors-bicep AVM Bicep Module Contributors - <module name> Triage avm-module-contributors-bicep parent team. Need to work in a fork. Examples - GitHub teams required for the Bicep resource module of Azure Virtual Network (avm/res/network/virtual-network): avm-res-network-virtualnetwork-module-owners-bicep –> assign to the avm-technical-reviewers-bicep parent team. avm-res-network-virtualnetwork-module-contributors-bicep –> assign to the avm-module-contributors-bicep parent team. Tip Direct link to create a new GitHub team and assign it to its parent: Create new team Fill in the values as follows: Team name: Following the naming convention described above, use the value defined in the module indexes. Description: Follow the guidance above (see the Description column in the table above). Parent team: Follow the guidance above (see the Permissions granted through column in the table above). Team visibility: Visible Team notifications: Enabled CODEOWNERS file As part of the “initial Pull Request” (that publishes the first version of the module), module owners MUST add an entry to the CODEOWNERS file in the BRM repository (here). Note Through this approach, the AVM core team will grant review permission to module owners as part of the standard PR review process. Every CODEOWNERS entry (line) MUST include the following segments separated by a single whitespace character: Path of the module, relative to the repo’s root, e.g.: /avm/res/network/virtual-network/ The -module-owners-team, with the @Azure/ prefix, e.g., @Azure/avm-res-network-virtualnetwork-module-owners-bicep The GitHub team of the AVM Bicep reviewers, with the @Azure/ prefix, i.e., @Azure/avm-module-reviewers-bicep Example - CODEOWNERS entry for the Bicep resource module of Azure Virtual Network (avm/res/network/virtual-network): /avm/res/network/virtual-network/ @Azure/avm-res-network-virtualnetwork-module-owners-bicep @Azure/avm-module-reviewers-bicep Grant Permissions - Terraform Module owners MUST assign the -module-owners-and -module-contributors- teams the necessary permissions on their Terraform module repository per the guidance below. GitHub Team Name Description Permissions Permissions granted through Where to work? <module name>-module-owners-tf AVM Terraform Module Owners - <module name> Admin Direct assignment to repo Module owner can decide whether they want to work in a branch local to the repo or in a fork. <module name>-module-contributors-tf AVM Terraform Module Contributors - <module name> Write Direct assignment to repo Need to work in a fork. Tip Direct link to create a new GitHub team: Create new team Fill in the values as follows: Team name: Following the naming convention described above, use the value defined in the module indexes. Description: Follow the guidance above (see the Description column in the table above). Parent team: Do not assign the team to any parent team. Team visibility: Visible Team notifications: Enabled Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR9 - Category: Contribution/Support - AVM & PG Teams GitHub Repo Permissions A module owner MUST make the following GitHub teams in the Azure GitHub organization admins on the GitHub repo of the module in question: Bicep @Azure/avm-core-team-technical-bicep = AVM Core Team @Azure/bicep-admins = Bicep PG team Note These required GitHub teams are already associated to the BRM repository and have the required permissions. Terraform @Azure/avm-core-team-technical-terraform = AVM Core Team @Azure/terraform-avm = Terraform PG Important Module owners MUST assign these GitHub teams as admins on the GitHub repo of the module in question. For detailed steps, please follow this guidance. Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR10 - Category: Contribution/Support - MIT Licensing A module MUST be published with the MIT License in the Azure GitHub organization. Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR11 - Category: Contribution/Support - Issues Response Times A module owner MUST respond to logged issues as defined in the support statement. See Module Support for more information. Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR12 - Category: Contribution/Support - Versions Supported Only the latest released version of a module MUST be supported. For example, if an AVM Resource Module is used in an AVM Pattern Module that was working but now is not. The first step by the AVM Pattern Module owner should be to upgrade to the latest version of the AVM Resource Module test and then if not fixed, troubleshoot and fix forward from the that latest version of the AVM Resource Module onwards. This avoids AVM Module owners from having to maintain multiple major release versions. Category-Contribution/Support Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin... ID: SNFR23 - Category: Contribution/Support - GitHub Repo Labels GitHub repositories where modules are held MUST use the below labels and SHOULD not use any additional labels: βž• AVM Standard GitHub Labels These labels are available in a CSV file from here Name Description HEX AZD πŸ§‘β€πŸ’» These modules are requested/used by the AZD team. E0BFFA Needs: Attention πŸ‘‹ Reply has been added to issue, maintainer to review E99695 Needs: Immediate Attention ‼️ Immediate attention of module owner / AVM team is needed FF0000 Needs: Author Feedback πŸ‘‚ Awaiting feedback from the issue/PR author F18A07 Needs: External Changes βš’οΈ When an issue/PR requires changes that are outside of the control of the module. e.g. to an RP. DE389D Needs: More Evidence βš– We are looking for more evidence to make a decision on this F64872 Needs: Triage πŸ” Maintainers need to triage still FBCA04 Needs: Module Owner πŸ“£ In the AVM repository: this module needs an owner to develop or maintain it. In the BRM repository: the module owner needs to review a PR. FF0019 Needs: Module Contributor πŸ“£ This module needs secondary owner(s) or contributor(s) to develop or maintain it C95474 Needs: Core Team πŸ§žβ€β™‚οΈ This item needs the AVM Core Team to review it DB4503 Status: Awaiting Release To Be Cut βœ‚οΈ This is fixed in the main branch but not in the latest release, will be fixed with next release cut 800080 Status: Do Not Merge β›” Do not merge PRs with this label attached as they are not ready or aligned to future direction etc. 8B4513 Status: External Contribution 🌍 This is being worked on by someone outside of the AVM module owners/contributors or AVM core team D8FA2C Status: Fixed βœ… Auto label applied when issue fixed by merged PR 90EE90 Status: Help Wanted πŸ†˜ Extra attention is needed FF4500 Status: In Triage πŸ” Picked up for triaging by an AVM core team member D4AF37 Status: In PR πŸ‘‰ This is when an issue is due to be fixed in an open PR EDEDED Status: Invalid ❌ This doesn't seem right E4E669 Status: Long Term ⏳ We will do it, but will take a longer amount of time due to complexity/priorities B60205 Status: No Recent Activity πŸ’€ When an issue/PR has not been modified for X amount of days 808080 Status: Won't Fix πŸ’” This will not be worked on FFFFFF Status: Owners Identified 🀘 This module has its owners identified FBEF2A Status: Module Available 🟒 The module is published C8E6C9 Status: Module Deprecated πŸ”΄ This is a request to deprecate a module 000000 Status: Module Orphaned 🟑 The module has no owner and is therefore orphaned at this time F4A460 Status: Ready For Repository Creation πŸ“ This module is approved and the owner is ready for the repository to be created (Terraform) 136A41 Status: Repository Created πŸ“„ This module has had it's repository created and configured ready for owner contribution (Terraform) 27AB03 Status: Response Overdue 🚩 When an issue/PR has not been responded to for X amount of days 850000 Status: Looking For Assistance πŸ¦† This item is looking for anyone to help develop the code and submit a PR for resolution 03FCC2 Type: Bug πŸ› Something isn't working D73A4A Type: CI πŸš€ This issue is related to the AVM CI 74CFB0 Type: Documentation πŸ“„ Improvements or additions to documentation 0075CA Type: Duplicate 🀲 This issue or pull request already exists CFD3D7 Type: Feature Request βž• New feature or request A2EEEF Type: Hygiene 🧹 things related to testing, issue triage etc. 17016A Type: New Module Proposal πŸ’‘ A new module for AVM is being proposed ADD8E6 Type: Question/Feedback πŸ™‹β€β™€οΈ Further information is requested or just some feedback CB6BA2 Type: Security Bug πŸ”’ This is a security bug FFFF00 Type: AVM πŸ…°οΈ ✌️ β“œοΈ This is an AVM related issue F0FFFF Language: Terraform 🌐 This is related to the Terraform IaC language 7740B6 Language: Bicep πŸ’ͺ This is related to the Bicep IaC language 1D73B3 Class: Resource Module πŸ“¦ This is a resource module D3D3D3 Class: Pattern Module πŸ“¦ This is a pattern module A9A9A9 Class: Utility Module πŸ“¦ This is a utility module CAD1DE Class: Child Module πŸ“¦ This is a child module 5E5186 To help apply these to a module GitHub repository you can use the below PowerShell script: βž• Set-AvmGitHubLabels.ps1 For most scenario this is the command you’ll need to call the below PowerShell script with, replacing the value for RepositoryName: ​ Invoke Set-AvmGitHubLabels.ps1 Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -CreateCsvLabelExports $false -NoUserPrompts $true ​ docker ```shell # Linux / MacOs # For Windows replace $PWD with your the local path or your repository # docker run -it -v $PWD:/repo -w /repo mcr.microsoft.com/powershell pwsh -Command ' #Invoke-WebRequest -Uri "https://azure.github.io/Azure-Verified-Modules/scripts/Set-AvmGitHubLabels.ps1" -OutFile "Set-AvmGitHubLabels.ps1" $gh_version = "2.44.1" Invoke-WebRequest -Uri "https://github.com/cli/cli/releases/download/v2.44.1/gh_2.44.1_linux_amd64.tar.gz" -OutFile "gh_$($gh_version)_linux_amd64.tar.gz" apt-get update && apt-get install -y git tar -xzf "gh_$($gh_version)_linux_amd64.tar.gz" ls -lsa mv "gh_$($gh_version)_linux_amd64/bin/gh" /usr/local/bin/ rm "gh_$($gh_version)_linux_amd64.tar.gz" && rm -rf "gh_$($gh_version)_linux_amd64" gh --version ls -lsa gh auth login $OrgProject = "Azure/terraform-azurerm-avm-res-kusto-cluster" gh auth status ./Set-AvmGitHubLabels.ps1 -RepositoryName $OrgProject -CreateCsvLabelExports $false -NoUserPrompts $true ' ``` By default this script will only update and append labels on the repository specified. However, this can be changed by setting the parameter -UpdateAndAddLabelsOnly to $false, which will remove all the labels from the repository first and then apply the AVM labels from the CSV only. Make sure you elevate your privilege to admin level or the labels will not be applied to your repository. Go to repos.opensource.microsoft.com/orgs/Azure/repos/ to request admin access before running the script. Full Script: These Set-AvmGitHubLabels.ps1 can be downloaded from here. ​ Set-AvmGitHubLabels.ps1 [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "Coloured output required in this script")] <# .SYNOPSIS This script can be used to create the Azure Verified Modules (AVM) standard GitHub labels to a GitHub repository. .DESCRIPTION This script can be used to create the Azure Verified Modules (AVM) standard GitHub labels to a GitHub repository. By default, the script will remove all pre-existing labels and apply the AVM labels. However, this can be changed by using the -RemoveExistingLabels parameter and setting it to $false. The tool will also output the labels that exist in the repository before and after the script has run to a CSV file in the current directory, or a directory specified by the -OutputDirectory parameter. The AVM labels to be created are documented here: TBC .NOTES Please ensure you have specified the GitHub repositry correctly. The script will prompt you to confirm the repository name before proceeding. .COMPONENT You must have the GitHub CLI installed and be authenticated to a GitHub account with access to the repository you are applying the labels to before running this script. .LINK TBC .Parameter RepositoryName The name of the GitHub repository to apply the labels to. .Parameter RemoveExistingLabels If set to $true, the default value, the script will remove all pre-existing labels from the repository specified in -RepositoryName before applying the AVM labels. If set to $false, the script will not remove any pre-existing labels. .Parameter UpdateAndAddLabelsOnly If set to $true, the default value, the script will only update and add labels to the repository specified in -RepositoryName. If set to $false, the script will remove all pre-existing labels from the repository specified in -RepositoryName before applying the AVM labels. .Parameter OutputDirectory The directory to output the pre-existing and post-existing labels to in a CSV file. The default value is the current directory. .Parameter CreateCsvLabelExports If set to $true, the default value, the script will output the pre-existing and post-existing labels to a CSV file in the current directory, or a directory specified by the -OutputDirectory parameter. If set to $false, the script will not output the pre-existing and post-existing labels to a CSV file. .Parameter GitHubCliLimit The maximum number of labels to return from the GitHub CLI. The default value is 999. .Parameter LabelsToApplyCsvUri The URI to the CSV file containing the labels to apply to the GitHub repository. The default value is https://raw.githubusercontent.com/jtracey93/label-source/main/avm-github-labels.csv. .Parameter NoUserPrompts If set to $true, the default value, the script will not prompt the user to confirm they want to remove all pre-existing labels from the repository specified in -RepositoryName before applying the AVM labels. If set to $false, the script will prompt the user to confirm they want to remove all pre-existing labels from the repository specified in -RepositoryName before applying the AVM labels. This is useful for running the script in automation workflows .EXAMPLE Create the AVM labels in the repository Org/MyGitHubRepo and remove all pre-existing labels. Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" .EXAMPLE Create the AVM labels in the repository Org/MyGitHubRepo and do not remove any pre-existing labels, just overwrite any labels that have the same name. Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -RemoveExistingLabels $false .EXAMPLE Create the AVM labels in the repository Org/MyGitHubRepo and output the pre-existing and post-existing labels to the directory C:\GitHubLabels. Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -OutputDirectory "C:\GitHubLabels" .EXAMPLE Create the AVM labels in the repository Org/MyGitHubRepo and output the pre-existing and post-existing labels to the directory C:\GitHubLabels and do not remove any pre-existing labels, just overwrite any labels that have the same name. Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -OutputDirectory "C:\GitHubLabels" -RemoveExistingLabels $false .EXAMPLE Create the AVM labels in the repository Org/MyGitHubRepo and do not create the pre-existing and post-existing labels CSV files and do not remove any pre-existing labels, just overwrite any labels that have the same name. Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -RemoveExistingLabels $false -CreateCsvLabelExports $false .EXAMPLE Create the AVM labels in the repository Org/MyGitHubRepo and do not create the pre-existing and post-existing labels CSV files and do not remove any pre-existing labels, just overwrite any labels that have the same name. Finally, use a custom CSV file hosted on the internet to create the labels from. Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -OutputDirectory "C:\GitHubLabels" -RemoveExistingLabels $false -CreateCsvLabelExports $false -LabelsToApplyCsvUri "https://example.com/csv/avm-github-labels.csv" #> #Requires -PSEdition Core [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$RepositoryName, [Parameter(Mandatory = $false)] [bool]$RemoveExistingLabels = $true, [Parameter(Mandatory = $false)] [bool]$UpdateAndAddLabelsOnly = $true, [Parameter(Mandatory = $false)] [bool]$CreateCsvLabelExports = $true, [Parameter(Mandatory = $false)] [string]$OutputDirectory = (Get-Location), [Parameter(Mandatory = $false)] [int]$GitHubCliLimit = 999, [Parameter(Mandatory = $false)] [string]$LabelsToApplyCsvUri = "https://azure.github.io/Azure-Verified-Modules/governance/avm-standard-github-labels.csv", [Parameter(Mandatory = $false)] [bool]$NoUserPrompts = $false ) # Check if the GitHub CLI is installed $GitHubCliInstalled = Get-Command gh -ErrorAction SilentlyContinue if ($null -eq $GitHubCliInstalled) { throw "The GitHub CLI is not installed. Please install the GitHub CLI and try again." } Write-Host "The GitHub CLI is installed..." -ForegroundColor Green # Check if GitHub CLI is authenticated $GitHubCliAuthenticated = gh auth status if ($LASTEXITCODE -ne 0) { Write-Host $GitHubCliAuthenticated -ForegroundColor Red throw "Not authenticated to GitHub. Please authenticate to GitHub using the GitHub CLI, `gh auth login`, and try again." } Write-Host "Authenticated to GitHub..." -ForegroundColor Green # Check if GitHub repository name is valid $GitHubRepositoryNameValid = $RepositoryName -match "^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$" if ($false -eq $GitHubRepositoryNameValid) { throw "The GitHub repository name $RepositoryName is not valid. Please check the repository name and try again. The format must be <OrgName>/<RepoName>" } # List GitHub repository provided and check it exists $GitHubRepository = gh repo view $RepositoryName if ($LASTEXITCODE -ne 0) { Write-Host $GitHubRepository -ForegroundColor Red throw "The GitHub repository $RepositoryName does not exist. Please check the repository name and try again." } Write-Host "The GitHub repository $RepositoryName exists..." -ForegroundColor Green # PRE - Get the current GitHub repository labels and export to a CSV file in the current directory or where -OutputDirectory specifies if set to a valid directory path and the directory exists or can be created if it does not exist already if ($RemoveExistingLabels -or $UpdateAndAddLabelsOnly) { Write-Host "Getting the current GitHub repository (pre) labels for $RepositoryName..." -ForegroundColor Yellow $GitHubRepositoryLabels = gh label list -R $RepositoryName -L $GitHubCliLimit --json name,description,color if ($null -ne $GitHubRepositoryLabels -and $CreateCsvLabelExports -eq $true) { $csvFileNamePathPre = "$OutputDirectory\$($RepositoryName.Replace('/', '_'))-Labels-Pre-$(Get-Date -Format FileDateTime).csv" Write-Host "Exporting the current GitHub repository (pre) labels for $RepositoryName to $csvFileNamePathPre" -ForegroundColor Yellow $GitHubRepositoryLabels | ConvertFrom-Json | Export-Csv -Path $csvFileNamePathPre -NoTypeInformation } } # Remove all pre-existing labels if -RemoveExistingLabels is set to $true and user confirms they want to remove all pre-existing labels if ($null -ne $GitHubRepositoryLabels) { $GitHubRepositoryLabelsJson = $GitHubRepositoryLabels | ConvertFrom-Json if ($RemoveExistingLabels -eq $true -and $NoUserPrompts -eq $false -and $UpdateAndAddLabelsOnly -eq $false) { $RemoveExistingLabelsConfirmation = Read-Host "Are you sure you want to remove all $($GitHubRepositoryLabelsJson.Count) pre-existing labels from $($RepositoryName)? (Y/N)" if ($RemoveExistingLabelsConfirmation -eq "Y") { Write-Host "Removing all pre-existing labels from $RepositoryName..." -ForegroundColor Yellow $GitHubRepositoryLabels | ConvertFrom-Json | ForEach-Object { Write-Host "Removing label $($_.name) from $RepositoryName..." -ForegroundColor DarkRed gh label delete -R $RepositoryName $_.name --yes } } } if ($RemoveExistingLabels -eq $true -and $NoUserPrompts -eq $true -and $UpdateAndAddLabelsOnly -eq $false) { Write-Host "Removing all pre-existing labels from $RepositoryName..." -ForegroundColor Yellow $GitHubRepositoryLabels | ConvertFrom-Json | ForEach-Object { Write-Host "Removing label $($_.name) from $RepositoryName..." -ForegroundColor DarkRed gh label delete -R $RepositoryName $_.name --yes } } } if ($null -eq $GitHubRepositoryLabels) { Write-Host "No pre-existing labels to remove or not selected to be removed from $RepositoryName..." -ForegroundColor Magenta } # Check LabelsToApplyCsvUri is valid and contains a CSV content Write-Host "Checking $LabelsToApplyCsvUri is valid..." -ForegroundColor Yellow $LabelsToApplyCsvUriValid = $LabelsToApplyCsvUri -match "^https?://" if ($false -eq $LabelsToApplyCsvUriValid) { throw "The LabelsToApplyCsvUri $LabelsToApplyCsvUri is not valid. Please check the URI and try again. The format must be a valid URI." } Write-Host "The LabelsToApplyCsvUri $LabelsToApplyCsvUri is valid..." -ForegroundColor Green # Create AVM lables from the AVM labels CSV file stored on the web using the convertfrom-csv cmdlet $avmLabelsCsv = Invoke-WebRequest -Uri $LabelsToApplyCsvUri | ConvertFrom-Csv # Check if the AVM labels CSV file contains the following columns: Name, Description, HEX $avmLabelsCsvColumns = $avmLabelsCsv | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name $avmLabelsCsvColumnsValid = $avmLabelsCsvColumns -contains "Name" -and $avmLabelsCsvColumns -contains "Description" -and $avmLabelsCsvColumns -contains "HEX" if ($false -eq $avmLabelsCsvColumnsValid) { throw "The labels CSV file does not contain the required columns: Name, Description, HEX. Please check the CSV file and try again. It contains the following columns: $avmLabelsCsvColumns" } Write-Host "The labels CSV file contains the required columns: Name, Description, HEX" -ForegroundColor Green # Create the AVM labels in the GitHub repository Write-Host "Creating/Updating the $($avmLabelsCsv.Count) AVM labels in $RepositoryName..." -ForegroundColor Yellow $avmLabelsCsv | ForEach-Object { if ($GitHubRepositoryLabelsJson.name -contains $_.name) { Write-Host "The label $($_.name) already exists in $RepositoryName. Updating the label to ensure description and color are consitent..." -ForegroundColor Magenta gh label create -R $RepositoryName "$($_.name)" -c $_.HEX -d $($_.Description) --force } else { Write-Host "The label $($_.name) does not exist in $RepositoryName. Creating label $($_.name) in $RepositoryName..." -ForegroundColor Cyan gh label create -R $RepositoryName "$($_.Name)" -c $_.HEX -d $($_.Description) --force } } # POST - Get the current GitHub repository labels and export to a CSV file in the current directory or where -OutputDirectory specifies if set to a valid directory path and the directory exists or can be created if it does not exist already if ($CreateCsvLabelExports -eq $true) { Write-Host "Getting the current GitHub repository (post) labels for $RepositoryName..." -ForegroundColor Yellow $GitHubRepositoryLabels = gh label list -R $RepositoryName -L $GitHubCliLimit --json name,description,color if ($null -ne $GitHubRepositoryLabels) { $csvFileNamePathPre = "$OutputDirectory\$($RepositoryName.Replace('/', '_'))-Labels-Post-$(Get-Date -Format FileDateTime).csv" Write-Host "Exporting the current GitHub repository (post) labels for $RepositoryName to $csvFileNamePathPre" -ForegroundColor Yellow $GitHubRepositoryLabels | ConvertFrom-Json | Export-Csv -Path $csvFileNamePathPre -NoTypeInformation } } # If -RemoveExistingLabels is set to $true and user confirms they want to remove all pre-existing labels check that only the avm labels exist in the repository if ($RemoveExistingLabels -eq $true -and ($RemoveExistingLabelsConfirmation -eq "Y" -or $NoUserPrompts -eq $true) -and $UpdateAndAddLabelsOnly -eq $false) { Write-Host "Checking that only the AVM labels exist in $RepositoryName..." -ForegroundColor Yellow $GitHubRepositoryLabels = gh label list -R $RepositoryName -L $GitHubCliLimit --json name,description,color $GitHubRepositoryLabels | ConvertFrom-Json | ForEach-Object { if ($avmLabelsCsv.Name -notcontains $_.name) { throw "The label $($_.name) exists in $RepositoryName but is not in the CSV file." } } Write-Host "Only the CSV labels exist in $RepositoryName..." -ForegroundColor Green } Write-Host "The CSV labels have been created/updated in $RepositoryName..." -ForegroundColor Green Category-Contribution/Support Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR3 - Category: Contribution/Support - GitHub Repo Branch Protection Module owners MUST set a branch protection policy on their GitHub Repositories for AVM modules against their default branch, typically main, to do the following: Requires a Pull Request before merging Require approval of the most recent reviewable push Dismiss stale pull request approvals when new commits are pushed Require linear history Prevents force pushes Not allow deletions Require CODEOWNERS review Do not allow bypassing the above settings Above settings MUST also be enforced to administrators Tip If you use the template repository as mentioned in the contribution guide, the above will automatically be set. Telemetry The content below is listed based on the following tags  Category-TelemetryClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SFR3 Deployment/Usage Telemetry MUST Owner Initial 2 SFR4 Telemetry Enablement Flexibility MUST Owner Initial βž• See Specifications for this category Category-Telemetry Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-Functional Validation-TBD See origin...ID: SFR3 - Category: Telemetry - Deployment/Usage Telemetry Important We will maintain a set of CSV files in the AVM Central Repo (Azure/Azure-Verified-Modules) with the required TelemetryId prefixes to enable checks to utilize this list to ensure the correct IDs are used. To see the formatted content of these CSV files with additional information, please visit the AVM Module Indexes page. These will also be provided as a comment on the module proposal, once accepted, from the AVM core team. Modules MUST provide the capability to collect deployment/usage telemetry as detailed in Telemetry further. To highlight that AVM modules use telemetry, an information notice MUST be included in the footer of each module’s README.md file with the below content. (See more details on this requirement, here.) Telemetry Information Notice Note The following information notice is automatically added at the bottom of the README.md file of the module when Bicep: Using the utilities/tools/Set-AVMModule.ps1 utility Terraform: Executing the make docs command with the note and header ## Data Collection being placed in the module’s _footer.md beforehand ### Data Collection The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at <https://go.microsoft.com/fwlink/?LinkID=824704>. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. Bicep Important The value you need to use for your module is defined in the related module index. You can look it up on the index pages for Resource Modules, Pattern Modules and Utility Modules. The ARM deployment name used for the telemetry MUST follow the pattern and MUST be no longer than 64 characters in length: 46d3xbcp.<res/ptn>.<(short) module name>.<version>.<uniqueness> <res/ptn> == AVM Resource or Pattern Module <(short) module name> == The AVM Module’s, possibly shortened, name including the resource provider and the resource type, without; The prefixes: avm-res- The prefixes: avm-ptn- <version> == The AVM Module’s MAJOR.MINOR version (only) with . (periods) replaced with - (hyphens), to allow simpler splitting of the ARM deployment name <uniqueness> == This section of the ARM deployment name is to be used to ensure uniqueness of the deployment name. This is to cater for the following scenarios: The module is deployed multiple times to the same: Location/Region Scope (Tenant, Management Group,Subscription, Resource Group) Note Due to the 64-character length limit of Azure deployment names, the <(short) module name> segment has a length limit of 36 characters, so if the module name is longer than that, it MUST be truncated to 36 characters. If any of the semantic version’s segments are longer than 1 character, it further restricts the number of characters that can be used for naming the module. An example deployment name for the AVM Virtual Machine Resource Module would be: 46d3xbcp.res.compute-virtualmachine.1-2-3.eum3 An example deployment name for a shortened module name would be: 46d3xbcp.res.desktopvirtualization-appgroup.1-2-3.eum3 Tip Terraform: Terraform uses a telemetry provider, the configuration of which is the same for every module and is included in the template repo. General: See the language specific contribution guides for detailed guidance and sample code to use in AVM modules to achieve this requirement. Bicep Terraform Terraform To enable telemetry data collection for Terraform modules, the modtm telemetry provider MUST be used. This lightweight telemetry provider sends telemetry data to Azure Application Insights via a HTTP POST front end service. The modtm telemetry provider is included in all Terraform modules and is enabled by default through the main.telemetry.tf file being automatically distributed from the template repo. The modtm provider MUST be listed under the required_providers section in the module’s terraform.tf file using the following entry. This is also validated by the linter. terraform { required_providers { # .. other required providers as needed modtm = { source = "Azure/modtm" version = "~> 0.3" } } } Category-Telemetry Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-Functional Validation-TBD See origin...ID: SFR4 - Category: Telemetry - Telemetry Enablement Flexibility The telemetry enablement MUST be on/enabled by default, however this MUST be able to be disabled by a module consumer by setting the below parameter/variable value to false: Bicep: enableTelemetry Terraform: enable_telemetry Note Whenever a module references AVM modules that implement the telemetry parameter (e.g., a pattern module that uses AVM resource modules), the telemetry parameter value MUST be passed through to these modules. This is necessary to ensure a consumer can reliably enable & disable the telemetry feature for all used modules. This general specification can be modified for some use-cases, that are language specific: Bicep For cross-references in resource modules, the spec BCPFR7 also applies. Terraform Currently, no further requirements apply. Naming / Composition The content below is listed based on the following tags  Category-Naming/CompositionClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SFR1 Preview Services MUST Owner BAU 2 SFR2 WAF Aligned SHOULD Owner BAU 3 SFR5 Availability Zones MUST Owner Initial 4 SFR6 Data Redundancy MUST Owner Initial 5 SNFR25 Resource Naming MUST Owner Initial 6 PMFR1 Resource Group Creation MAY OwnerContributor BAU 7 PMNFR1 Module Naming MUST Owner Initial 8 PMNFR2 Use Resource Modules to Build a Pattern Module MUST OwnerContributor BAU 9 PMNFR3 Use other Pattern Modules to Build a Pattern Module MUST OwnerContributor BAU 10 TFFR1 Cross-Referencing Modules MUST OwnerContributor BAU 11 TFFR3 Providers - Permitted Versions MUST OwnerContributor BAU 12 TFNFR4 Lower snake_casing MUST OwnerContributor BAU βž• See Specifications for this category Category-Naming/Composition Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Owner Severity-MUST Type-Functional Validation-TBD See origin...ID: SFR1 - Category: Composition - Preview Services Modules MAY create/adopt public preview services and features at their discretion. Preview API versions MAY be used when: The resource/service/feature is GA but the only API version available for the GA resource/service/feature is a preview version For example, Diagnostic Settings (Microsoft.Insights/diagnosticSettings) the latest version of the API available with GA features, like Category Groups etc., is 2021-05-01-preview Otherwise the latest “non-preview” version of the API SHOULD be used Preview services and features, SHOULD NOT be promoted and exposed, unless they are supported by the respective PG, and it’s documented publicly. However, they MAY be exposed at the module owners discretion, but the following rules MUST be followed: The description of each of the parameters/variables used for the preview service/feature MUST start with: “THIS IS A <PARAMETER/VARIABLE> USED FOR A PREVIEW SERVICE/FEATURE, MICROSOFT MAY NOT PROVIDE SUPPORT FOR THIS, PLEASE CHECK THE PRODUCT DOCS FOR CLARIFICATION” Category-Naming/Composition Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Owner Severity-SHOULD Type-Functional Validation-TBD See origin...ID: SFR2 - Category: Composition - WAF Aligned Modules SHOULD set defaults in input parameters/variables to align to high priority/impact/severity recommendations, where appropriate and applicable, in the following frameworks and resources: Well-Architected Framework (WAF) Reliability Hub Azure Proactive Resiliency Library (APRL) Only Product Group (PG) verified Microsoft Defender for Cloud (MDFC) They SHOULD NOT align to these recommendations when it requires an external dependency/resource to be deployed and configured and then associated to the resources in the module. Alignment SHOULD prioritize best-practices and security over cost optimization, but MUST allow for these to be overridden by a module consumer easily, if desired. Tip Read the FAQ of What does AVM mean by “WAF Aligned”? for more detailed information and examples. Category-Naming/Composition Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-Functional Validation-TBD See origin...ID: SFR5 - Category: Composition - Availability Zones Modules that deploy zone-redundant resources MUST enable the spanning across as many zones as possible by default, typically all 3. Modules that deploy zonal resources MUST provide the ability to specify a zone for the resources to be deployed/pinned to. However, they MUST NOT default to a particular zone by default, e.g. 1 in an effort to make the consumer aware of the zone they are selecting to suit their architecture requirements. For both scenarios the modules MUST expose these configuration options via configurable parameters/variables. Note For information on the differences between zonal and zone-redundant services, see Availability zone service and regional support Category-Naming/Composition Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-Functional Validation-TBD See origin...ID: SFR6 - Category: Composition - Data Redundancy Modules that deploy resources or patterns that support data redundancy SHOULD enable this to the highest possible value by default, e.g. RA-GZRS. When a resource or pattern doesn’t provide the ability to specify data redundancy as a simple property, e.g. GRS etc., then the modules MUST provide the ability to enable data redundancy for the resources or pattern via parameters/variables. For example, a Storage Account module can simply set the sku.name property to Standard_RAGZRS. Whereas a SQL DB or Cosmos DB module will need to expose more properties, via parameters/variables, to allow the specification of the regions to replicate data to as per the consumers requirements. Note For information on the data redundancy options in Azure, see Cross-region replication in Azure Category-Naming/Composition Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR25 - Category: Composition - Resource Naming Module owners MUST set the default resource name prefix for child, extension, and interface resources to the associated abbreviation for the specific resource as documented in the following CAF article Abbreviation examples for Azure resources, if specified and documented. This reduces the amount of input values a module consumer MUST provide by default when using the module. For example, a Private Endpoint that is being deployed as part of a resource module, via the mandatory interfaces, MUST set the Private Endpoint’s default name to begin with the prefix of pep-. Module owners MUST also provide the ability for these default names, including the prefixes, to be overridden via a parameter/variable if the consumer wishes to. Furthermore, as per RMNFR2, Resource Modules MUST not have a default value specified for the name of the primary resource and therefore the name MUST be provided and specified by the module consumer. The name provided MAY be used by the module owner to generate the rest of the default name for child, extension, and interface resources if they wish to. For example, for the Private Endpoint mentioned above, the full default name that can be overridden by the consumer, MAY be pep-<primary-resource-name>. Tip If the resource does not have a documented abbreviation in Abbreviation examples for Azure resources, then the module owner is free to use a sensible prefix instead. Category-Naming/Composition Class-Pattern Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MAY Type-Functional Validation-TBD See origin...ID: PMFR1 - Category: Composition - Resource Group Creation A Pattern Module MAY create Resource Group(s). Category-Naming/Composition Class-Pattern Language-Bicep Language-Terraform Lifecycle-Initial Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: PMNFR1 - Category: Naming - Module Naming Pattern Modules MUST follow the below naming conventions (all lower case): Bicep Pattern Module Naming Naming convention: avm/ptn/<hyphenated grouping/category name>/<hyphenated pattern module name> Example: avm/ptn/compute/app-tier-vmss or avm/ptn/avd-lza/management-plane or avm/ptn/3-tier/web-app Segments: ptn defines this as a pattern module <hyphenated grouping/category name> is a hierarchical grouping of pattern modules by category, with each word separated by dashes, such as: project name, e.g., avd-lza, primary resource provider, e.g., compute or network, or architecture, e.g., 3-tier <hyphenated pattern module name> is a term describing the module’s function, with each word separated by dashes, e.g., app-tier-vmss = Application Tier VMSS; management-plane = Azure Virtual Desktop Landing Zone Accelerator Management Plane Terraform Pattern Module Naming Naming convention: avm-ptn-<pattern module name> (Module name for registry) terraform-<provider>-avm-ptn-<pattern module name> (GitHub repository name to meet registry naming requirements) Example: avm-ptn-apptiervmss or avm-ptn-avd-lza-managementplane Segments: <provider> is the logical abstraction of various APIs used by Terraform. In most cases, this is going to be azurerm or azuread for resource modules. ptn defines this as a pattern module <pattern module name> is a term describing the module’s function, e.g., apptiervmss = Application Tier VMSS; avd-lza-managementplane = Azure Virtual Desktop Landing Zone Accelerator Management Plane Category-Naming/Composition Class-Pattern Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: PMNFR2 - Category: Composition - Use Resource Modules to Build a Pattern Module A Pattern Module SHOULD be built from AVM Resources Modules to establish a standardized code base and improve maintainability. If a valid reason exists, a pattern module MAY contain native resources (“vanilla” code) where it’s necessary. A Pattern Module MUST NOT contain references to non-AVM modules. Valid reasons for not using a Resource Module for a resource required by a Pattern Module include but are not limited to: When using a Resource Module would result in hitting scaling limitations and/or would reduce the capabilities of the Pattern Module due to the limitations of Azure Resource Manager. Developing a Pattern Module under time constraint, without having all required Resource Modules readily available. Note In the latter case, the Pattern Module SHOULD be updated to use the Resource Module when the required Resource Module becomes available, to avoid accumulating technical debt. Ideally, all required Resource Modules SHOULD be developed first, and then leveraged by the Pattern Module. Category-Naming/Composition Class-Pattern Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: PMNFR3 - Category: Composition - Use other Pattern Modules to Build a Pattern Module A Pattern Module MAY contain and be built using other AVM Pattern Modules. A Pattern Module MUST NOT contain references to non-AVM modules. Category-Naming/Composition Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-Functional Validation-TBD See origin...ID: TFFR1 - Category: Composition - Cross-Referencing Modules Module owners MAY cross-references other modules to build either Resource or Pattern modules. However, they MUST be referenced only by a HashiCorp Terraform registry reference to a pinned version e.g., module "other-module" { source = "Azure/xxx/azurerm" version = "1.2.3" } They MUST NOT use git reference to a module. module "other-module" { source = "git::https://xxx.yyy/xxx.git" } module "other-module" { source = "github.com/xxx/yyy" } Modules MUST NOT contain references to non-AVM modules. Tip See Module Sources for more information. Category-Naming/Composition Class-Pattern Class-Resource Class-Utility Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-Functional Validation-TF/CI/Enforced See origin...ID: TFFR3 - Category: Providers - Permitted Versions Authors MUST only use the following Azure providers, and versions, in their modules: provider min version max version azapi >= 2.0 < 3.0 azurerm >= 4.0 < 5.0 Note Authors MAY select either Azurerm, Azapi, or both providers in their module. Authors MUST use the required_providers block in their module to enforce the provider versions. The following is an example. In it we use the pessimistic version constraint operator ~>. That is to say that ~> 4.0 is equivalent to >= 4.0, < 5.0. terraform { required_providers { # Include one or both providers, as needed azurerm = { source = "hashicorp/azurerm" version = "~> 4.0" } azapi = { source = "Azure/azapi" version = "~> 2.0" } } } Category-Naming/Composition Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR4 - Category: Composition - Code Styling - lower snake_casing Module owners MUST use lower snake_casing for naming the following: Locals Variables Outputs Resources (symbolic names) Modules (symbolic names) For example: snake_casing_example (every word in lowercase, with each word separated by an underscore _) Code Style The content below is listed based on the following tags  Category-CodeStyleClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 TFNFR6 Resource & Data Order SHOULD OwnerContributor BAU 2 TFNFR7 Count & for_each Use MUST OwnerContributor BAU 3 TFNFR8 Resource & Data Block Orders SHOULD OwnerContributor BAU 4 TFNFR9 Module Block Order SHOULD OwnerContributor BAU 5 TFNFR10 No Double Quotes in ignore_changes MUST OwnerContributor BAU 6 TFNFR11 Null Comparison Toggle SHOULD OwnerContributor BAU 7 TFNFR12 Dynamic for Optional Nested Objects MUST OwnerContributor BAU 8 TFNFR13 Default Values with coalesce/try SHOULD OwnerContributor BAU 9 TFNFR16 Variable Naming Rules SHOULD OwnerContributor BAU 10 TFNFR17 Variables with Descriptions SHOULD OwnerContributor BAU 11 TFNFR18 Variables with Types MUST OwnerContributor BAU 12 TFNFR19 Sensitive Data Variables SHOULD OwnerContributor BAU 13 TFNFR20 Non-Nullable Defaults for collection values SHOULD OwnerContributor BAU 14 TFNFR21 Discourage Nullability by Default MUST OwnerContributor BAU 15 TFNFR22 Avoid sensitive = false MUST OwnerContributor BAU 16 TFNFR23 Sensitive Default Value Conditions MUST OwnerContributor BAU 17 TFNFR24 Handling Deprecated Variables MUST OwnerContributor BAU 18 TFNFR25 Verified Modules Requirements MUST OwnerContributor BAU 19 TFNFR26 Providers in required_providers MUST OwnerContributor BAU 20 TFNFR27 Provider Declarations in Modules MUST OwnerContributor BAU 22 TFNFR30 Handling Deprecated Outputs MUST OwnerContributor BAU 23 TFNFR31 locals.tf for Locals Only MAY OwnerContributor BAU 25 TFNFR33 Precise Local Types SHOULD OwnerContributor BAU 26 TFNFR34 Using Feature Toggles MUST OwnerContributor BAU 27 TFNFR35 Reviewing Potential Breaking Changes MUST OwnerContributor BAU 28 TFNFR36 Setting prevent_deletion_if_contains_resources SHOULD OwnerContributor BAU 29 TFNFR37 Tool Usage by Module Owner MAY OwnerContributor BAU βž• See Specifications for this category Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR6 - Category: Code Style - Resource & Data Order For the definition of resources in the same file, the resources be depended on SHOULD come first, after them are the resources depending on others. Resources that have dependencies SHOULD be defined close to each other. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR7 - Category: Code Style - count & for_each Use We can use count and for_each to deploy multiple resources, but the improper use of count can lead to anti pattern. You can use count to create some kind of resources under certain conditions, for example: resource "azurerm_network_security_group" "this" { count = local.create_new_security_group ? 1 : 0 name = coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg") resource_group_name = var.resource_group_name location = local.location tags = var.new_network_security_group_tags } The module’s owners MUST use map(xxx) or set(xxx) as resource’s for_each collection, the map’s key or set’s element MUST be static literals. Good example: resource "azurerm_subnet" "pair" { for_each = var.subnet_map // `map(string)`, when user call this module, it could be: `{ "subnet0": "subnet0" }`, or `{ "subnet0": azurerm_subnet.subnet0.name }` name = "${each.value}"-pair resource_group_name = azurerm_resource_group.example.name virtual_network_name = azurerm_virtual_network.example.name address_prefixes = ["10.0.1.0/24"] } Bad example: resource "azurerm_subnet" "pair" { for_each = var.subnet_name_set // `set(string)`, when user use `toset([azurerm_subnet.subnet0.name])`, it would cause an error. name = "${each.value}"-pair resource_group_name = azurerm_resource_group.example.name virtual_network_name = azurerm_virtual_network.example.name address_prefixes = ["10.0.1.0/24"] } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR8 - Category: Code Style - Resource & Data Block Orders There are 3 types of assignment statements in a resource or data block: argument, meta-argument and nested block. The argument assignment statement is a parameter followed by =: location = azurerm_resource_group.example.location or: tags = { environment = "Production" } Nested block is a assignment statement of parameter followed by {} block: subnet { name = "subnet1" address_prefix = "10.0.1.0/24" } Meta-arguments are assignment statements can be declared by all resource or data blocks. They are: count depends_on for_each lifecycle provider The order of declarations within resource or data blocks is: All the meta-arguments SHOULD be declared on the top of resource or data blocks in the following order: provider count for_each Then followed by: required arguments optional arguments required nested blocks optional nested blocks All ranked in alphabetical order. These meta-arguments SHOULD be declared at the bottom of a resource block with the following order: depends_on lifecycle The parameters of lifecycle block SHOULD show up in the following order: create_before_destroy ignore_changes prevent_destroy parameters under depends_on and ignore_changes are ranked in alphabetical order. Meta-arguments, arguments and nested blocked are separated by blank lines. dynamic nested blocks are ranked by the name comes after dynamic, for example: dynamic "linux_profile" { for_each = var.admin_username == null ? [] : ["linux_profile"] content { admin_username = var.admin_username ssh_key { key_data = replace(coalesce(var.public_ssh_key, tls_private_key.ssh[0].public_key_openssh), "\n", "") } } } This dynamic block will be ranked as a block named linux_profile. Code within a nested block will also be ranked following the rules above. PS: You can use avmfix tool to reformat your code automatically. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR9 - Category: Code Style - Module Block Order The meta-arguments below SHOULD be declared on the top of a module block with the following order: source version count for_each blank lines will be used to separate them. After them will be required arguments, optional arguments, all ranked in alphabetical order. These meta-arguments below SHOULD be declared on the bottom of a resource block in the following order: depends_on providers Arguments and meta-arguments SHOULD be separated by blank lines. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR10 - Category: Code Style - No Double Quotes in ignore_changes The ignore_changes attribute MUST NOT be enclosed in double quotes. Good example: lifecycle { ignore_changes = [ tags, ] } Bad example: lifecycle { ignore_changes = [ "tags", ] } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR11 - Category: Code Style - Null Comparison Toggle Sometimes we need to ensure that the resources created are compliant to some rules at a minimum extent, for example a subnet has to be connected to at least one network_security_group. The user SHOULD pass in a security_group_id and ask us to make a connection to an existing security_group, or want us to create a new security group. Intuitively, we will define it like this: variable "security_group_id" { type: string } resource "azurerm_network_security_group" "this" { count = var.security_group_id == null ? 1 : 0 name = coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg") resource_group_name = var.resource_group_name location = local.location tags = var.new_network_security_group_tags } The disadvantage of this approach is if the user create a security group directly in the root module and use the id as a variable of the module, the expression which determines the value of count will contain an attribute from another resource, the value of this very attribute is “known after apply” at plan stage. Terraform core will not be able to get an exact plan of deployment during the “plan” stage. You can’t do this: resource "azurerm_network_security_group" "foo" { name = "example-nsg" resource_group_name = "example-rg" location = "eastus" } module "bar" { source = "xxxx" ... security_group_id = azurerm_network_security_group.foo.id } For this kind of parameters, wrapping with object type is RECOMMENDED: variable "security_group" { type: object({ id = string }) default = null } The advantage of doing so is encapsulating the value which is “known after apply” in an object, and the object itself can be easily found out if it’s null or not. Since the id of a resource cannot be null, this approach can avoid the situation we are facing in the first example, like the following: resource "azurerm_network_security_group" "foo" { name = "example-nsg" resource_group_name = "example-rg" location = "eastus" } module "bar" { source = "xxxx" ... security_group = { id = azurerm_network_security_group.foo.id } } This technique SHOULD be used under this use case only. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR12 - Category: Code Style - Dynamic for Optional Nested Objects An example from the community: resource "azurerm_kubernetes_cluster" "main" { ... dynamic "identity" { for_each = var.client_id == "" || var.client_secret == "" ? [1] : [] content { type = var.identity_type user_assigned_identity_id = var.user_assigned_identity_id } } ... } Please refer to the coding style in the example. Nested blocks under conditions, MUST be declared as: for_each = <condition> ? [<some_item>] : [] Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR13 - Category: Code Style - Default Values with coalesce/try The following example shows how "${var.subnet_name}-nsg" SHOULD be used when var.new_network_security_group_name is null or "" Good examples: coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg") try(coalesce(var.new_network_security_group.name, "${var.subnet_name}-nsg"), "${var.subnet_name}-nsg") Bad examples: var.new_network_security_group_name == null ? "${var.subnet_name}-nsg" : var.new_network_security_group_name) Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR16 - Category: Code Style - Variable Naming Rules The naming of a variable SHOULD follow HashiCorp’s naming rule. variable used as feature switches SHOULD apply a positive statement, use xxx_enabled instead of xxx_disabled. Avoid double negatives like !xxx_disabled. Please use xxx_enabled instead of xxx_disabled as name of a variable. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR17 - Category: Code Style - Variables with Descriptions The target audience of description is the module users. For a newly created variable (Eg. variable for switching dynamic block on-off), it’s description SHOULD precisely describe the input parameter’s purpose and the expected data type. description SHOULD NOT contain any information for module developers, this kind of information can only exist in code comments. For object type variable, description can be composed in HEREDOC format: variable "kubernetes_cluster_key_management_service" { type: object({ key_vault_key_id = string key_vault_network_access = optional(string) }) default = null description = <<-EOT - `key_vault_key_id` - (Required) Identifier of Azure Key Vault key. See [key identifier format](https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name) for more details. When Azure Key Vault key management service is enabled, this field is required and must be a valid key identifier. When `enabled` is `false`, leave the field empty. - `key_vault_network_access` - (Optional) Network access of the key vault Network access of key vault. The possible values are `Public` and `Private`. `Public` means the key vault allows public access from all networks. `Private` means the key vault disables public access and enables private link. Defaults to `Public`. EOT } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR18 - Category: Code Style - Variables with Types type MUST be defined for every variable. type SHOULD be as precise as possible, any MAY only be defined with adequate reasons. Use bool instead of string or number for true/false Use string for text Use concrete object instead of map(any) Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR19 - Category: Code Style - Sensitive Data Variables If variable’s type is object and contains one or more fields that would be assigned to a sensitive argument, then this whole variable SHOULD be declared as sensitive = true, otherwise you SHOULD extract sensitive field into separated variable block with sensitive = true. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR20 - Category: Code Style - Non-Nullable Defaults for collection values Nullable SHOULD be set to false for collection values (e.g. sets, maps, lists) when using them in loops. However for scalar values like string and number, a null value MAY have a semantic meaning and as such these values are allowed. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR21 - Category: Code Style - Discourage Nullability by Default nullable = true MUST be avoided. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR22 - Category: Code Style - Avoid sensitive = false sensitive = false MUST be avoided. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR23 - Category: Code Style - Sensitive Default Value Conditions A default value MUST NOT be set for a sensitive input - e.g., a default password. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR24 - Category: Code Style - Handling Deprecated Variables Sometimes we will find names for some variable are not suitable anymore, or a change SHOULD be made to the data type. We want to ensure forward compatibility within a major version, so direct changes are strictly forbidden. The right way to do this is move this variable to an independent deprecated_variables.tf file, then redefine the new parameter in variable.tf and make sure it’s compatible everywhere else. Deprecated variable MUST be annotated as DEPRECATED at the beginning of the description, at the same time the replacement’s name SHOULD be declared. E.g., variable "enable_network_security_group" { type = string default = null description = "DEPRECATED, use `network_security_group_enabled` instead; Whether to generate a network security group and assign it to the subnet. Changing this forces a new resource to be created." } A cleanup of deprecated_variables.tf SHOULD be performed during a major version release. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR25 - Category: Code Style - Verified Modules Requirements The terraform.tf file MUST only contain one terraform block. The first line of the terraform block MUST define a required_version property for the Terraform CLI. The required_version property MUST include a constraint on the minimum version of the Terraform CLI. Previous releases of the Terraform CLI can have unexpected behavior. The required_version property MUST include a constraint on the maximum major version of the Terraform CLI. Major version releases of the Terraform CLI can introduce breaking changes and MUST be tested. The required_version property constraint SHOULD use the ~> #.# or the >= #.#.#, < #.#.# format. Note: You can read more about Terraform version constraints in the documentation. Example terraform.tf file: terraform { required_version = "~> 1.6" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.11" } } } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR26 - Category: Code Style - Providers in required_providers The terraform block in terraform.tf MUST contain the required_providers block. Each provider used directly in the module MUST be specified with the source and version properties. Providers in the required_providers block SHOULD be sorted in alphabetical order. Do not add providers to the required_providers block that are not directly required by this module. If submodules are used then each submodule SHOULD have its own versions.tf file. The source property MUST be in the format of namespace/name. If this is not explicitly specified, it can cause failure. The version property MUST include a constraint on the minimum version of the provider. Older provider versions may not work as expected. The version property MUST include a constraint on the maximum major version. A provider major version release may introduce breaking change, so updates to the major version constraint for a provider MUST be tested. The version property constraint SHOULD use the ~> #.# or the >= #.#.#, < #.#.# format. Note: You can read more about Terraform version constraints in the documentation. Good examples: terraform { required_version = "~> 1.6" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.0" } } } terraform { required_version = ">= 1.6.6, < 2.0.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = ">= 3.11.1, < 4.0.0" } } } terraform { required_version = ">= 1.6, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = ">= 3.11, < 4.0" } } } Acceptable example (but not recommended): terraform { required_version = "1.6" required_providers { azurerm = { source = "hashicorp/azurerm" version = "3.11" } } } Bad example: terraform { required_version = ">= 1.6" required_providers { azurerm = { source = "hashicorp/azurerm" version = ">= 3.11" } } } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR27 - Category: Code Style - Provider Declarations in Modules By rules, in the module code provider MUST NOT be declared. The only exception is when the module indeed need different instances of the same kind of provider(Eg. manipulating resources across different locations or accounts), you MUST declare configuration_aliases in terraform.required_providers. See details in this document. provider block declared in the module MUST only be used to differentiate instances used in resource and data. Declaration of fields other than alias in provider block is strictly forbidden. It could lead to module users unable to utilize count, for_each or depends_on. Configurations of the provider instance SHOULD be passed in by the module users. Good examples: In verified module: terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.0" configuration_aliases = [ azurerm.alternate ] } } } In the root module where we call this verified module: provider "azurerm" { features {} } provider "azurerm" { alias = "alternate" features {} } module "foo" { source = "xxx" providers = { azurerm = azurerm azurerm.alternate = azurerm.alternate } } Bad example: In verified module: provider "azurerm" { # Configuration options features {} } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR30 - Category: Code Style - Handling Deprecated Outputs Sometimes we notice that the name of certain output is not appropriate anymore, however, since we have to ensure forward compatibility in the same major version, its name MUST NOT be changed directly. It MUST be moved to an independent deprecated_outputs.tf file, then redefine a new output in output.tf and make sure it’s compatible everywhere else in the module. A cleanup SHOULD be performed to deprecated_outputs.tf and other logics related to compatibility during a major version upgrade. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MAY Type-NonFunctional Validation-TBD See origin...ID: TFNFR31 - Category: Code Style - locals.tf for Locals Only In locals.tf, file we could declare multiple locals blocks, but only locals blocks are allowed. You MAY declare locals blocks next to a resource block or data block for some advanced scenarios, like making a fake module to execute some light-weight tests aimed at the expressions. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR33 - Category: Code Style - Precise Local Types Precise local types SHOULD be used. Good example: { name = "John" age = 52 } Bad example: { name = "John" age = "52" # age should be number } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR34 - Category: Code Style - Using Feature Toggles A toggle variable MUST be used to allow users to avoid the creation of a new resource block by default if it is added in a minor or patch version. E.g., our previous release was v1.2.1 and next release would be v1.3.0, now we’d like to submit a pull request which contains such new resource: resource "azurerm_route_table" "this" { location = local.location name = coalesce(var.new_route_table_name, "${var.subnet_name}-rt") resource_group_name = var.resource_group_name } A user who’s just upgraded the module’s version would be surprised to see a new resource to be created in a newly generated plan file. A better approach is adding a feature toggle to be turned off by default: variable "create_route_table" { type = bool default = false nullable = false } resource "azurerm_route_table" "this" { count = var.create_route_table ? 1 : 0 location = local.location name = coalesce(var.new_route_table_name, "${var.subnet_name}-rt") resource_group_name = var.resource_group_name } Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR35 - Category: Code Style - Reviewing Potential Breaking Changes Potential breaking(surprise) changes introduced by resource block Adding a new resource without count or for_each for conditional creation, or creating by default Adding a new argument assignment with a value other than the default value provided by the provider’s schema Adding a new nested block without making it dynamic or omitting it by default Renaming a resource block without one or more corresponding moved blocks Change resource’s count to for_each, or vice versa Terraform moved block could be your cure. Potential breaking changes introduced by variable and output blocks Deleting(Renaming) a variable Changing type in a variable block Changing the default value in a variable block Changing variable’s nullable to false Changing variable’s sensitive from false to true Adding a new variable without default Deleting an output Changing an output’s value Changing an output’s sensitive value These changes do not necessarily trigger breaking changes, but they are very likely to, they MUST be reviewed with caution. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR36 - Category: Code Style - Setting prevent_deletion_if_contains_resources From Terraform AzureRM 3.0, the default value of prevent_deletion_if_contains_resources in provider block is true. This will lead to an unstable test because the test subscription has some policies applied, and they will add some extra resources during the run, which can cause failures during destroy of resource groups. Since we cannot guarantee our testing environment won’t be applied some Azure Policy Remediation Tasks in the future, for a robust testing environment, prevent_deletion_if_contains_resources SHOULD be explicitly set to false. Category-CodeStyle Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MAY Type-NonFunctional Validation-TBD See origin...ID: TFNFR37 - Category: Code Style - Tool Usage by Module Owner newres is a command-line tool that generates Terraform configuration files for a specified resource type. It automates the process of creating variables.tf and main.tf files, making it easier to get started with Terraform and reducing the time spent on manual configuration. Module owners MAY use newres when they’re trying to add new resource block, attribute, or nested block. They MAY generate the whole block along with the corresponding variable blocks in an empty folder, then copy-paste the parts they need with essential refactoring. Inputs / Outputs The content below is listed based on the following tags  Category-Inputs/OutputsClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SNFR14 Data Types SHOULD OwnerContributor BAU 2 SNFR22 Parameters/Variables for Resource IDs MUST OwnerContributor BAU 3 SNFR26 Output - Parameters - Decorators MUST OwnerContributor BAU 4 PMNFR5 Parameter/Variable Naming SHOULD OwnerContributor BAU 5 TFFR2 Additional Terraform Outputs SHOULD OwnerContributor BAU 6 TFNFR14 Not allowed variables MUST OwnerContributor BAU βž• See Specifications for this category Category-Inputs/Outputs Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: SNFR14 - Category: Inputs - Data Types A module SHOULD use either: simple data types. e.g., string, int, bool. OR Complex data types (objects, arrays, maps) when the language-compliant schema is defined. Category-Inputs/Outputs Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR22 - Category: Inputs - Parameters/Variables for Resource IDs A module parameter/variable that requires a full Azure Resource ID as an input value, e.g. /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{keyVaultName}, MUST contain ResourceId/resource_id in its parameter/variable name to assist users in knowing what value to provide at a glance of the parameter/variable name. Example for the property workspaceId for the Diagnostic Settings resource. In Bicep its parameter name should be workspaceResourceId and the variable name in Terraform should be workspace_resource_id. workspaceId is not descriptive enough and is ambiguous as to which ID is required to be input. Category-Inputs/Outputs Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR26 - Output-Parameters - Decorators Output parameters MUST implement: Decorators in Bicep such as description & secure (if sensitive) Arguments in Terraform such as description & sensitive (if sensitive) Output parameters Bicep Terraform @description('The resourceId of your resource.') output sampleResourceId string = sampleResource.id @description('The key of your resource.') @secure() output sampleResourceKey string = sampleResource.key # Resource output output "foo" { description = "MyResource foo attribute" value = azurerm_resource_myresource.foo } # Output of a sensitive attribute output "bar" { description = "MyResource bar attribute" value = azurerm_resource_myresource.bar sensitive = true } Category-Inputs/Outputs Class-Pattern Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: PMNFR5 - Category: Inputs - Parameter/Variable Naming Parameter/variable input names SHOULD contain the resource to which they pertain. E.g., virtualMachineSku/virtualmachine_sku Category-Inputs/Outputs Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-Functional Validation-TBD See origin...ID: TFFR2 - Category: Outputs - Additional Terraform Outputs Authors SHOULD NOT output entire resource objects as these may contain sensitive outputs and the schema can change with API or provider versions. Instead, authors SHOULD output the computed attributes of the resource as discreet outputs. This kind of pattern protects against provider schema changes and is known as an anti-corruption layer. Remember, you SHOULD NOT output values that are already inputs (other than name). E.g., # Resource output, computed attribute. output "foo" { description = "MyResource foo attribute" value = azurerm_resource_myresource.foo } # Resource output for resources that are deployed using `for_each`. Again only computed attributes. output "childresource_foos" { description = "MyResource children's foo attributes" value = { for key, value in azurerm_resource_mychildresource : key => value.foo } } # Output of a sensitive attribute output "bar" { description = "MyResource bar attribute" value = azurerm_resource_myresource.bar sensitive = true } Category-Inputs/Outputs Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR14 - Category: Inputs - Not allowed variables Since Terraform 0.13, count, for_each and depends_on are introduced for modules, module development is significantly simplified. Module’s owners MUST NOT add variables like enabled or module_depends_on to control the entire module’s operation. Boolean feature toggles are acceptable however. Testing The content below is listed based on the following tags  Category-TestingClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SNFR1 Prescribed Tests MUST OwnerContributor BAU 2 SNFR2 E2E Testing MUST OwnerContributor BAU 3 SNFR3 AVM Compliance Tests MUST OwnerContributor Initial 4 SNFR4 Unit Tests SHOULD OwnerContributor BAU 5 SNFR5 Upgrade Tests SHOULD OwnerContributor BAU 6 SNFR6 Static Analysis/Linting Tests MUST OwnerContributor BAU 7 SNFR7 Idempotency Tests MUST OwnerContributor BAU 8 SNFR24 Testing Child, Extension & Interface Resources MUST OwnerContributor BAU 9 TFNFR5 Test Tooling MUST OwnerContributor BAU 10 TFNFR15 Variable Definition Order SHOULD OwnerContributor BAU βž• See Specifications for this category Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR1 - Category: Testing - Prescribed Tests Modules MUST use the prescribed tooling and testing frameworks defined in the language specific specs. Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR2 - Category: Testing - E2E Testing Modules MUST implement end-to-end (deployment) testing that create actual resources to validate that module deployments work. In Bicep tests are sourced from the directories in /tests/e2e. In Terraform, these are in /examples. Each test MUST run and complete without user inputs successfully, for automation purposes. Each test MUST also destroy/clean-up its resources and test dependencies following a run. Tip To see a directory and file structure for a module, see the language specific contribution guide. Bicep Terraform Resources/Dependencies Required for E2E Tests It is likely that to complete E2E tests, a number of resources will be required as dependencies to enable the tests to pass successfully. Some examples: When testing the Diagnostic Settings interface for a Resource Module, you will need an existing Log Analytics Workspace to be able to send the logs to as a destination. When testing the Private Endpoints interface for a Resource Module, you will need an existing Virtual Network, Subnet and Private DNS Zone to be able to complete the Private Endpoint deployment and configuration. Module owners MUST: Create the required resources that their module depends upon in the test file/directory They MUST either use: Simple/native resource declarations/definitions in their respective IaC language, OR Another already published AVM Module that MUST be pinned to a specific published version. They MUST NOT use any local directory path references or local copies of AVM modules in their own modules test directory. βž• Terraform & Bicep Log Analytics Workspace examples using simple/native declarations for use in E2E tests Terraform resource "azurerm_resource_group" "example" { name = "rsg-test-001" location = "West Europe" } resource "azurerm_log_analytics_workspace" "example" { name = "law-test-001" location = azurerm_resource_group.example.location resource_group_name = azurerm_resource_group.example.name sku = "PerGB2018" retention_in_days = 30 } Bicep resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { name: 'law-test-001' location: resourceGroup().location properties: { sku: { name: 'PerGB2018' } retentionInDays: 30 } } Skipping Deployments (SHOULD NOT) Deployment tests are an important part of a module’s validation and a staple of AVM’s CI environment. However, there are situations where certain e2e-test-deployments cannot be performed against AVM’s test environment (e.g., if a special configuration/registration (such as certain AI models) is required). For these cases, the CI offers the possibility to ‘skip’ specific test cases by placing a file named .e2eignore in their test folder. Note A skipped test case is still added to the ‘Usage Examples’ section of the module’s readme and should be manually validated in regular intervals. Details for use in E2E tests Bicep Terraform You MUST add a note to the tests metadata description, which explains the excemption. If you require that a test is skipped and add an β€œ.e2eignore” file (e.g. \<module\>/tests/e2e/\<testname\>/.e2eignore) to a pull request, a member of the AVM Core Technical Bicep Team must approve set pull request. The content of the file is logged the module’s workflow runs and transparently communicates why the test case is skipped during the deployment validation stage. It iss hence important to specify the reason for skipping the deployment in this file. Sample filecontent: The test is skipped, as only one instance of this service can be deployed to a subscription. Note For resource modules, the ‘defaults’ and ‘waf-aligned’ tests can’t be skipped. The deployment of a test can be skipped by adding a .e2eignore file into a test folder (e.g. /examples/<testname>). Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-Initial Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR3 - Category: Testing - AVM Compliance Tests Modules MUST pass all tests that ensure compliance to AVM specifications. These tests MUST pass before a module version can be published. Important Please note these are still under development at this time and will be published and available soon for module owners. Module owners MUST request a manual GitHub Pull Request review, prior to their first release of version 0.1.0 of their module, from the related GitHub Team: @Azure/avm-core-team-technical-bicep, OR @Azure/avm-core-team-technical-terraform. Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: SNFR4 - Category: Testing - Unit Tests Modules SHOULD implement unit testing to ensure logic and conditions within parameters/variables/locals are performing correctly. These tests MUST pass before a module version can be published. Unit Tests test specific module functionality, without deploying resources. Used on more complex modules. In Bicep and Terraform these live in tests/unit. Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: SNFR5 - Category: Testing - Upgrade Tests Modules SHOULD implement upgrade testing to ensure new features are implemented in a non-breaking fashion on non-major releases. Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR6 - Category: Testing - Static Analysis/Linting Tests Modules MUST use static analysis, e.g., linting, security scanning (PSRule, tflint, etc.). These tests MUST pass before a module version can be published. There may be differences between languages in linting rules standards, but the AVM core team will try to close these and bring them into alignment over time. Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR7 - Category: Testing - Idempotency Tests Modules MUST implement idempotency end-to-end (deployment) testing. E.g. deploying the module twice over the top of itself. Modules SHOULD pass the idempotency test, as we are aware that there are some exceptions where they may fail as a false-positive or legitimate cases where a resource cannot be idempotent. For example, Virtual Machine Image names must be unique on each resource creation/update. Category-Testing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR24 - Category: Testing - Testing Child, Extension & Interface Resources Module owners MUST test that child and extension resources and those Bicep or Terreform interface resources that are supported by their modules, are validated in E2E tests as per SNFR2 to ensure they deploy and are configured correctly. These MAY be tested in a separate E2E test and DO NOT have to be tested in each E2E test. Category-Testing Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR5 - Category: Testing - Test Tooling Module owners MUST use the below tooling for unit/linting/static/security analysis tests. These are also used in the AVM Compliance Tests. Terraform terraform <validate/fmt/test> terrafmt Checkov tflint (with azurerm ruleset) Go Some tests are provided as part of the AVM Compliance Tests, but you are free to also use Go for your own tests. Category-Testing Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: TFNFR15 - Category: Code Style - Variable Definition Order Input variables SHOULD follow this order: All required fields, in alphabetical order All optional fields, in alphabetical order A variable without default value is a required field, otherwise it’s an optional one. Documentation The content below is listed based on the following tags  Category-DocumentationClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SNFR15 Automatic Documentation Generation MUST OwnerContributor BAU 2 SNFR16 Examples/E2E MUST OwnerContributor BAU 3 TFNFR1 Descriptions MUST OwnerContributor BAU 4 TFNFR2 Module Documentation Generation MUST OwnerContributor BAU βž• See Specifications for this category Category-Documentation Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR15 - Category: Documentation - Automatic Documentation Generation README documentation MUST be automatically/programmatically generated. MUST include the sections as defined in the language specific requirements BCPNFR2, TFNFR2. Category-Documentation Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR16 - Category: Documentation - Examples/E2E An examples/e2e directory MUST exist to provide named scenarios for module deployment. Category-Documentation Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR1 - Category: Documentation - Descriptions Where descriptions for variables and outputs spans multiple lines. The description MAY provide variable input examples for each variable using the HEREDOC format and embedded markdown. Example: variable "my_complex_input" { type = map(object({ param1 = string param2 = optional(number, null) })) description = <<DESCRIPTION A complex input variable that is a map of objects. Each object has two attributes: - `param1`: A required string parameter. - `param2`: (Optional) An optional number parameter. Example Input: ```terraform my_complex_input = { "object1" = { param1 = "value1" param2 = 2 } "object2" = { param1 = "value2" } } ``` DESCRIPTION } Category-Documentation Class-Pattern Class-Resource Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: TFNFR2 - Category: Documentation - Module Documentation Generation Terraform modules documentation MUST be automatically generated via Terraform Docs. A file called .terraform-docs.yml MUST be present in the root of the module and have the following content: --- ### To generate the output file to partially incorporate in the README.md, ### Execute this command in the Terraform module's code folder: # terraform-docs -c .terraform-docs.yml . formatter: "markdown document" # this is required version: "0.16.0" header-from: "_header.md" footer-from: "_footer.md" recursive: enabled: false path: modules sections: hide: [] show: [] content: |- {{ .Header }} <!-- markdownlint-disable MD033 --> {{ .Requirements }} {{ .Providers }} {{ .Resources }} <!-- markdownlint-disable MD013 --> {{ .Inputs }} {{ .Outputs }} {{ .Modules }} {{ .Footer }} output: file: README.md mode: replace template: |- <!-- BEGIN_TF_DOCS --> {{ .Content }} <!-- END_TF_DOCS --> output-values: enabled: false from: "" sort: enabled: true by: required settings: anchor: true color: true default: true description: false escape: true hide-empty: false html: true indent: 2 lockfile: true read-comments: true required: true sensitive: true type: true Release / Publishing The content below is listed based on the following tags  Category-Release/PublishingClass-PatternLanguage-Terraform # ID Title Severity Persona Lifecycle 1 SNFR17 Semantic Versioning MUST OwnerContributor BAU 2 SNFR18 Breaking Changes SHOULD OwnerContributor BAU 3 SNFR19 Registries Targeted MUST OwnerContributor BAU 4 SNFR21 Cross Language Collaboration SHOULD OwnerContributor BAU βž• See Specifications for this category Category-Release/Publishing Class-Pattern Class-Resource Class-Utility Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR17 - Category: Release - Semantic Versioning Important You cannot specify the patch version for Bicep modules in the public Bicep Registry, as this is automatically incremented by 1 each time a module is published. You can only set the Major and Minor versions. See the Bicep Contribution Guide for more information. Modules MUST use semantic versioning (aka semver) for their versions and releases in accordance with: Semantic Versioning 2.0.0 For example all modules should be released using a semantic version that matches this pattern: X.Y.Z X == Major Version Y == Minor Version Z == Patch Version Module versioning before first Major version release 1.0.0 Initially modules MUST be released as version 0.1.0 and incremented via Minor and Patch versions only until the AVM Core Team are confident the AVM specifications are mature enough and appropriate CI test coverage is in place, plus the module owner is happy the module has been “road tested” and is now stable enough for its first Major release of version 1.0.0. Note Releasing as version 0.1.0 initially and only incrementing Minor and Patch versions allows the module owner to make breaking changes more easily and frequently as it’s still not an official Major/Stable release. πŸ‘ Until first Major version 1.0.0 is released, given a version number X.Y.Z: X Major version MUST NOT be bumped. Y Minor version MUST be bumped when introducing breaking changes (which would normally bump Major after 1.0.0 release) or feature updates (same as it will be after 1.0.0 release). Z Patch version MUST be bumped when introducing non-breaking, backward compatible bug fixes (same as it will be after 1.0.0 release). Category-Release/Publishing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: SNFR18 - Category: Release - Breaking Changes A module SHOULD avoid breaking changes, e.g., deprecating inputs vs. removing. If you need to implement changes that cause a breaking change, the major version should be increased. Info Modules that have not been released as 1.0.0 may introduce breaking changes, as explained in the previous ID SNFR17. That means that you have to introduce non-breaking and breaking changes with a minor version jump, as long as the module has not reached version 1.0.0. There are, however, scenarios where you want to include breaking changes into a commit and not create a new major version. If you want to introduce breaking changes as part of a minor update, you can do so. In this case, it is essential to keep the change backward compatible, so that the existing code will continue to work. At a later point, another update can increase the major version and remove the code introduced for the backward compatibility. Tip See the language specific examples to find out how you can deal with deprecations in AVM modules. Bicep Category-Release/Publishing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-MUST Type-NonFunctional Validation-TBD See origin...ID: SNFR19 - Category: Publishing - Registries Targeted Modules MUST be published to their respective language public registries. Bicep = Bicep Public Module Registry Within the avm directory Terraform = HashiCorp Terraform Registry Tip See the language specific contribution guides for detailed guidance and sample code to use in AVM modules to achieve this requirement. Bicep Terraform Category-Release/Publishing Class-Pattern Class-Resource Language-Bicep Language-Terraform Lifecycle-BAU Persona-Contributor Persona-Owner Severity-SHOULD Type-NonFunctional Validation-TBD See origin...ID: SNFR21 - Category: Publishing - Cross Language Collaboration When the module owners of the same Resource or Pattern AVM module are not the same individual or team for all languages, each languages team SHOULD collaborate with their sibling language team for the same module to ensure consistency where possible. --- Source: https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/content/specs-defs/specs/terraform/pattern.md Last Modified: 0001-01-01