Bicep Resource Module Specifications
Contribution / Support
The content below is listed based on the following tags
# | 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 | PMNFR4 | Missing Resource Module(s) | MUST | OwnerContributor | BAU |
9 | BCPNFR15 | AVM Module Issue template file | MUST | Owner | BAU |
➕ See Specifications for this category
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.
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.
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’sModule Owners
team<hyphenated module name>-module-contributors-<bicep/tf>
- to be assigned as the GitHub repository’sModule 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>
module-owners
ormodule-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 theavm-technical-reviewers-bicep
parent team.avm-res-network-virtualnetwork-module-contributors-bicep
–> assign to theavm-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
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.
ID: SNFR10 - Category: Contribution/Support - MIT Licensing
A module MUST be published with the MIT License in the Azure GitHub organization.
ID: SNFR11 - Category: Contribution/Support - Issues Response Times
A module owner MUST respond to logged issues within 3 business days. See Module Support for more information.
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.
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. |
|
Needs: Attention 👋 | Reply has been added to issue, maintainer to review |
|
Needs: Immediate Attention ‼️ | Immediate attention of module owner / AVM team is needed |
|
Needs: Author Feedback 👂 | Awaiting feedback from the issue/PR author |
|
Needs: External Changes ⚒️ | When an issue/PR requires changes that are outside of the control of the module. e.g. to an RP. |
|
Needs: More Evidence ⚖ | We are looking for more evidence to make a decision on this |
|
Needs: Triage 🔍 | Maintainers need to triage still |
|
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. |
|
Needs: Module Contributor 📣 | This module needs secondary owner(s) or contributor(s) to develop or maintain it |
|
Needs: Core Team 🧞♂️ | This item needs the AVM Core Team to review it |
|
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 |
|
Status: Do Not Merge ⛔ | Do not merge PRs with this label attached as they are not ready or aligned to future direction etc. |
|
Status: External Contribution 🌍 | This is being worked on by someone outside of the AVM module owners/contributors or AVM core team |
|
Status: Fixed ✅ | Auto label applied when issue fixed by merged PR |
|
Status: Help Wanted 🆘 | Extra attention is needed |
|
Status: In Triage 🔍 | Picked up for triaging by an AVM core team member |
|
Status: In PR 👉 | This is when an issue is due to be fixed in an open PR |
|
Status: Invalid ❌ | This doesn't seem right |
|
Status: Long Term ⏳ | We will do it, but will take a longer amount of time due to complexity/priorities |
|
Status: No Recent Activity 💤 | When an issue/PR has not been modified for X amount of days |
|
Status: Won't Fix 💔 | This will not be worked on |
|
Status: Owners Identified 🤘 | This module has its owners identified |
|
Status: Module Available 🟢 | The module is published |
|
Status: Module Orphaned 👀 | The module has no owner and is therefore orphaned at this time |
|
Status: Response Overdue 🚩 | When an issue/PR has not been responded to for X amount of days |
|
Status: Looking For Assistance 🦆 | This item is looking for anyone to help develop the code and submit a PR for resolution |
|
Type: Bug 🐛 | Something isn't working |
|
Type: CI 🚀 | This issue is related to the AVM CI |
|
Type: Documentation 📄 | Improvements or additions to documentation |
|
Type: Duplicate 🤲 | This issue or pull request already exists |
|
Type: Feature Request ➕ | New feature or request |
|
Type: Hygiene 🧹 | things related to testing, issue triage etc. |
|
Type: New Module Proposal 💡 | A new module for AVM is being proposed |
|
Type: Question/Feedback 🙋♀️ | Further information is requested or just some feedback |
|
Type: Security Bug 🔒 | This is a security bug |
|
Type: AVM 🅰️ ✌️ ⓜ️ | This is an AVM related issue |
|
Language: Terraform 🌐 | This is related to the Terraform IaC language |
|
Language: Bicep 💪 | This is related to the Bicep IaC language |
|
Class: Resource Module 📦 | This is a resource module |
|
Class: Pattern Module 📦 | This is a pattern module |
|
Class: Utility Module 📦 | This is a utility module |
|
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
:
Set-AvmGitHubLabels.ps1 -RepositoryName "Org/MyGitHubRepo" -CreateCsvLabelExports $false -NoUserPrompts $true
```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/
Full Script:
These Set-AvmGitHubLabels.ps1
can be downloaded from here.
[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
ID: PMNFR4 - Category: Hygiene - Missing Resource Module(s)
An item MUST be logged onto as an issue on the AVM Central Repo (Azure/Azure-Verified-Modules
) if a Resource Module does not exist for resources deployed by the pattern module.
Exception
If the Resource Module adds no value, see Resource Module functional requirement ID: RMFR2.
ID: BCPNFR15 - Category: Contribution/Support - AVM Module Issue template file
As part of the “initial Pull Request” (that publishes the first version of the module), module owners MUST add an entry to the AVM Module Issue template
file in the BRM repository (here).
Note
Through this approach, the AVM core team will allow raising a bug or feature request for a module, only after the module gets merged to the BRM repository.
The module name entry MUST be added to the dropdown list with id module-name-dropdown
as an option, in alphabetical order.
Important
Module owners MUST ensure that the module name is added in alphabetical order, to simplify selecting the right module name when raising an AVM module issue.
Example - AVM Module Issue template
module name entry for the Bicep resource module of Azure Virtual Network (avm/res/network/virtual-network
):
- type: dropdown
id: module-name-dropdown
attributes:
label: Module Name
description: Which existing AVM module is this issue related to?
options:
...
- "avm/res/network/virtual-network"
...
Telemetry
The content below is listed based on the following tags
# | ID | Title | Severity | Persona | Lifecycle |
---|---|---|---|---|---|
1 | SFR3 | Deployment/Usage Telemetry | MUST | Owner | Initial |
2 | SFR4 | Telemetry Enablement Flexibility | MUST | Owner | Initial |
3 | SNFR3 | AVM Compliance Tests | MUST | OwnerContributor | Initial |
4 | BCPFR4 | Telemetry Enablement | MUST | OwnerContributor | BAU |
➕ See Specifications for this category
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
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-
- The prefixes:
<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)
- The module is deployed multiple times to the same:
- This is to cater for the following scenarios:
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
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.
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
.
ID: BCPFR4 - Category: Composition - Telemetry Enablement
To comply with specifications outlined in SFR3 & SFR4 you MUST incorporate the following code snippet into your modules. Place this code sample in the “top level” main.bicep
file; it is not necessary to include it in any nested Bicep files (child modules).
@description('Optional. Location for all resources.')
param location string = resourceGroup().location
@description('Optional. Enable/Disable usage telemetry for module.')
param enableTelemetry bool = true
#disable-next-line no-deployments-resources
resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) {
name: take('46d3xbcp.res.compute-virtualmachine.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}', 64)
properties: {
mode: 'Incremental'
template: {
'$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
contentVersion: '1.0.0.0'
resources: []
outputs: {
telemetry: {
type: 'String'
value: 'For more information, see https://aka.ms/avm/TelemetryInfo'
}
}
}
}
}
Naming / Composition
The content below is listed based on the following tags
# | 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 | RMFR1 | Single Resource Only | MUST | OwnerContributor | BAU |
7 | RMFR2 | No Resource Wrapper Modules | MUST | Owner | Initial |
8 | RMFR3 | Resource Groups | MUST | OwnerContributor | BAU |
9 | RMFR4 | AVM Consistent Feature & Extension Resources Value Add | MUST | OwnerContributor | BAU |
10 | RMFR5 | AVM Consistent Feature & Extension Resources Value Add Interfaces/Schemas | MUST | OwnerContributor | BAU |
11 | RMFR8 | Dependency on child and other resources | MUST | OwnerContributor | BAU |
12 | RMFR9 | End-of-life resource versions | SHOULD | OwnerContributor | BAU |
13 | RMNFR1 | Module Naming | MUST | Owner | Initial |
14 | RMNFR3 | RP Collaboration | SHOULD | Owner | BAU |
15 | BCPFR1 | Cross-Referencing Modules | MAY | OwnerContributor | BAU |
16 | BCPFR6 | Cross-Referencing Child-Modules | MUST | OwnerContributor | BAU |
17 | BCPFR2 | Role Assignments Role Definition Mapping | MUST | OwnerContributor | BAU |
18 | BCPNFR19 | User-defined types - Naming | MUST | OwnerContributor | BAU |
19 | BCPNFR5 | Role Assignments Role Definition Mapping Limits | SHOULD | OwnerContributor | BAU |
20 | BCPNFR6 | Role Assignments Role Definition Mapping Compulsory Roles | MUST | OwnerContributor | BAU |
21 | BCPNFR14 | Versioning | MUST | OwnerContributor | BAU |
22 | BCPRMNFR3 | Child resources structure | MUST | OwnerContributor | BAU |
➕ See Specifications for this category
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., is2021-05-01-preview
- Otherwise the latest “non-preview” version of the API SHOULD be used
- For example, Diagnostic Settings (
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”
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.
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
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
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.
ID: RMFR1 - Category: Composition - Single Resource Only
A resource module MUST only deploy a single instance of the primary resource, e.g., one virtual machine per instance.
Multiple instances of the module MUST be used to scale out.
ID: RMFR2 - Category: Composition - No Resource Wrapper Modules
A resource module MUST add value by including additional features on top of the primary resource.
ID: RMFR3 - Category: Composition - Resource Groups
A resource module MUST NOT create a Resource Group for resources that require them.
In the case that a Resource Group is required, a module MUST have an input (scope or variable):
- In Bicep the
targetScope
MUST be set toresourceGroup
or not specified (which means default toresourceGroup
scope) - In Terraform the
variable
MUST be calledresource_group_name
Scopes will be covered further in the respective language specific specifications.
ID: RMFR4 - Category: Composition - AVM Consistent Feature & Extension Resources Value Add
Resource modules support the following optional features/extension resources, as specified, if supported by the primary resource. The top-level variable/parameter names MUST be:
Optional Features/Extension Resources | Bicep Parameter Name | Terraform Variable Name | MUST/SHOULD |
---|---|---|---|
Diagnostic Settings | diagnosticSettings | diagnostic_settings | MUST |
Role Assignments | roleAssignments | role_assignments | MUST |
Resource Locks | lock | lock | MUST |
Tags | tags | tags | MUST |
Managed Identities (System / User Assigned) | managedIdentities | managed_identities | MUST |
Private Endpoints | privateEndpoints | private_endpoints | MUST |
Customer Managed Keys | customerManagedKey | customer_managed_key | MUST |
Azure Monitor Alerts | alerts | alerts | SHOULD |
Resource modules MUST NOT deploy required/dependent resources for the optional features/extension resources specified above. For example, for Diagnostic Settings the resource module MUST NOT deploy the Log Analytics Workspace, this is expected to be already in existence from the perspective of the resource module deployed via another method/module etc.
Note
Please note that the implementation of Customer Managed Keys from an ARM API perspective is different across various RPs that implement Customer Managed Keys in their service. For that reason you may see differences between modules on how Customer Managed Keys are handled and implemented, but functionality will be as expected.
Module owners MAY choose to utilize cross repo dependencies for these “add-on” resources, or MAY chose to implement the code directly in their own repo/module. So long as the implementation and outputs are as per the specifications requirements, then this is acceptable.
ID: RMFR5 - Category: Composition - AVM Consistent Feature & Extension Resources Value Add Interfaces/Schemas
Resource modules MUST implement a common interface, e.g. the input’s data structures and properties within them (objects/arrays/dictionaries/maps), for the optional features/extension resources:
See:
ID: RMFR8 - Category: Composition - Dependency on child and other resources
A resource module MAY contain references to other resource modules, however MUST NOT contain references to non-AVM modules nor AVM pattern modules.
See BCPFR1 and TFFR1 for more information on this.
ID: RMFR9 - Category: Composition - End-of-life resource versions
When a given version of an Azure resource used in a resource module reaches its end-of-life (EOL) and is no longer supported by Microsoft, the module owner SHOULD ensure that:
- The module is aligned with these changes and only includes supported versions of the resource. This is typically achieved through the allowed values in the parameter that specifies the resource SKU or type.
- The following notice is shown under the
Notes
section of the module’sreadme.md
. (If any related public announcement is available, it can also be linked to from the Notes section.):“Certain versions of this Azure resource reached their end of life. The latest version of this module only includes supported versions of the resource. All unsupported versions have been removed from the related parameters.”
- AND the related parameter’s description:
“Certain versions of this Azure resource reached their end of life. The latest version of this module only includes supported versions of the resource. All unsupported versions have been removed from this parameter.”
ID: RMNFR1 - Category: Naming - Module Naming
Note
We will maintain a set of CSV files in the AVM Central Repo (Azure/Azure-Verified-Modules
) with the correct singular names for all resource types to enable checks to utilize this list to ensure repos are named correctly. To see the formatted content of these CSV files with additional information, please visit the AVM Module Indexes page.
This will be updated quarterly, or ad-hoc as new RPs/ Resources are created and highlighted via a check failure.
Resource modules MUST follow the below naming conventions (all lower case):
Bicep Resource Module Naming
- Naming convention:
avm/res/<hyphenated resource provider name>/<hyphenated ARM resource type>
(module name for registry) - Example:
avm/res/compute/virtual-machine
oravm/res/managed-identity/user-assigned-identity
- Segments:
res
defines this is a resource module<hyphenated resource provider name>
is the resource provider’s name after theMicrosoft
part, with each word starting with a capital letter separated by dashes, e.g.,Microsoft.Compute
=compute
,Microsoft.ManagedIdentity
=managed-identity
.<hyphenated ARM resource type>
is the singular version of the word after the resource provider, with each word starting with a capital letter separated by dashes, e.g.,Microsoft.Compute/virtualMachines
=virtual-machine
, BUTMicrosoft.Network/trafficmanagerprofiles
=trafficmanagerprofile
- sincetrafficmanagerprofiles
is all lower case as per the ARM API definition.
Terraform Resource Module Naming
- Naming convention:
avm-res-<resource provider>-<ARM resource type>
(module name for registry)terraform-<provider>-avm-res-<resource provider>-<ARM resource type>
(GitHub repository name to meet registry naming requirements)
- Example:
avm-res-compute-virtualmachine
oravm-res-managedidentity-userassignedidentity
- Segments:
<provider>
is the logical abstraction of various APIs used by Terraform. In most cases, this is going to beazurerm
orazuread
for resource modules.res
defines this is a resource module<resource provider>
is the resource provider’s name after theMicrosoft
part, e.g.,Microsoft.Compute
=compute
.<ARM resource type>
is the singular version of the word after the resource provider, e.g.,Microsoft.Compute/virtualMachines
=virtualmachine
ID: RMNFR3 - Category: Composition - RP Collaboration
Module owners (Microsoft FTEs) SHOULD reach out to the respective Resource Provider teams to build a partnership and collaboration on the modules creation, existence and long term maintenance.
Review this wiki page (Microsoft Internal) for more information.
ID: BCPFR1 - 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 public registry reference to a pinned version e.g. br/public:avm/[res|ptn|utl]/<publishedModuleName>:>version<
. They MUST NOT use local parent path references to a module e.g. ../../xxx/yyy.bicep
.
The only exception to this rule are child modules as documented in BCPFR6.
Modules MUST NOT contain references to non-AVM modules.
ID: BCPFR6 - Cross-Referencing Child-Modules
Parent templates MUST reference all their direct child-templates to allow for an end-to-end deployment experience.
For example, the SQL server template must reference its child database module and encapsulate it in a loop to allow for the deployment of multiple databases.
@description('Optional. The databases to create in the server')
param databases databaseType[]?
resource server 'Microsoft.Sql/servers@(...)' = { (...) }
module server_databases 'database/main.bicep' = [for (database, index) in (databases ?? []): {
name: '${uniqueString(deployment().name, location)}-Sql-DB-${index}'
params: {
serverName: server.name
(...)
}
}]
ID: BCPFR2 - Category: Composition - Role Assignments Role Definition Mapping
Module owners MAY define common RBAC Role Definition names and IDs within a variable to allow consumers to define a RBAC Role Definition by their name rather than their ID, this should be self contained within the module themselves.
However, they MUST use only the official RBAC Role Definition name within the variable and nothing else.
To meet the requirements of BCPFR2, BCPNFR5 and BCPNFR6 you MUST use the below code sample in your AVM Modules to achieve this.
@description('''Required. You can provide either the display name (note not all roles are supported, check module documentation) of the role definition, or its fully qualified ID in the following format: `/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11`.''')
param roleDefinitionIdOrName string
var builtInRbacRoleNames = {
Owner: '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
Contributor: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c'
Reader: '/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7'
'Role Based Access Control Administrator (Preview)': '/providers/Microsoft.Authorization/roleDefinitions/f58310d9-a9f6-439a-9e8d-f62e7b41a168'
'User Access Administrator': '/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'
//Other RBAC Role Definitions Names & IDs can be added here as needed for your module
}
var roleDefinitionIdMappedResult = (contains(builtInRbacRoleNames, roleDefinitionIdOrName) ? builtInRbacRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName)
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
//Other properties removed for ease of reading
properties: {
roleDefinitionId: roleDefinitionIdMappedResult
//Other properties removed for ease of reading
}
}
ID: BCPNFR19 - User-defined types - Naming
User-defined types (UDTs) MUST always end with the suffix (...)Type
to make them obvious to users. In addition it is recommended to extend the suffix to (...)OutputType
if a UDT is exclusively used for outputs.
type subnet = { ... } // Wrong
type subnetType = { ... } // Correct
type subnetOutputType = { ... } // Correct, if used only for outputs
Since User-defined types (UDTs) MUST always be singular as per BCPNFR18, their naming should reflect this and also be singular.
type subnetsType = { ... } // Wrong
type subnetType = { ... } // Correct
ID: BCPNFR5 - Category: Composition - Role Assignments Role Definition Mapping Limits
As per BCPFR2, module owners MAY define common RBAC Role Definition names and IDs within a variable to allow consumers to define a RBAC Role Definition by their name rather than their ID.
Module owners SHOULD NOT map every RBAC Role Definition within this variable as it can cause the module to bloat in size and cause consumption issues later when stitched together with other modules due to the 4MB ARM Template size limit.
Therefore module owners SHOULD only map the most applicable and common RBAC Role Definition names for their module and SHOULD NOT exceed 15 RBAC Role Definitions in the variable.
Important
Remember if the RBAC Role Definition name is not included in the variable this does not mean it cannot be declared, used and assigned to an identity via an RBAC Role Assignment as part of a module, as any RBAC Role Definition can be specified via its ID without being in the variable.
Tip
Review the Bicep Contribution Guide’s ‘RBAC Role Definition Name Mapping’ section for a code sample to achieve this requirement.
ID: BCPNFR6 - Category: Composition - Role Assignments Role Definition Mapping Compulsory Roles
Module owners MUST include the following roles in the variable for RBAC Role Definition names:
- Owner - ID:
8e3af657-a8ff-443c-a75c-2fe8c4bcb635
- Contributor - ID:
b24988ac-6180-42a0-ab88-20f7382dd24c
- Reader - ID:
acdd72a7-3385-48ef-bd42-f606fba81ae7
- User Access Administrator - ID:
18d7d88d-d35e-4fb5-a5c3-7773c20a72d9
- Role Based Access Control Administrator (Preview) - ID:
f58310d9-a9f6-439a-9e8d-f62e7b41a168
Tip
Review the Bicep Contribution Guide’s ‘RBAC Role Definition Name Mapping’ section for a code sample to achieve this requirement.
ID: BCPNFR14 - Category: Composition - Versioning
To meet SNFR17 and depending on the changes you make, you may need to bump the version in the version.json
file.
{
"$schema": "https://aka.ms/bicep-registry-module-version-file-schema#",
"version": "0.1",
"pathFilters": [
"./main.json"
]
}
The version
value is in the form of MAJOR.MINOR
. The PATCH version will be incremented by the CI automatically when publishing the module to the Public Bicep Registry once the corresponding pull request is merged. Therefore, contributions that would only require an update of the patch version, can keep the version.json
file intact.
For example, the version
value should be:
0.1
for new modules, so that they can be released asv0.1.0
.1.0
once the module owner signs off the module is stable enough for it’s first Major release ofv1.0.0
.0.x
for all feature updates between the first releasev0.1.0
and the first Major release ofv1.0.0
.
ID: BCPRMNFR3 - Implementing child resources
Child resource modules MUST be stored in a subfolder of their parent resource module and named after the child resource’s singular name (ref), so that the path to the child resource folder is consistent with the hierarchy of its resource type.
For example, Microsoft.Sql/servers
may have dedicated child resources of type Microsoft.Sql/servers/databases
. Hence, the SQL server database child module is stored in a database
subfolder of the server
parent folder.
sql
└─ server [module]
└─ database [child-module/resource]
In this folder, we recommend to place the child resource-template alongside a ReadMe & compiled JSON (to be generated via the default Set-AVMModule utility) and optionally further nest additional folders for its child resources.
There are several reasons to structure a module in this way. For example:
- It allows a separation of concerns where each module can focus on its own properties and logic, while delegating most of a child-resource’s logic to its separate child module
- It’s consistent with the provider namespace structure and makes modules easier to understand not only because they’re more aligned with set structure, but also are aligned with one another
- As each module is its own ‘deployment’, it reduces limitations around nested loops
- Once the feature is enabled, it will enable module owners to publish set child-modules as separate modules to the public registry, allowing consumers to make use of them directly.
Note
In full transparency: The drawbacks of these additional deployments is an extended deployment period & a contribution to the 800 deployments limit. However, for AVM resource modules it was agreed that the advantages listed above outweigh these limitations.
Inputs / Outputs
The content below is listed based on the following tags
# | ID | Title | Severity | Persona | Lifecycle |
---|---|---|---|---|---|
1 | SNFR14 | Data Types | SHOULD | OwnerContributor | BAU |
2 | SNFR22 | Parameters/Variables for Resource IDs | MUST | OwnerContributor | BAU |
3 | RMFR6 | Parameter/Variable Naming | MUST | OwnerContributor | BAU |
4 | RMFR7 | Minimum Required Outputs | MUST | OwnerContributor | BAU |
5 | RMNFR2 | Parameter/Variable Naming | MUST | OwnerContributor | BAU |
6 | BCPFR5 | Availability Zones Implementation | SHOULD | OwnerContributor | BAU |
7 | BCPNFR1 | User-defined types - General | MUST | OwnerContributor | BAU |
8 | BCPNFR18 | User-defined types - Specification | MUST | OwnerContributor | BAU |
9 | BCPNFR19 | User-defined types - Naming | MUST | OwnerContributor | BAU |
10 | BCPNFR20 | User-defined types - Export | MUST | OwnerContributor | BAU |
11 | BCPNFR21 | User-defined types - Decorators | MUST | OwnerContributor | BAU |
12 | BCPNFR9 | Inputs - Decorators | MUST | OwnerContributor | BAU |
13 | BCPNFR7 | Parameter Requirement Types | MUST | OwnerContributor | BAU |
14 | BCPRMNFR2 | User-defined types - AVM-Common-Types | MUST | OwnerContributor | BAU |
➕ See Specifications for this category
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.
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.
ID: RMFR6 - Category: Inputs - Parameter/Variable Naming
Parameters/variables that pertain to the primary resource MUST NOT use the resource type in the name.
e.g., use sku
, vs. virtualMachineSku
/virtualmachine_sku
Another example for where RPs contain some of their name within a property, leave the property unchanged. E.g. Key Vault has a property called keySize
, it is fine to leave as this and not remove the key
part from the property/parameter name.
ID: RMFR7 - Category: Outputs - Minimum Required Outputs
Module owners MUST output the following outputs as a minimum in their modules:
Output | Bicep Output Name | Terraform Output Name |
---|---|---|
Resource Name | name | name |
Resource ID | resourceId | resource_id |
System Assigned Managed Identity Principal ID (if supported by module) | systemAssignedMIPrincipalId | system_assigned_mi_principal_id |
Tip
ID: RMNFR2 - Category: Inputs - Parameter/Variable Naming
A resource module MUST use the following standard inputs:
name
(no default)location
(if supported by the resource and not a global resource, then use Resource Group location, if resource supports Resource Groups, otherwise no default)
ID: BCPFR5 - Category: Inputs - Availability Zones Implementation
To implement requirement SFR5, the following convention SHOULD apply:
@description('Optional. The Availability Zones to place the resources in.')
@allowed([
1
2
3
])
param zones int[] = [
1
2
3
]
resource myResource (...) {
(...)
properties: {
(...)
zones: map(zones, zone => string(zone))
}
}
@description('Required. The Availability Zone to place the resource in. If set to 0, then Availability Zone is not set.')
@allowed([
0
1
2
3
])
param zone int
resource myResource (...) {
(...)
properties: {
(...)
zones: zone != 0 ? [ string(zone) ] : null
}
}
ID: BCPNFR1 - Inputs - User-defined types - General
To simplify the consumption experience for module consumers when interacting with complex data types input parameters, mainly objects and arrays, the Bicep feature of User-Defined Types MUST be used and declared.
Tip
User-Defined Types are GA in Bicep as of version v0.21.1, please ensure you have this version installed as a minimum.
User-Defined Types allow intellisense support in supported IDEs (e.g. Visual Studio Code) for complex input parameters using arrays and objects.
CARML Migration Exemption
While the transition of CARML modules into AVM is complete, retrofitting User-Defined Types for all modules will take a considerable amount of time.
Therefore, the addition of User-Defined Types is currently NOT mandated/enforced. However, past their initial release, all modules MUST implement User-Defined Types prior to the release of their next version.
ID: BCPNFR18 - User-defined types - Specification
User-defined types (UDTs) MUST always be singular and non-nullable. The configuration of either should instead be done directly at the parameter or output that uses the type.
For example, instead of
param subnets subnetsType
type subnetsType = { ... }[]?
the type should be defined like
param subnets subnetType[]?
type subnetType = { ... }
The primary reason for this requirement is clarity. If not defined directly at the parameter or output, a user would always be required to check the type to understand how e.g., a parameter is expected.
ID: BCPNFR19 - User-defined types - Naming
User-defined types (UDTs) MUST always end with the suffix (...)Type
to make them obvious to users. In addition it is recommended to extend the suffix to (...)OutputType
if a UDT is exclusively used for outputs.
type subnet = { ... } // Wrong
type subnetType = { ... } // Correct
type subnetOutputType = { ... } // Correct, if used only for outputs
Since User-defined types (UDTs) MUST always be singular as per BCPNFR18, their naming should reflect this and also be singular.
type subnetsType = { ... } // Wrong
type subnetType = { ... } // Correct
ID: BCPNFR20 - User-defined types - Export
User-defined types (UDTs) SHOULD always be exported via the @export()
annotation in every template they’re implemented in.
@export()
type subnetType = { ... }
Doing so has the benefit that other (e.g., parent) modules can import them and as such reduce code duplication. Also, if the module itself is published, users of the Public Bicep Registry can import the types independently of the module itself. One example where this can be useful is a pattern module that may re-use the same interface when referencing a module from the registry.
ID: BCPNFR21 - User-defined types - Decorators
Similar to BCPNFR9, User-defined types (UDTs) MUST implement decorators such as description
& secure
(if sensitive). This is true for every property of the UDT, as well as the UDT itself.
Further, User-defined types SHOULD implement decorators like allowed
, minValue
, maxValue
, minLength
& maxLength
(and others if available) as they have a big positive impact on the module’s usability.
@description('My type''s description.')
type myType = {
@description('Optional. The threshold of your resource.')
@minValue(1)
@maxValue(10)
threshold: int?
@description('Required. The SKU of your resource.')
sku: ('Basic' | 'Premium' | 'Standard')
}
ID: BCPNFR9 - Inputs - Decorators
Similar to BCPNFR21, input parameters MUST implement decorators such as description
& secure
(if sensitive).
Further, input parameters SHOULD implement decorators like allowed
, minValue
, maxValue
, minLength
& maxLength
(and others if available) as they have a big positive impact on the module’s usability.
@description('Optional. The threshold of your resource.')
@minValue(1)
@maxValue(10)
param threshold: int?
@description('Required. The SKU of your resource.')
@allowed([
'Basic'
'Premium'
'Standard'
])
param sku string
ID: BCPNFR7 - Category: Inputs - Parameter Requirement Types
Modules will have lots of parameters that will differ in their requirement type (required, optional, etc.). To help consumers understand what each parameter’s requirement type is, module owners MUST add the requirement type to the beginning of each parameter’s description. Below are the requirement types with a definition and example for the description decorator:
Parameter Requirement Type | Definition | Example Description Decorator |
---|---|---|
Required | The parameter value must be provided. The parameter does not have a default value and hence the module expects and requires an input. | @description('Required. <PARAMETER DESCRIPTION HERE...>') |
Conditional | The parameter value can be optional or required based on a condition, mostly based on the value provided to other parameters. Should contain a sentence starting with ‘Required if (…).’ to explain the condition. | @description('Conditional. <PARAMETER DESCRIPTION HERE...>') |
Optional | The parameter value is not mandatory. The module provides a default value for the parameter. | @description('Optional. <PARAMETER DESCRIPTION HERE...>') |
Generated | The parameter value is generated within the module and should not be specified as input in most cases. A common example of this is the utcNow() function that is only supported as the input for a parameter value, and not inside a variable. | @description('Generated. <PARAMETER DESCRIPTION HERE...>') |
ID: BCPRMNFR2 - User-defined types - AVM-Common-Types
When implementing any of the shared or Bicep-specific AVM interface variants you MUST import their User-defined type (UDT) via the published AVM-Common-Types module.
When doing so, each type MUST be imported separately, right above the parameter or output that uses it.
import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:*.*.*'
@description('Optional. Array of role assignments to create.')
param roleAssignments roleAssignmentType[]?
import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:*.*.*'
@description('Optional. The diagnostic settings of the service.')
param diagnosticSettings diagnosticSettingFullType[]?
Importing them individually as opposed to one common block has several benefits such as
- Individual versioning of types
- If you must update the version for one type, you’re not exposed to unexpected changes to other types
Important
The import (...)
block MUST not be added in between a parameter’s definition and its metadata. Doing so breaks the metadata’s binding to the parameter in question.
Finally, you should check for version updates regularly to ensure the resource module stays consistent with the specs. If the used AVM-Common-Types runs stale, the CI may eventually fail the module’s static tests.
Testing
The content below is listed based on the following tags
# | ID | Title | Severity | Persona | Lifecycle |
---|---|---|---|---|---|
1 | SNFR1 | Prescribed Tests | MUST | OwnerContributor | BAU |
2 | SNFR2 | E2E Testing | MUST | OwnerContributor | BAU |
3 | SNFR4 | Unit Tests | SHOULD | OwnerContributor | BAU |
4 | SNFR5 | Upgrade Tests | SHOULD | OwnerContributor | BAU |
5 | SNFR6 | Static Analysis/Linting Tests | MUST | OwnerContributor | BAU |
6 | SNFR7 | Idempotency Tests | MUST | OwnerContributor | BAU |
7 | SNFR24 | Testing Child, Extension & Interface Resources | MUST | OwnerContributor | BAU |
8 | BCPNFR10 | Test Bicep File Naming | MUST | OwnerContributor | BAU |
9 | BCPNFR11 | Test Tooling | MUST | OwnerContributor | BAU |
10 | BCPNFR12 | Deployment Test Naming | MUST | OwnerContributor | BAU |
11 | BCPNFR13 | Test file metadata | MUST | OwnerContributor | BAU |
12 | BCPNFR16 | Post-deployment tests | MUST | OwnerContributor | BAU |
13 | BCPRMNFR1 | Expected Test Directories | MUST | OwnerContributor | BAU |
➕ See Specifications for this category
ID: SNFR1 - Category: Testing - Prescribed Tests
Modules MUST use the prescribed tooling and testing frameworks defined in the language specific specs.
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
Required 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.
- Simple/native resource declarations/definitions in their respective IaC language,
- They MUST either use:
➕ 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
}
}
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
.
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.
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.
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.
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.
ID: BCPNFR10 - Category: Testing - Test Bicep File Naming
Module owners MUST name their test .bicep
files in the /tests/e2e/<defaults/waf-aligned/max/etc.>
directories: main.test.bicep
as the test framework (CI) relies upon this name.
ID: BCPNFR11 - 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.
- PSRule for Azure
- Pester
- Some tests are provided as part of the AVM Compliance Tests, but you are free to also use Pester for your own tests.
ID: BCPNFR12 - Category: Testing - Deployment Test Naming
Module owners MUST invoke the module in their test using the syntax:
module testDeployment '../../../main.bicep' =
Example 1: Working example with a single deployment
module testDeployment '../../../main.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name, location)}-test-${serviceShort}'
params: {
(...)
}
}
Example 2: Working example using a deployment loop
@batchSize(1)
module testDeployment '../../main.bicep' = [for iteration in [ 'init', 'idem' ]: {
scope: resourceGroup
name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}'
params: {
(...)
}
}]
The syntax is used by the ReadMe-generating utility to identify, pull & format usage examples.
ID: BCPNFR13 - Category: Testing - Test file metadata
By default, the ReadMe-generating utility will create usage examples headers based on each e2e
folder’s name.
Module owners MAY provide a custom name & description by specifying the metadata blocks name
& description
in their main.test.bicep
test files.
For example:
metadata name = 'Using Customer-Managed-Keys with System-Assigned identity'
metadata description = 'This instance deploys the module using Customer-Managed-Keys using a System-Assigned Identity. This required the service to be deployed twice, once as a pre-requisite to create the System-Assigned Identity, and once to use it for accessing the Customer-Managed-Key secret.'
would lead to a header in the module’s readme.md
file along the lines of
### Example 1: _Using Customer-Managed-Keys with System-Assigned identity_
This instance deploys the module using Customer-Managed-Keys using a System-Assigned Identity. This required the service to be deployed twice, once as a pre-requisite to create the System-Assigned Identity, and once to use it for accessing the Customer-Managed-Key secret.
ID: BCPNFR16 - Category: Testing - Post-deployment tests
For each test case in the e2e
folder, you can optionally add post-deployment Pester tests that are executed once the corresponding deployment completed and before the removal logic kicks in.
To leverage the feature you MUST:
Use Pester as a test framework in each test file
Name the file with the suffix
"*.tests.ps1"
Place each test file the
e2e
test’s folder or any subfolder (e.g.,e2e/max/myTest.tests.ps1
ore2e/max/tests/myTest.tests.ps1
)Implement an input parameter
TestInputData
in the following way:param ( [Parameter(Mandatory = $false)] [hashtable] $TestInputData = @{} )
Through this parameter you can make use of every output the
main.test.bicep
file returns, as well as the path to the test template file in case you want to extract data from it directly.For example, with an output such as
output resourceId string = testDeployment[1].outputs.resourceId
defined in themain.test.bicep
file, the$TestInputData
would look like:$TestInputData = @{ DeploymentOutputs = @{ resourceId = @{ Type = "String" Value = "/subscriptions/***/resourceGroups/dep-***-keyvault.vaults-kvvpe-rg/providers/Microsoft.KeyVault/vaults/***kvvpe001" } } ModuleTestFolderPath = "/home/runner/work/bicep-registry-modules/bicep-registry-modules/avm/res/key-vault/vault/tests/e2e/private-endpoint" }
A full test file may look like:
➕ Pester post-deployment test file example
param ( [Parameter(Mandatory = $false)] [hashtable] $TestInputData = @{} ) Describe 'Validate private endpoint deployment' { Context 'Validate sucessful deployment' { It "Private endpoints should be deployed in resource group" { $keyVaultResourceId = $TestInputData.DeploymentOutputs.resourceId.Value $testResourceGroup = ($keyVaultResourceId -split '\/')[4] $deployedPrivateEndpoints = Get-AzPrivateEndpoint -ResourceGroupName $testResourceGroup $deployedPrivateEndpoints.Count | Should -BeGreaterThan 0 } } }
ID: BCPRMNFR1 - Category: Testing - Expected Test Directories
Module owners MUST create the defaults
, waf-aligned
folders within their /tests/e2e/
directory in their resource module source code and SHOULD create a max
folder also. Module owners CAN create additional folders as required. Each folder will be used as described for various test cases.
Defaults tests (MUST)
The defaults
folder contains a test instance that deploys the module with the minimum set of required parameters.
This includes input parameters of type Required
plus input parameters of type Conditional
marked as required for WAF compliance.
This instance has heavy reliance on the default values for other input parameters. Parameters of type Optional
SHOULD NOT be used.
WAF aligned tests (MUST)
The waf-aligned
folder contains a test instance that deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.
This includes input parameters of type Required
, parameters of type Conditional
marked as required for WAF compliance, and parameters of type Optional
useful for WAF compliance.
Parameters and dependencies which are not needed for WAF compliance, SHOULD NOT be included.
Max tests (SHOULD)
The max
folder contains a test instance that deploys the module using a large parameter set, enabling most of the modules’ features.
The purpose of this instance is primarily parameter validation and not necessarily to serve as a real example scenario. Ideally, all features, extension resources and child resources should be enabled in this test, unless not possible due to conflicts, e.g., in case parameters are mutually exclusive.
Note
Please note that this test is not mandatory to have, but recommended for bulk parameter validation. It can be skipped in case the module parameter validation is covered already by additional, more scenario-specific tests.
Additional tests (CAN)
Additional folders CAN
be created by module owners as required.
For example, to validate parameters not covered by the max
test due to conflicts, or to provide a real example scenario for a specific use case.
If a module can deploy varying styles of the same resource, e.g., VMs can be Linux or Windows, each style should be tested as both defaults
and waf-aligned
. These names should be used as suffixes in the directory name to denote the style, e.g., for a VM we would expect to see:
/tests/e2e/defaults.linux/main.test.bicep
/tests/e2e/waf-aligned.linux/main.test.bicep
/tests/e2e/defaults.windows/main.test.bicep
/tests/e2e/waf-aligned.windows/main.test.bicep
Documentation
The content below is listed based on the following tags
# | ID | Title | Severity | Persona | Lifecycle |
---|---|---|---|---|---|
1 | SNFR15 | Automatic Documentation Generation | MUST | OwnerContributor | BAU |
2 | SNFR16 | Examples/E2E | MUST | OwnerContributor | BAU |
3 | BCPNFR2 | Module Documentation Generation | MUST | OwnerContributor | BAU |
4 | BCPNFR3 | Usage Example formats | MUST | OwnerContributor | BAU |
5 | BCPNFR4 | Parameter Input Examples | MAY | OwnerContributor | BAU |
➕ See Specifications for this category
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.
ID: SNFR16 - Category: Documentation - Examples/E2E
An examples/e2e directory MUST exist to provide named scenarios for module deployment.
ID: BCPNFR2 - Category: Documentation - Module Documentation Generation
Note
This script/tool is currently being developed by the AVM team and will be made available very soon.
Bicep modules documentation MUST be automatically generated via the provided script/tooling from the AVM team, providing the following headings:
- Title
- Description
- Navigation
- Resource Types
- Usage Examples
- Parameters
- Outputs
- Cross-referenced modules
ID: BCPNFR3 - Category: Documentation - Usage Example formats
Usage examples for Bicep modules MUST be provided in the following formats:
Bicep file (orchestration module style) -
.bicep
module <resourceName> 'br/public:avm/[res|ptn|utl]/<publishedModuleName>:>version<' = { name: '${uniqueString(deployment().name, location)}-test-<uniqueIdentifier>' params: { (...) } }
JSON / ARM Template Parameter Files -
.json
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { (...) } }
Note
The above formats are currently automatically taken & generated from the tests/e2e
tests. It is enough to run the Set-ModuleReadMe
or Set-AVMModule
functions (from the utilities
folder) to update the usage examples in the readme(s).
Note
Bicep Parameter Files (.bicepparam
) are being reviewed and considered by the AVM team for the usability and features at this time and will likely be added in the future.
ID: BCPNFR4 - Category: Documentation - Parameter Input Examples
Bicep modules MAY provide parameter input examples for parameters using the metadata.example
property via the @metadata()
decorator.
Example:
@metadata({
example: 'uksouth'
})
@description('Optional. Location for all resources.')
param location string = resourceGroup().location
@metadata({
example: '''
{
keyName: 'myKey'
keyVaultResourceId: '/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/myvault'
keyVersion: '6d143c1a0a6a453daffec4001e357de0'
userAssignedIdentityResourceId '/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/my-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity'
}
'''
})
@description('Optional. The customer managed key definition.')
param customerManagedKey customerManagedKeyType
It is planned that these examples are automatically added to the module readme’s parameter descriptions when running either the Set-ModuleReadMe
or Set-AVMModule
scripts (available in the utilities folder).
Release / Publishing
The content below is listed based on the following tags
# | 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
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 VersionY
== Minor VersionZ
== 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 version1.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 numberX.Y.Z
:X
Major version MUST NOT be bumped.Y
Minor version MUST be bumped when introducing breaking changes (which would normally bump Major after1.0.0
release) or feature updates (same as it will be after1.0.0
release).Z
Patch version MUST be bumped when introducing non-breaking, backward compatible bug fixes (same as it will be after1.0.0
release).
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.
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
- Within the
- Terraform = HashiCorp Terraform Registry
Tip
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.
Code Style
The content below is listed based on the following tags
# | ID | Title | Severity | Persona | Lifecycle |
---|---|---|---|---|---|
1 | BCPNFR8 | Code Styling - lower camelCasing | SHOULD | OwnerContributor | BAU |
2 | BCPNFR17 | Code Styling - Type casting | SHOULD | OwnerContributor | BAU |
➕ See Specifications for this category
ID: BCPNFR8 - Category: Composition - Code Styling - lower camelCasing
Module owners SHOULD use lower camelCasing for naming the following:
- Parameters
- Variables
- Outputs
- User Defined Types
- Resources (symbolic names)
- Modules (symbolic names)
For example: camelCasingExample
(lowercase first word (entirely), with capital of first letter of all other words and rest of word in lowercase)
ID: BCPNFR17 - Category: Composition - Code Styling - Type casting
To improve the usability of primitive module properties declared as strings, you SHOULD declare them using a type which better represents them, and apply any required casting in the module on behalf of the user.
For reference, please refer to the following examples:
Boolean as String
@allowed([
'false'
'true'
])
param myParameterValue string = 'false'
resource myResource '(...)' = {
(...)
properties: {
myParameter: myParameterValue
}
}
param myParameterValue string = false
resource myResource '(...)' = {
(...)
properties: {
myParameter: string(myParameterValue)
}
}
Integer Array as String Array
@allowed([
'1'
'2'
'3'
])
param zones array
resource myResource '(...)' = {
(...)
properties: {
zones: zones
}
}
@allowed([
1
2
3
])
param zones int[]
resource myResource '(...)' = {
(...)
properties: {
zones: map(zones, zone => string(zone))
}
}