If you cannot find guidance for what you need, please let us know via GitHub Issues π
Subsections of Contributing
Bicep Contribution Guide
Important
While this page describes and summarizes important aspects of contributing to AVM, it may not reference All of the shared and language specific requirements.
Therefore, this contribution guide MUST be used in conjunction with the Bicep specifications. ALL AVM modules (Resource and Pattern modules) MUST meet the respective requirements described in these specifications!
Summary
This section lists AVM’s Bicep-specific contribution guidance.
While this page describes and summarizes important aspects of the composition of AVM modules, it may not reference All of the shared and language specific requirements.
Therefore, this guide MUST be used in conjunction with the Bicep specifications. ALL AVM modules (Resource and Pattern modules) MUST meet the respective requirements described in these specifications!
Important
Before jumping on implementing your contribution, please review the AVM Module specifications, in particular the Bicep specification page, to make sure your contribution complies with the AVM module’s design and principles.
For new modules, the files can be created automatically, once the parent folder exists. This example shows how to create a res module res/compute/virtual-machine.
Modules enable you to reuse code from a Bicep file in other Bicep files. As such, for resource modules they’re normally leveraged for deploying child resources (e.g., file services in a storage account), cross referenced resources (e.g., network interface in a virtual machine) or extension resources (e.g., role assignments in a key vault). Pattern modules, normally reuse resource modules combined together.
Make sure to review all specifications covering module properties and usage.
Tip
See examples in specifications BCPFR1 for resource modules and PMNFR2 for pattern modules.
Outputs
Make sure to review all specifications of Category: Inputs/Outputs within the Bicep specific pages.
This section is only relevant for contributions to resource modules.
To meet RMFR4 and RMFR5 AVM resource modules must leverage consistent interfaces for all the optional features/extension resources supported by the AVM module primary resource.
Please refer to the Bicep Interfaces page. If the primary resource of the AVM resource module you are developing supports any of the listed features/extension resources, please follow the corresponding provided Bicep schema to develop them.
Deprecation
Breaking changes are sometimes not avoidable. The impact should be kept as low as possible. A recommendation is to deprecate parameters, instead of completely removing them for a couple of versions. The Semantic Versioning sections offers information about versioning AVM modules.
In case you need to deprecate an input parameter, this sample shows you how this can be achieved.
Note
Since all modules are versioned, nothing will change for existing deployments, as the parameter usage does not change for any existing versions.
Example-Scenario
An AVM module is modified, and the parameters will change, which breaks backward compatibility.
parameters are changing to a custom type
the parameter structure is changing
backward compatibility will be maintained
Existing input parameters used to be defined as follows (reducing the examples to the minimum):
Before you begin to modify anything, it is recommended to create a new test case (e.g. deprecated), in addition to the already existing tests, to make sure that the changes are not breaking backward compatibility until you decide to finally remove the deprecated parameters (see BCPRMNFR1 - Category: Testing - Expected Test Directories for more details about the requirements).
The test should include all previously used parameters to make sure they are covered before any changes to the new parameter layout are done.
Code Changes
The new parameter structure requires a change to the used parameters and moves them to a different location and looks like:
// main.bicep:param item itemType?
type itemtype: {
name: string // the name parameter did not change properties ={
osType: 'Linux' | 'Windows'? // the new place for the osType variant: {
size: string? // the new place for the variant size }?
}
// keep these for backward compatibility in the new type @description('Optional. Note: This is a deprecated property, please use the corresponding `properties.osType` instead.')
osType: string? // the old parameter location @description('Optional. Note: This is a deprecated property, please use the corresponding `properties.variant.size` instead.')
variant: string? // the old parameter location}
The original parameter item is of type object and does not give the user any clue of what the syntax is and what is expected to be added to it. The tests could bring light into the darkness, but this is not ideal. In order to retain backward compatibility, the previously used parameters need to be added to the new type, as they would be invalid otherwise. Now that the new type is in place, some logic needs to be implemented to make sure the module can handle the different sources of data (new and old parameters).
resource<modulename>'Microsoft.xy/yz@2024-01-01' = {
name: name
properties: {
osType: item.?properties.?osType ?? item.?osType ??'Linux'// add a default here, if needed variant: {
size: item.?properties.?variant.?size ?? item.?variant
}
}
}
By choosing this order for the Coalesce operator, the new format takes precedence over the old syntax. Also note the safe-dereference ensures that no null reference exception will occure if the property has optional parameters.
The tests can now be changed to adapt the new parameter structure for the new version of the module. They will not cover the old parameter structure anymore.
Changes to modules (resource or pattern) can bei implemented in two ways.
Implement changes with backward compatibility
In this scenario, you need to make sure that the code does not break backward compatibility by:
adding new parameters
marking other parameters as deprecated
create a test case for the old usage syntax
increase the minor version number of the module (0.x)
Introduce breaking changes
The easier way to introduce a new major version requires fewer steps:
adding new parameters
create a test case for the usage
increase the major version number of the module (x.0.0)
Note
Be aware that currently no module has been released as 1.0.0 (or beyond), which lets you implement breaking changes without increasing the major version.
Bicep Contribution Flow
High-level contribution flow
---
config:
nodeSpacing: 20
rankSpacing: 20
diagramPadding: 50
padding: 5
flowchart:
wrappingWidth: 300
padding: 5
layout: elk
elk:
mergeEdges: true
nodePlacementStrategy: LINEAR_SEGMENTS
---
flowchart TD
A("1 - Fork the module source repository")
click A "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#1-fork-the-module-source-repository"
B(2 - Configure a deployment identity in Azure)
click B "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#2-configure-a-deployment-identity-in-azure"
C("3 - Configure CI environment for module tests")
click C "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#3-configure-your-ci-environment"
D("4 - Implementing your contribution<br>(Refer to Gitflow Diagram below)")
click D "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#4-implement-your-contribution"
E(5 - Workflow test completed successfully?)
click E "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#5-createupdate-and-run-tests"
F(6 - Create a pull request to the upstream repository)
click F "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#6-create-a-pull-request-to-the-public-bicep-registry"
G(7 - Get your pull request approved)
click G "/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#7-get-your-pull-request-approved"
A --> B
B --> C
C --> D
D --> E
E -->|yes|F
E -->|no|D
F --> G
GitFlow for contributors
The GitFlow process outlined here introduces a central anchor branch. This branch should be treated as if it were a protected branch. It serves to synchronize the forked repository with the original upstream repository. The use of the anchor branch is designed to give contributors the flexibility to work on several modules simultaneous.
When implementing the GitFlow process as described, it is advisable to configure the local clone with a remote for the upstream repository. This will enable the Git CLI and local IDE to merge changes directly from the upstream repository. Using GitHub Desktop, this is configured automatically when cloning the forked repository via the application.
PowerShell Helper Script To Setup Fork & CI Test Environment
Now defaults to OIDC setup
The PowerShell Helper Script has recently added support for the OIDC setup and configuration as documented in detail on this page. This is now the default for the script.
The easiest way to get yourself set back up, is to delete your fork repository, including the local clone of it that you have and start over with the script. This will ensure you have the correct setup for the OIDC authentication method for the AVM CI.
Important
To simplify the setup of the fork, clone and configuration of the required GitHub Environments, Secrets, User-Assigned Managed Identity (UAMI), Federated Credentials and RBAC assignments in your Azure environment for the CI framework to function correctly in your fork, we have created a PowerShell script that you can use to do steps 1, 2 & 3 below.
The script performs the following steps:
Forks the Azure/bicep-registry-modules to your GitHub Account.
Clones the repo locally to your machine, based on the location you specify in the parameter: -GitHubRepositoryPathForCloneOfForkedRepository.
Prompts you and takes you directly to the place where you can enable GitHub Actions Workflows on your forked repo.
Creates an User-Assigned Managed Identity (UAMI) and federated credentials for OIDC with your forked GitHub repo and grants it the RBAC roles of Owner at Management Group level, if specified in the -GitHubSecret_ARM_MGMTGROUP_ID parameter, and at Azure Subscription level if you provide it via the -GitHubSecret_ARM_SUBSCRIPTION_ID parameter.
Creates the required GitHub Environments & required Secrets in your forked repo as per step 3, based on the input provided in parameters and the values from resources the script creates and configures for OIDC. Also set the workflow permissions to Read and write permissions as per step 3.3.
Pre-requisites
You must have the Azure PowerShell Modules installed and you need to be logged with the context set to the desired Tenant. You must have permissions to create an SPN and grant RBAC over the specified Subscription and Management Group, if provided.
You must have the GitHub CLI installed and need to be authenticated with the GitHub user account you wish to use to fork, clone and work with on AVM.
The New-AVMBicepBRMForkSetup.ps1 can be downloaded from here.
Once downloaded, you can run the script by running the below - Please change all the parameter values in the below script usage example to your own values (see the parameter documentation in the script itself)!:
.\<PATH-TO-SCRIPT-DOWNLOAD-LOCATION>\New-AVMBicepBRMForkSetup.ps1 -GitHubRepositoryPathForCloneOfForkedRepository "<pathToCreateForkedRepoIn>" -GitHubSecret_ARM_MGMTGROUP_ID "<managementGroupId>" -GitHubSecret_ARM_SUBSCRIPTION_ID "<subscriptionId>" -GitHubSecret_ARM_TENANT_ID "<tenantId>" -GitHubSecret_TOKEN_NAMEPREFIX "<unique3to5AlphanumericStringForAVMDeploymentNames>" -UAMIRsgLocation "<Azure Region/Location of your choice such as 'uksouth'>"
For more examples, see the below script’s parameters section.
ο»Ώ[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "Coloured output required in this script")]
#Requires-PSEdition Core#Requires-Modules @{ ModuleName="Az.Accounts"; ModuleVersion="2.19.0" }#Requires-Modules @{ ModuleName="Az.Resources"; ModuleVersion="6.16.2" }<#
.SYNOPSISThis function creates and sets up everything a contributor to the AVM Bicep project should need to get started with their contribution to a AVM Bicep Module.
.DESCRIPTIONThis function creates and sets up everything a contributor to the AVM Bicep project should need to get started with their contribution to a AVM Bicep Module. This includes:
- Forking and cloning the `Azure/bicep-registry-modules` repository
- Creating a new SPN and granting it the necessary permissions for the CI tests and configuring the forked repositories secrets, as per: https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#2-configure-a-deployment-identity-in-azure
- Enabling GitHub Actions on the forked repository
- Disabling all the module workflows by default, as per: https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/enable-or-disable-workflows/
Effectively simplifying this process to a single command, https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/
.PARAMETER GitHubRepositoryPathForCloneOfForkedRepository
Mandatory. The path to the GitHub repository to fork and clone. Directory will be created if does not already exist. Can use either relative paths or full literal paths.
.PARAMETER GitHubSecret_ARM_MGMTGROUP_ID
Optional. The group ID of the management group to test-deploy modules in. Is needed for resources that are deployed to the management group scope. If not provided CI tests on Management Group scoped modules will not work and you will need to manually configure the RBAC role assignments for the SPN and associated repository secret later.
.PARAMETER GitHubSecret_ARM_SUBSCRIPTION_ID
Mandatory. The ID of the subscription to test-deploy modules in. Is needed for resources that are deployed to the subscription scope.
.PARAMETER GitHubSecret_ARM_TENANT_ID
Mandatory. The tenant ID of the Azure Active Directory tenant to test-deploy modules in. Is needed for resources that are deployed to the tenant scope.
.PARAMETER GitHubSecret_TOKEN_NAMEPREFIX
Mandatory. Required. A short (3-5 character length), unique string that should be included in any deployment to Azure. Usually, AVM Bicep test cases require this value to ensure no two contributors deploy resources with the same name - which is especially important for resources that require a globally unique name (e.g., Key Vault). These characters will be used as part of each resourceβs name during deployment.
.PARAMETER SPNName
Optional. The name of the SPN (Service Principal) to create. If not provided, a default name of `spn-avm-bicep-brm-fork-ci-<GitHub Organization>` will be used.
.PARAMETER UAMIName
Optional. The name of the UAMI (User Assigned Managed Identity) to create. If not provided, a default name of `id-avm-bicep-brm-fork-ci-<GitHub Organization>` will be used.
.PARAMETER UAMIRsgName
Optional. The name of the Resource Group to create for the UAMI (User Assigned Managed Identity) to create. If not provided, a default name of `rsg-avm-bicep-brm-fork-ci-<GitHub Organization>-oidc` will be used.
.PARAMETER UAMIRsgLocation
Optional. The location of the Resource Group to create for the UAMI (User Assigned Managed Identity) to create. Also UAMI will be created in this location. This is required for OIDC deployments.
.PARAMETER UseOIDC
Optional. Default is `$true`. If set to `$true`, the script will use the OIDC (OpenID Connect) authentication method for the SPN instead of secrets as per https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#31-set-up-secrets. If set to `$false`, the script will use the Client Secret authentication method for the SPN and not OIDC.
.EXAMPLE.\<PATH-TO-SCRIPT-DOWNLOAD-LOCATION>\New-AVMBicepBRMForkSetup.ps1 -GitHubRepositoryPathForCloneOfForkedRepository "D:\GitRepos\" -GitHubSecret_ARM_MGMTGROUP_ID "alz" -GitHubSecret_ARM_SUBSCRIPTION_ID "1b60f82b-d28e-4640-8cfa-e02d2ddb421a" -GitHubSecret_ARM_TENANT_ID "c3df6353-a410-40a1-b962-e91e45e14e4b" -GitHubSecret_TOKEN_NAMEPREFIX "ex123" -UAMIRsgLocation "uksouth"
Example Subscription & Management Group scoped deployments enabled via OIDC with default generated UAMI Resource Group name of `rsg-avm-bicep-brm-fork-ci-<GitHub Organization>-oidc` and UAMI name of `id-avm-bicep-brm-fork-ci-<GitHub Organization>`.
.EXAMPLE.\<PATH-TO-SCRIPT-DOWNLOAD-LOCATION>\New-AVMBicepBRMForkSetup.ps1 -GitHubRepositoryPathForCloneOfForkedRepository "D:\GitRepos\" -GitHubSecret_ARM_MGMTGROUP_ID "alz" -GitHubSecret_ARM_SUBSCRIPTION_ID "1b60f82b-d28e-4640-8cfa-e02d2ddb421a" -GitHubSecret_ARM_TENANT_ID "c3df6353-a410-40a1-b962-e91e45e14e4b" -GitHubSecret_TOKEN_NAMEPREFIX "ex123" -UAMIRsgLocation "uksouth" -UAMIName "my-uami-name" -UAMIRsgName "my-uami-rsg-name"
Example with provided UAMI Name & UAMI Resource Group Name.
.EXAMPLE.\<PATH-TO-SCRIPT-DOWNLOAD-LOCATION>\New-AVMBicepBRMForkSetup.ps1 -GitHubRepositoryPathForCloneOfForkedRepository "D:\GitRepos\" -GitHubSecret_ARM_SUBSCRIPTION_ID "1b60f82b-d28e-4640-8cfa-e02d2ddb421a" -GitHubSecret_ARM_TENANT_ID "c3df6353-a410-40a1-b962-e91e45e14e4b" -GitHubSecret_TOKEN_NAMEPREFIX "ex123" -UseOIDC $false
DEPRECATED - USE OIDC INSTEAD.
Example Subscription scoped deployments enabled only with default generated SPN name of `spn-avm-bicep-brm-fork-ci-<GitHub Organization>`.
.EXAMPLE.\<PATH-TO-SCRIPT-DOWNLOAD-LOCATION>\New-AVMBicepBRMForkSetup.ps1 -GitHubRepositoryPathForCloneOfForkedRepository "D:\GitRepos\" -GitHubSecret_ARM_MGMTGROUP_ID "alz" -GitHubSecret_ARM_SUBSCRIPTION_ID "1b60f82b-d28e-4640-8cfa-e02d2ddb421a" -GitHubSecret_ARM_TENANT_ID "c3df6353-a410-40a1-b962-e91e45e14e4b" -GitHubSecret_TOKEN_NAMEPREFIX "ex123" -SPNName "my-spn-name" -UseOIDC $false
DEPRECATED - USE OIDC INSTEAD.
Example with provided SPN name.
#>[CmdletBinding(SupportsShouldProcess = $false)]
param (
[Parameter(Mandatory = $true)]
[string] $GitHubRepositoryPathForCloneOfForkedRepository,
[Parameter(Mandatory = $false)]
[string] $GitHubSecret_ARM_MGMTGROUP_ID,
[Parameter(Mandatory = $true)]
[string] $GitHubSecret_ARM_SUBSCRIPTION_ID,
[Parameter(Mandatory = $true)]
[string] $GitHubSecret_ARM_TENANT_ID,
[Parameter(Mandatory = $true)]
[string] $GitHubSecret_TOKEN_NAMEPREFIX,
[Parameter(Mandatory = $false)]
[string] $SPNName,
[Parameter(Mandatory = $false)]
[string] $UAMIName,
[Parameter(Mandatory = $false)]
[string] $UAMIRsgName = "rsg-avm-bicep-brm-fork-ci-oidc",
[Parameter(Mandatory = $false)]
[string] $UAMIRsgLocation,
[Parameter(Mandatory = $false)]
[bool] $UseOIDC = $true
)
# 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. Install link for GitHub CLI: https://github.com/cli/cli#installation'}
Write-Host 'The GitHub CLI is installed...' -ForegroundColor Green
# Check if GitHub CLI is authenticated$GitHubCliAuthenticated = gh auth status
if ($LASTEXITCODE -ne0) {
Write-Host $GitHubCliAuthenticated -ForegroundColor Red
throw"Not authenticated to GitHub. Please authenticate to GitHub using the GitHub CLI command of 'gh auth login', and try again."}
Write-Host 'Authenticated to GitHub with following details...' -ForegroundColor Cyan
Write-Host ''gh auth status
Write-Host ''# Ask the user to confirm if it's the correct GitHub accountdo {
Write-Host "Is the above GitHub account correct to coninue with the fork setup of the 'Azure/bicep-registry-modules' repository? Please enter 'y' or 'n'." -ForegroundColor Yellow
$userInput = Read-Host
$userInput = $userInput.ToLower()
switch ($userInput) {
'y' {
Write-Host '' Write-Host 'User Confirmed. Proceeding with the GitHub account listed above...' -ForegroundColor Green
Write-Host ''break }
'n' {
Write-Host ''throw"User stated incorrect GitHub account. Please switch to the correct GitHub account. You can do this in the GitHub CLI (gh) by logging out by running 'gh auth logout' and then logging back in with 'gh auth login'" }
default {
Write-Host '' Write-Host "Invalid input. Please enter 'y' or 'n'." -ForegroundColor Red
Write-Host '' }
}
} while ($userInput -ne'y'-and $userInput -ne'n')
# Fork and clone repository locallyWrite-Host "Changing to directory $GitHubRepositoryPathForCloneOfForkedRepository ..." -ForegroundColor Magenta
if (-not (Test-Path -Path $GitHubRepositoryPathForCloneOfForkedRepository)) {
Write-Host "Directory does not exist. Creating directory $GitHubRepositoryPathForCloneOfForkedRepository ..." -ForegroundColor Yellow
New-Item -Path $GitHubRepositoryPathForCloneOfForkedRepository -ItemType Directory -ErrorAction Stop
Write-Host ''}
Set-Location -Path $GitHubRepositoryPathForCloneOfForkedRepository -ErrorAction stop
$CreatedDirectoryLocation = Get-Location
Write-Host "Forking and cloning 'Azure/bicep-registry-modules' repository..." -ForegroundColor Magenta
gh repo fork 'Azure/bicep-registry-modules' --default-branch-only --clone=true
if ($LASTEXITCODE -ne0) {
throw"Failed to fork and clone the 'Azure/bicep-registry-modules' repository. Please check the error message above, resolve any issues, and try again."}
$ClonedRepoDirectoryLocation = Join-Path $CreatedDirectoryLocation 'bicep-registry-modules'Write-Host ''Write-Host "Fork of 'Azure/bicep-registry-modules' created successfully directory in $CreatedDirectoryLocation ..." -ForegroundColor Green
Write-Host ''Write-Host "Changing into cloned repository directory $ClonedRepoDirectoryLocation ..." -ForegroundColor Magenta
Set-Location $ClonedRepoDirectoryLocation -ErrorAction stop
# Check is user is logged in to Azure$UserLoggedIntoAzure = Get-AzContext -ErrorAction SilentlyContinue
if ($null -eq $UserLoggedIntoAzure) {
throw'You are not logged into Azure. Please log into Azure using the Azure PowerShell module using the command of `Connect-AzAccount` to the correct tenant and try again.'}
$UserLoggedIntoAzureJson = $UserLoggedIntoAzure | ConvertTo-Json -Depth 10 | ConvertFrom-Json
Write-Host "You are logged into Azure as '$($UserLoggedIntoAzureJson.Account.Id)' ..." -ForegroundColor Green
# Check user has access to desired subscription$UserCanAccessSubscription = Get-AzSubscription -SubscriptionId $GitHubSecret_ARM_SUBSCRIPTION_ID -ErrorAction SilentlyContinue
if ($null -eq $UserCanAccessSubscription) {
throw"You do not have access to the subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)'. Please ensure you have access to the subscription and try again."}
Write-Host "You have access to the subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)' ..." -ForegroundColor Green
Write-Host ''# Get GitHub Login/Org Name$GitHubUserRaw = gh api user
$GitHubUserConvertedToJson = $GitHubUserRaw | ConvertFrom-Json -Depth 10$GitHubOrgName = $GitHubUserConvertedToJson.login
$GitHubOrgAndRepoNameCombined = "$($GitHubOrgName)/bicep-registry-modules"# Create SPN if not using OIDCif ($UseOIDC -eq $false) {
if ($SPNName -eq'') {
Write-Host "No value provided for the SPN Name. Defaulting to 'spn-avm-bicep-brm-fork-ci-<GitHub Organization>' ..." -ForegroundColor Yellow
$SPNName = "spn-avm-bicep-brm-fork-ci-$($GitHubOrgName)" }
$newSpn = New-AzADServicePrincipal -DisplayName $SPNName -Description "Service Principal Name (SPN) for the AVM Bicep CI Tests in the $($GitHubOrgName) fork. See: https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/#2-configure-a-deployment-identity-in-azure" -ErrorAction Stop
Write-Host "New SPN created with a Display Name of '$($newSpn.DisplayName)' and an Object ID of '$($newSpn.Id)'." -ForegroundColor Green
Write-Host ''# Create RBAC Role Assignments for SPN Write-Host 'Starting 120 second sleep to allow the SPN to be created and available for RBAC Role Assignments (eventual consistency) ...' -ForegroundColor Yellow
Start-Sleep -Seconds 120 Write-Host "Creating RBAC Role Assignments of 'Owner' for the Service Principal Name (SPN) '$($newSpn.DisplayName)' on the Subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)' ..." -ForegroundColor Magenta
New-AzRoleAssignment -ApplicationId $newSpn.AppId -RoleDefinitionName 'Owner' -Scope "/subscriptions/$($GitHubSecret_ARM_SUBSCRIPTION_ID)" -ErrorAction Stop
Write-Host "RBAC Role Assignments of 'Owner' for the Service Principal Name (SPN) '$($newSpn.DisplayName)' created successfully on the Subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)'." -ForegroundColor Green
Write-Host ''if ($GitHubSecret_ARM_MGMTGROUP_ID -eq'') {
Write-Host "No Management Group ID provided as input parameter to '-GitHubSecret_ARM_MGMTGROUP_ID', skipping RBAC Role Assignments upon Management Groups" -ForegroundColor Yellow
Write-Host '' }
if ($GitHubSecret_ARM_MGMTGROUP_ID -ne'') {
Write-Host "Creating RBAC Role Assignments of 'Owner' for the Service Principal Name (SPN) '$($newSpn.DisplayName)' on the Management Group with the ID of '$($GitHubSecret_ARM_MGMTGROUP_ID)' ..." -ForegroundColor Magenta
New-AzRoleAssignment -ApplicationId $newSpn.AppId -RoleDefinitionName 'Owner' -Scope "/providers/Microsoft.Management/managementGroups/$($GitHubSecret_ARM_MGMTGROUP_ID)" -ErrorAction Stop
Write-Host "RBAC Role Assignments of 'Owner' for the Service Principal Name (SPN) '$($newSpn.DisplayName)' created successfully on the Management Group with the ID of '$($GitHubSecret_ARM_MGMTGROUP_ID)'." -ForegroundColor Green
Write-Host '' }
}
# Create UAMI if using OIDCif ($UseOIDC) {
if ($UAMIName -eq'') {
Write-Host "No value provided for the UAMI Name. Defaulting to 'id-avm-bicep-brm-fork-ci-<GitHub Organization>' ..." -ForegroundColor Yellow
$UAMIName = "id-avm-bicep-brm-fork-ci-$($GitHubOrgName)" }
if ($UAMIRsgName -eq'') {
Write-Host "No value provided for the UAMI Resource Group Name. Defaulting to 'rsg-avm-bicep-brm-fork-ci-<GitHub Organization>-oidc' ..." -ForegroundColor Yellow
$UAMIRsgName = "rsg-avm-bicep-brm-fork-ci-$($GitHubOrgName)-oidc" }
Write-Host "Selecting the subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)' to create Resource Group & UAMI in for OIDC ..." -ForegroundColor Magenta
Select-AzSubscription -Subscription $GitHubSecret_ARM_SUBSCRIPTION_ID
Write-Host ''if ($UAMIRsgLocation -eq'') {
Write-Host "No value provided for the UAMI Location ..." -ForegroundColor Yellow
$UAMIRsgLocation = Read-Host -Prompt "Please enter the location for the UAMI and the Resource Group to be created in for OIDC deployments. e.g. 'uksouth' or 'eastus', etc..." $UAMIRsgLocation = $UAMIRsgLocation.ToLower()
$availableLocations = Get-AzLocation | Where-Object {$_.RegionType -eq'Physical'} | Select-Object -ExpandProperty Location
if ($availableLocations -notcontains $UAMIRsgLocation) {
Write-Host "Invalid location provided. Please provide a valid location from the list below ..." -ForegroundColor Yellow
Write-Host '' Write-Host "Available Locations: $($availableLocations -join ', ')" -ForegroundColor Yellow
do {
$UAMIRsgLocation = Read-Host -Prompt "Please enter the location for the UAMI and the Resource Group to be created in for OIDC deployments. e.g. 'uksouth' or 'eastus', etc..." } until (
$availableLocations -icontains $UAMIRsgLocation
)
}
}
Write-Host "Creating Resource Group for UAMI with the name of '$($UAMIRsgName)' and location of '$($UAMIRsgLocation)'..." -ForegroundColor Magenta
$newUAMIRsg = New-AzResourceGroup -Name $UAMIRsgName -Location $UAMIRsgLocation -ErrorAction Stop
Write-Host "New Resource Group created with a Name of '$($newUAMIRsg.ResourceGroupName)' and a Location of '$($newUAMIRsg.Location)'." -ForegroundColor Green
Write-Host '' Write-Host "Creating UAMI with the name of '$($UAMIName)' and location of '$($UAMIRsgLocation)' in the Resource Group with the name of '$($UAMIRsgName)..." -ForegroundColor Magenta
$newUAMI = New-AzUserAssignedIdentity -ResourceGroupName $newUAMIRsg.ResourceGroupName -Name $UAMIName -Location $newUAMIRsg.Location -ErrorAction Stop
Write-Host "New UAMI created with a Name of '$($newUAMI.Name)' and an Object ID of '$($newUAMI.PrincipalId)'." -ForegroundColor Green
Write-Host '' Write-Host 'Starting 120 second sleep to allow the UAMI to be created and available for Federated Credential creation and RBAC Role Assignments (eventual consistency) ...' -ForegroundColor Yellow
Start-Sleep -Seconds 120# Create Federated Credentials for UAMI for OIDC Write-Host "Creating Federated Credentials for the User-Assigned Managed Identity Name (UAMI) for OIDC ... '$($newUAMI.Name)' for OIDC ..." -ForegroundColor Magenta
New-AzFederatedIdentityCredentials -ResourceGroupName $newUAMIRsg.ResourceGroupName -IdentityName $newUAMI.Name -Name 'avm-gh-env-validation' -Issuer "https://token.actions.githubusercontent.com" -Subject "repo:$($GitHubOrgAndRepoNameCombined):environment:avm-validation" -ErrorAction Stop
Write-Host ''# Create RBAC Role Assignments for UAMI Write-Host "Creating RBAC Role Assignments of 'Owner' for the User-Assigned Managed Identity Name (UAMI) '$($newUAMI.Name)' on the Subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)' ..." -ForegroundColor Magenta
New-AzRoleAssignment -ObjectId $newUAMI.PrincipalId -RoleDefinitionName 'Owner' -Scope "/subscriptions/$($GitHubSecret_ARM_SUBSCRIPTION_ID)" -ErrorAction Stop
Write-Host "RBAC Role Assignments of 'Owner' for the User-Assigned Managed Identity Name (UAMI) '$($newUAMI.Name)' created successfully on the Subscription with the ID of '$($GitHubSecret_ARM_SUBSCRIPTION_ID)'." -ForegroundColor Green
Write-Host ''if ($GitHubSecret_ARM_MGMTGROUP_ID -eq'') {
Write-Host "No Management Group ID provided as input parameter to '-GitHubSecret_ARM_MGMTGROUP_ID', skipping RBAC Role Assignments upon Management Groups" -ForegroundColor Yellow
Write-Host '' }
if ($GitHubSecret_ARM_MGMTGROUP_ID -ne'') {
Write-Host "Creating RBAC Role Assignments of 'Owner' for the User-Assigned Managed Identity Name (UAMI) '$($newSpn.DisplayName)' on the Management Group with the ID of '$($GitHubSecret_ARM_MGMTGROUP_ID)' ..." -ForegroundColor Magenta
New-AzRoleAssignment -ObjectId $newUAMI.PrincipalId -RoleDefinitionName 'Owner' -Scope "/providers/Microsoft.Management/managementGroups/$($GitHubSecret_ARM_MGMTGROUP_ID)" -ErrorAction Stop
Write-Host "RBAC Role Assignments of 'Owner' for the User-Assigned Managed Identity Name (UAMI) '$($newUAMI.Name)' created successfully on the Management Group with the ID of '$($GitHubSecret_ARM_MGMTGROUP_ID)'." -ForegroundColor Green
Write-Host '' }
}
# Set GitHub Repo Secrets (non-OIDC)if ($UseOIDC -eq $false) {
Write-Host "Setting GitHub Secrets on forked repository (non-OIDC) '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Magenta
Write-Host 'Creating and formatting secret `AZURE_CREDENTIALS` with details from SPN creation process (non-OIDC) and other parameter inputs ...' -ForegroundColor Cyan
$FormattedAzureCredentialsSecret = "{ 'clientId': '$($newSpn.AppId)', 'clientSecret': '$($newSpn.PasswordCredentials.SecretText)', 'subscriptionId': '$($GitHubSecret_ARM_SUBSCRIPTION_ID)', 'tenantId': '$($GitHubSecret_ARM_TENANT_ID)' }" $FormattedAzureCredentialsSecretJsonCompressed = $FormattedAzureCredentialsSecret | ConvertFrom-Json | ConvertTo-Json -Compress
if ($GitHubSecret_ARM_MGMTGROUP_ID -ne'') {
gh secret set ARM_MGMTGROUP_ID --body $GitHubSecret_ARM_MGMTGROUP_ID -R $GitHubOrgAndRepoNameCombined
}
gh secret set ARM_SUBSCRIPTION_ID --body $GitHubSecret_ARM_SUBSCRIPTION_ID -R $GitHubOrgAndRepoNameCombined
gh secret set ARM_TENANT_ID --body $GitHubSecret_ARM_TENANT_ID -R $GitHubOrgAndRepoNameCombined
gh secret set AZURE_CREDENTIALS --body $FormattedAzureCredentialsSecretJsonCompressed -R $GitHubOrgAndRepoNameCombined
gh secret set TOKEN_NAMEPREFIX --body $GitHubSecret_TOKEN_NAMEPREFIX -R $GitHubOrgAndRepoNameCombined
Write-Host '' Write-Host "Successfully created and set GitHub Secrets (non-OIDC) on forked repository '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Green
Write-Host ''}
# Set GitHub Repo Secrets & Environment (OIDC)if ($UseOIDC) {
Write-Host "Setting GitHub Environment (avm-validation) and required Secrets on forked repository (OIDC) '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Magenta
Write-Host "Creating 'avm-validation' environment on forked repository' ..." -ForegroundColor Cyan
$GitHubEnvironment = gh api --method PUT -H "Accept: application/vnd.github+json""repos/$($GitHubOrgAndRepoNameCombined)/environments/avm-validation" $GitHubEnvironmentConvertedToJson = $GitHubEnvironment | ConvertFrom-Json -Depth 10if ($GitHubEnvironmentConvertedToJson.name -ne'avm-validation') {
throw"Failed to create 'avm-validation' environment on forked repository. Please check the error message above, resolve any issues, and try again." }
Write-Host "Successfully created 'avm-validation' environment on forked repository' ..." -ForegroundColor Green
Write-Host '' Write-Host "Creating and formatting secrets for 'avm-validation' environment with details from UAMI creation process (OIDC) and other parameter inputs ..." -ForegroundColor Cyan
gh secret set VALIDATE_CLIENT_ID --body $newUAMI.ClientId -R $GitHubOrgAndRepoNameCombined -e 'avm-validation' gh secret set VALIDATE_SUBSCRIPTION_ID --body $GitHubSecret_ARM_SUBSCRIPTION_ID -R $GitHubOrgAndRepoNameCombined -e 'avm-validation' gh secret set VALIDATE_TENANT_ID --body $GitHubSecret_ARM_TENANT_ID -R $GitHubOrgAndRepoNameCombined -e 'avm-validation' Write-Host "Creating and formatting secrets for repo with details from UAMI creation process (OIDC) and other parameter inputs ..." -ForegroundColor Cyan
if ($GitHubSecret_ARM_MGMTGROUP_ID -ne'') {
gh secret set ARM_MGMTGROUP_ID --body $GitHubSecret_ARM_MGMTGROUP_ID -R $GitHubOrgAndRepoNameCombined
}
gh secret set ARM_SUBSCRIPTION_ID --body $GitHubSecret_ARM_SUBSCRIPTION_ID -R $GitHubOrgAndRepoNameCombined
gh secret set ARM_TENANT_ID --body $GitHubSecret_ARM_TENANT_ID -R $GitHubOrgAndRepoNameCombined
gh secret set TOKEN_NAMEPREFIX --body $GitHubSecret_TOKEN_NAMEPREFIX -R $GitHubOrgAndRepoNameCombined
Write-Host '' Write-Host "Successfully created and set GitHub Secrets in 'avm-validation' environment and repo (OIDC) on forked repository '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Green
Write-Host ''}
Write-Host "Opening browser so you can enable GitHub Actions on newly forked repository '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Magenta
Write-Host "Please select click on the green button stating 'I understand my workflows, go ahead and enable them' to enable actions/workflows on your forked repository via the website that has appeared in your browser window and then return to this terminal session to continue ..." -ForegroundColor Yellow
Start-Process "https://github.com/$($GitHubOrgAndRepoNameCombined)/actions" -ErrorAction Stop
Write-Host ''$GitHubWorkflowPlatformToggleWorkflows = '.Platform - Toggle AVM workflows'$GitHubWorkflowPlatformToggleWorkflowsFileName = 'platform.toggle-avm-workflows.yml'do {
Write-Host "Did you successfully enable the GitHub Actions/Workflows on your forked repository '$($GitHubOrgAndRepoNameCombined)'? Please enter 'y' or 'n'." -ForegroundColor Yellow
$userInput = Read-Host
$userInput = $userInput.ToLower()
switch ($userInput) {
'y' {
Write-Host '' Write-Host "User Confirmed. Proceeding to trigger workflow of '$($GitHubWorkflowPlatformToggleWorkflows)' to disable all workflows as per: https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/enable-or-disable-workflows/..." -ForegroundColor Green
Write-Host ''break }
'n' {
Write-Host '' Write-Host 'User stated no. Ending script here. Please review and complete any of the steps you have not completed, likely just enabling GitHub Actions/Workflows on your forked repository and then disabling all workflows as per: https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/enable-or-disable-workflows/' -ForegroundColor Yellow
exit
}
default {
Write-Host '' Write-Host "Invalid input. Please enter 'y' or 'n'." -ForegroundColor Red
Write-Host '' }
}
} while ($userInput -ne'y'-and $userInput -ne'n')
Write-Host "Setting Read/Write Workflow permissions on forked repository '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Magenta
gh api --method PUT -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28""/repos/$($GitHubOrgAndRepoNameCombined)/actions/permissions/workflow"-f"default_workflow_permissions=write"Write-Host ''Write-Host "Triggering '$($GitHubWorkflowPlatformToggleWorkflows) on '$($GitHubOrgAndRepoNameCombined)' ..." -ForegroundColor Magenta
Write-Host ''gh workflow run $GitHubWorkflowPlatformToggleWorkflows -R $GitHubOrgAndRepoNameCombined
Write-Host ''Write-Host 'Starting 120 second sleep to allow the workflow run to complete ...' -ForegroundColor Yellow
Start-Sleep -Seconds 120Write-Host ''Write-Host "Workflow '$($GitHubWorkflowPlatformToggleWorkflows) on '$($GitHubOrgAndRepoNameCombined)' should have now completed, opening workflow in browser so you can check ..." -ForegroundColor Magenta
Start-Process "https://github.com/$($GitHubOrgAndRepoNameCombined)/actions/workflows/$($GitHubWorkflowPlatformToggleWorkflowsFileName)" -ErrorAction Stop
Write-Host ''Write-Host "Script execution complete. Fork of '$($GitHubOrgAndRepoNameCombined)' created and configured and cloned to '$($ClonedRepoDirectoryLocation)' as per Bicep contribution guide: https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/ you are now ready to proceed from step 4. Opening the Bicep Contribution Guide for you to review and continue..." -ForegroundColor Green
Start-Process 'https://azure.github.io/Azure-Verified-Modules/contributing/bicep/bicep-contribution-flow/'
Each time in the following sections we refer to ‘your xyz’, it is an indicator that you have to change something in your own environment.
Bicep AVM Modules (Resource, Pattern and Utility modules) are located in the /avm directory of the Azure/bicep-registry-modules repository, as per SNFR19.
Module owners are expected to fork the Azure/bicep-registry-modules repository and work on a branch from within their fork, before creating a Pull Request (PR) back into the Azure/bicep-registry-modules repository’s upstream main branch.
To do so, simply navigate to the Public Bicep Registry repository, select the 'Fork' button to the top right of the UI, select where the fork should be created (i.e., the owning organization) and finally click ‘Create fork’.
1.1 Create a GitHub environment
Create the avm-validation environment in your fork.
β How to: Create an environment in GitHub
Navigate to the repository’s Settings.
In the list of settings, expand Environments. You can create a new environment by selecting New environment on the top right.
In the opening view, provide avm-validation for the environment Name. Click on the Configure environment button.
Make sure to use a Managed Identity for OIDC as instructed below, not a Service Principal. Azure access token issued by Managed Identities is expected to have an expiration of 24 hours by default. With Service Principal, instead, it would be only 1 hour - which is not sufficient for many deployment pipelines.
Create a new or leverage an existing user-assigned managed identity with at least Contributor & User Access Administrator permissions on the Management-Group/Subscription you want to test the modules in. You may find creating an Owner role assignment is more efficient and avoids some validation failures for some modules. You might find the following links useful:
Some Azure resources may require additional roles to be assigned to the deployment identity. An example is the avm/res/aad/domain-service module, which requires the deployment identity to have the Domain Services Contributor Azure role to create the required Domain Services resources.
In those cases, for the first PR adding such modules to the public registry, we recommend the author to reach out to AVM maintainers or, alternatively, to create a CI environment GitHub issue in BRM, specifying the additional prerequisites. This ensures that the required additional roles get assigned in the upstream CI environment before the corresponding PR gets merged.
Configure a federated identity credential on a user-assigned managed identity to trust tokens issued by GitHub Actions to your GitHub repository.
In the Microsoft Entra admin center, navigate to the user-assigned managed identity you created. Under Settings in the left nav bar, select Federated credentials and then Add Credential.
In the Federated credential scenario dropdown box, select GitHub Actions deploying Azure resources
For the Organization, specify your GitHub organization name, for the Repository the value bicep-registry-modules.
For the Entity type, select Environment and specify the value avm-validation.
Add a Name for the federated credential, for example, avm-gh-env-validation.
The Issuer, Audiences, and Subject identifier fields auto-populate based on the values you entered.
Select Add to configure the federated credential.
You might find the following links & information useful:
If configuring the federated credential via API (e.g. Bicep, PowerShell etc.), you will need the following information points that are configured automatically for you via the portal experience:
β Option 2 [Deprecated]: Configure Service Principal + Secret
Create a new or leverage an existing Service Principal with at least Contributor & User Access Administrator permissions on the Management-Group/Subscription you want to test the modules in. You may find creating an Owner role assignment is more efficient and avoids some validation failures for some modules. You might find the following links useful:
To use the Continuous Integration environment’s workflows you should set up the following repository secrets:
Secret Name
Example
Description
ARM_MGMTGROUP_ID
11111111-1111-1111-1111-111111111111
The group ID of the management group to test-deploy modules in. Is needed for resources that are deployed to the management group scope.
ARM_SUBSCRIPTION_ID
22222222-2222-2222-2222-222222222222
The ID of the subscription to test-deploy modules in. Is needed for resources that are deployed to the subscription scope. Note: This repository secret will be deprecated in favor of the VALIDATE_SUBSCRIPTION_ID environment secret required by the OIDC authentication.
ARM_TENANT_ID
33333333-3333-3333-3333-333333333333
The tenant ID of the Azure Active Directory tenant to test-deploy modules in. Is needed for resources that are deployed to the tenant scope. Note: This repository secret will be deprecated in favor of the VALIDATE_TENANT_ID environment secret required by the OIDC authentication.
TOKEN_NAMEPREFIX
cntso
Required. A short (3-5 character length), unique string that should be included in any deployment to Azure. Usually, AVM Bicep test cases require this value to ensure no two contributors deploy resources with the same name - which is especially important for resources that require a globally unique name (e.g., Key Vault). These characters will be used as part of each resource’s name during deployment. For more information, see the [Special case: TOKEN_NAMEPREFIX] note below.
Special case: TOKEN_NAMEPREFIX
To lower the barrier to entry and allow users to easily define their own naming conventions, we introduced a default ’name prefix’ for all deployed resources.
This prefix is only used by the CI environment you validate your modules in, and doesn’t affect the naming of any resources you deploy as part of any solutions (applications/workloads) based on the modules.
Each workflow in AVM deploying resources uses a logic that automatically replaces “tokens” (i.e., placeholders) in any module test file. These tokens are, for example, included in the resources names (e.g. 'name: kvlt-${namePrefix}'). Tokens are stored as repository secrets to facilitate maintenance.
β How to: Add a repository secret to GitHub
Navigate to the repository’s Settings.
In the list of settings, expand Secrets and select Actions. You can create a new repository secret by selecting New repository secret on the top right.
In the opening view, you can create a secret by providing a secret Name, a secret Value, followed by a click on the Add secret button.
3.1.2 Authentication secrets
In addition to shared repository secrets detailed above, additional GitHub secrets are required to allow the deploying identity to authenticate to Azure.
Expand and follow the option corresponding to the deployment identity setup chosen at Step 2 and use the information you gathered during that step.
β Option 1 [Recommended]: Authenticate via OIDC
Create the following environment secrets in the avm-validation GitHub environment created at Step 1
Secret Name
Example
Description
VALIDATE_CLIENT_ID
44444444-4444-4444-4444-444444444444
The login credentials of the deployment principal used to log into the target Azure environment to test in. See the deployment credentials format.
VALIDATE_SUBSCRIPTION_ID
22222222-2222-2222-2222-222222222222
Same as the ARM_SUBSCRIPTION_ID repository secret set up above. The ID of the subscription to test-deploy modules in. Is needed for resources that are deployed to the subscription scope.
VALIDATE_TENANT_ID
33333333-3333-3333-3333-333333333333
Same as the ARM_TENANT_ID repository secret set up above. The tenant ID of the Azure Active Directory tenant to test-deploy modules in. Is needed for resources that are deployed to the tenant scope.
β How to: Add an environment secret to GitHub
Navigate to the repository’s Settings.
In the list of settings, select Environments. Click on the previously created avm-validation environment.
In the Environment secrets Section click on the Add environment secret button.
In the opening view, you can create a secret by providing a secret Name, a secret Value, followed by a click on the Add secret button.
β Option 2 [Deprecated]: Authenticate via Service Principal + Secret
Create the following environment repository secret:
The login credentials of the deployment principal used to log into the target Azure environment to test in. See the deployment credentials format. For more information, see the [Special case: AZURE_CREDENTIALS] note below.
Special case: AZURE_CREDENTIALS
This secret represent the service connection to Azure, and its value is a compressed JSON object that must match the following format:
Make sure you create this object as one continuous string as shown above - using the information you collected during Step 2. Failing to format the secret as above, causes GitHub to consider each line of the JSON object as a separate secret string. See the deployment credentials format for more information.
3.2. Enable actions
Finally, ‘GitHub Actions’ are disabled by default and hence, must be enabled first.
To do so, perform the following steps:
Navigate to the Actions tab on the top of the repository page.
Next, select ‘I understand my workflows, go ahead and enable them’.
3.3. Set Read/Write Workflow permissions
To let the workflow engine publish their results into your repository, you have to enable the read / write access for the GitHub actions.
Navigate to the Settings tab on the top of your repository page.
Within the section Code and automation click on Actions and General
Make sure to enable Read and write permissions
Tip
Once you enabled the GitHub actions, your workflows will behave as they do in the upstream repository. This includes a scheduled trigger to continuously check that all modules are working and compliant with the latest tests. However, testing all modules can incur substantial costs with the target subscription. Therefore, we recommend disabling all workflows of modules you are not working on. To make this as easy as possible, we created a workflow that disables/enables workflows based on a selected toggle & naming pattern. For more information on how to use this workflow, please refer to the corresponding documentation.
4. Implement your contribution
To implement your contribution, we kindly ask you to first review the Bicep specifications and composition guidelines in particular to make sure your contribution complies with the repository’s design and principles.
If you’re working on a new module, we’d also ask you to create its corresponding workflow file. Each module has its own file, but only differs in very few details, such as its triggers and pipeline variables. As a result, you can either copy & update any other module workflow file (starting with 'avm.[res|ptn|utl].') or leverage the following template:
β Module workflow template
# >>> UPDATE to for example "avm.res.key-vault.vault" and remove this commentname: "avm.[res|ptn|utl].[provider-namespace].[resource-type]"on:
workflow_dispatch:
inputs:
staticValidation:
type: booleandescription: "Execute static validation"required: falsedefault: truedeploymentValidation:
type: booleandescription: "Execute deployment validation"required: falsedefault: trueremoveDeployment:
type: booleandescription: "Remove deployed module"required: falsedefault: truecustomLocation:
type: stringdescription: "Default location overwrite (e.g., eastus)"required: falsepush:
branches:
- mainpaths:
# >>> UPDATE to for example ".github/workflows/avm.res.key-vault.vault.yml" and remove this comment - ".github/workflows/avm.[res|ptn|utl].[provider-namespace].[resource-type].yml"# >>> UPDATE to for example "avm/res/key-vault/vault/**" and remove this comment - "avm/[res|ptn|utl]/[provider-namespace]/[resource-type]/**" - "!*/**/README.md"env:
# >>> UPDATE to for example "avm/res/key-vault/vault" and remove this commentmodulePath: "avm/[res|ptn|utl]/[provider-namespace]/[resource-type]"# >>> Update to for example ".github/workflows/avm.res.key-vault.vault.yml" and remove this commentworkflowPath: ".github/workflows/avm.[res|ptn|utl].[provider-namespace].[resource-type].yml"concurrency:
group: ${{ github.workflow }}jobs:
############################ Initialize pipeline ############################job_initialize_pipeline:
runs-on: ubuntu-latestname: "Initialize pipeline"if: ${{ !cancelled() && !(github.repository != 'Azure/bicep-registry-modules' && github.event_name != 'workflow_dispatch') }}steps:
- name: "Checkout"uses: actions/checkout@v5with:
fetch-depth: 0 - name: "Set input parameters to output variables"id: get-workflow-paramuses: ./.github/actions/templates/avm-getWorkflowInputwith:
workflowPath: "${{ env.workflowPath}}" - name: "Get module test file paths"id: get-module-test-file-pathsuses: ./.github/actions/templates/avm-getModuleTestFileswith:
modulePath: "${{ env.modulePath }}"outputs:
workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }}moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }}psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }}modulePath: "${{ env.modulePath }}"############################### Call reusable workflow ###############################call-workflow-passing-data:
name: "Run"permissions:
id-token: write# For OIDCcontents: write# For release tagsneeds:
- job_initialize_pipelineuses: ./.github/workflows/avm.template.module.ymlwith:
workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}"moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}"psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}"modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}"secrets: inherit
Note
The workflow is configured to be triggered by any changes in the main branch of Upstream (i.e., Azure/bicep-registry-modules) that could affect the module or its validation. However, in a fork, the workflow is stopped immediately after being triggered due to the condition:
# Only run if not canceled and not in a fork, unless triggered by a workflow_dispatch eventif: ${{ !cancelled() && !(github.repository != 'Azure/bicep-registry-modules' && github.event_name != 'workflow_dispatch') }}
This condition prevents accidentally triggering a large amount of module workflows, e.g., when merging upstream changes into your fork.
In forks, workflow validation remains possible through explicit runs (that is, by using the Β workflow_dispatchΒ event).
Tip
After any change to a module and before running tests, we highly recommend running the Set-AVMModule utility to update all module files that are auto-generated (e.g., the main.json & readme.md files).
5. Create/Update and run tests
Before opening a Pull Request to the Bicep Public Registry, ensure your module is ready for publishing, by validating that it meets all the Testing Specifications as per SNFR1, SNFR2, SNFR3, SNFR4, SNFR5, SNFR6, SNFR7.
For example, to meet SNFR2, ensure the updated module is deployable against a testing Azure subscription and compliant with the intended configuration.
Depending on the type of contribution you implemented (for example, a new resource module feature) we would kindly ask you to also update the e2e test run by the pipeline. For a new parameter this could mean to either add its usage to an existing test file, or to add an entirely new test as per BCPRMNFR1.
Once the contribution is implemented and the changes are pushed to your forked repository, we kindly ask you to validate your updates in your own cloud environment before requesting to merge them to the main repo. Test your code leveraging the forked AVM CI environment you configured before
Tip
In case your contribution involves changes to a module, you can also optionally leverage the Validate module locally utility to validate the updated module from your local host before validating it through its pipeline.
Creating end-to-end tests
As per BCPRMNFR1, a resource module must contain a minimum set of deployment test cases, while for pattern modules there is no restriction on the naming each deployment test must have. In either case, you’re free to implement any additional, meaningful test that you see fit. Each test is implemented in its own test folder, containing at least a main.test.bicep and optionally any amount of extra deployment files that you may require (e.g., to deploy dependencies using a dependencies.bicep that you reference in the test template file).
To get started implementing your test in the main.test.bicep file, we recommend the following guidelines:
As per BCPNFR13, each main.test.bicep file should implement metadata to render the test more meaningful in the documentation
The main.test.bicep file should deploy any immediate dependencies (e.g., a resource group, if required) and invoke the module’s main template while providing all parameters for a given test scenario.
Parameters
Each file should define a parameter serviceShort. This parameter should be unique to this file (i.e, no two test files should share the same) as it is injected into all resource deployments, making them unique too and account for corresponding requirements.
As a reference you can create a identifier by combining a substring of the resource type and test scenario (e.g., in case of a Linux Virtual Machine Deployment: vmlin).
For the substring, we recommend to take the first character and subsequent ‘first’ character from the resource type identifier and combine them into one string. Following you can find a few examples for reference:
db-for-postgre-sql/flexible-server with a test folder default could be: dfpsfsdef
storage/storage-account with a test folder waf-aligned could be: ssawaf
π‘ If the combination of the servicesShort with the rest of a resource name becomes too long, it may be necessary to bend the above recommendations and shorten the name. This can especially happen when deploying resources such as Virtual Machines or Storage Accounts that only allow comparatively short names.
If the module deploys a resource-group-level resource, the template should further have a resourceGroupName parameter and subsequent resource deployment. As a reference for the default name you can use dep-<namePrefix><providerNamespace>.<resourceType>-${serviceShort}-rg.
Each file should also provide a location parameter that may default to the deployments default location
It is recommended to define all major resource names in the main.test.bicep file as it makes later maintenance easier. To implement this, make sure to pass all resource names to any referenced module (including any resource deployed in the dependencies.bicep).
Further, for any test file (including the dependencies.bicep file), the usage of variables should be reduced to the absolute minimum. In other words: You should only use variables if you must use them in more than one place. The idea is to keep the test files as simple as possible
References to dependencies should be implemented using resource references in combination with outputs. In other words: You should not hardcode any references into the module template’s deployment. Instead use references such as nestedDependencies.outputs.managedIdentityPrincipalId
Important
As per BCPNFR12 you must use the header module testDeployment '../.*main.bicep' = when invoking the module’s template.
The dependencies.bicep should optionally be used if any additional dependencies must be deployed into a nested scope (e.g. into a deployed Resource Group).
Note that you can reuse many of the assets implemented in other modules. For example, there are many recurring implementations for Managed Identities, Key Vaults, Virtual Network deployments, etc.
A special case to point out is the implementation of Key Vaults that require purge protection (for example, for Customer Managed Keys). As this implies that we cannot fully clean up a test deployment, it is recommended to generate a new name for this resource upon each pipeline run using the output of the utcNow() function at the time.
π If your test case requires any value that you cannot / should not specify in the test file itself (e.g., tenant-specific object IDs or secrets), please refer to the Custom CI secrets feature.
Reusable assets
The e2e template assets provide additional scripts and utilities that may be of use to module owners/contributors. These contain both scripts and Bicep templates that you can re-use in your test files (e.g., to deploy standadized dependencies, or to generate keys using deployment scripts).
Example: Certificate creation script
If you need a Deployment Script to set additional non-template resources up (for example certificates/files, etc.), we recommend to store it as a file in the shared utilities/e2e-template-assets/scripts folder and load it using the template function loadTextContent() (for example: scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/New-SSHKey.ps1')). This approach makes it easier to test & validate the logic and further allows reusing the same logic across multiple test cases.
Example: Diagnostic Settings dependencies
To test the numerous diagnostic settings targets (Log Analytics Workspace, Storage Account, Event Hub, etc.) the AVM core team have provided a dependencies .bicep file to help create all these pre-requisite targets that will be needed during test runs.
β Diagnostic Settings Dependencies - Bicep File
// ========== //// Parameters //// ========== //@description('Required. The name of the storage account to create.')
@maxLength(24)
param storageAccountName string
@description('Required. The name of the log analytics workspace to create.')
param logAnalyticsWorkspaceName string
@description('Required. The name of the event hub namespace to create.')
param eventHubNamespaceName string
@description('Required. The name of the event hub to create inside the event hub namespace.')
param eventHubNamespaceEventHubName string
@description('Optional. The location to deploy resources to.')
param location string = resourceGroup().location
// ============ //// Dependencies //// ============ //resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
name: storageAccountName
location: location
kind: 'StorageV2' sku: {
name: 'Standard_LRS' }
properties: {
allowBlobPublicAccess: false }
}
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
name: logAnalyticsWorkspaceName
location: location
}
resource eventHubNamespace 'Microsoft.EventHub/namespaces@2021-11-01' = {
name: eventHubNamespaceName
location: location
resource eventHub 'eventhubs@2021-11-01' = {
name: eventHubNamespaceEventHubName
}
resource authorizationRule 'authorizationRules@2021-06-01-preview' = {
name: 'RootManageSharedAccessKey' properties: {
rights: [
'Listen''Manage''Send' ]
}
}
}
// ======= //// Outputs //// ======= //@description('The resource ID of the created Storage Account.')
output storageAccountResourceId string = storageAccount.id
@description('The resource ID of the created Log Analytics Workspace.')
output logAnalyticsWorkspaceResourceId string = logAnalyticsWorkspace.id
@description('The resource ID of the created Event Hub Namespace.')
output eventHubNamespaceResourceId string = eventHubNamespace.id
@description('The resource ID of the created Event Hub Namespace Authorization Rule.')
output eventHubAuthorizationRuleId string = eventHubNamespace::authorizationRule.id
@description('The name of the created Event Hub Namespace Event Hub.')
output eventHubNamespaceEventHubName string = eventHubNamespace::eventHub.name
6. Create a Pull Request to the Public Bicep Registry
Finally, once you are satisfied with your contribution and validated it, open a PR for the module owners or core team to review. Make sure you:
Provide a meaningful title in the form of feat: <module name> to align with the Semantic PR Check.
Provide a meaningful description.
Follow instructions you find in the PR template.
If applicable (i.e., a module is created/updated), please reference the badge status of your pipeline run. This badge will show the reviewer that the code changes were successfully validated & tested in your environment. To create a badge, first select the three dots (...) at the top right of the pipeline, and then chose the Create status badge option.
In the opening pop-up, you first need to select your branch and then click on the Copy status badge Markdown
Note
If you receive any comments for your pull request, please adhere to the following practices
If it is a ‘suggestion’ that you agree with, you can directly commit it into your branch by selecting the ‘Apply suggestion’ button, auto-resolving the comment
If it’s a regular comment that you agree with, please address its ask and leave a comment indicating the same. Do not resolve it yourself as this renders a re-review a lot harder for the reviewer.
Note
If you’re the sole owner of the module, the AVM core team must review and approve the PR. To indicate that your PR needs the core team’s attention, apply the Β Needs: Core Team π§Β label on it!
7. Get your pull request approved
To publish a new module or a new version of an existing module, each Pull Request (PR) MUST be reviewed and approved before being merged and published in the Public Bicep Registry. A contributor (the submitter of the PR) cannot approve their own PR.
This behavior is assisted by policies, bots, through automatic assignment of the expected reviewer(s) and supporting labels.
Important
As part of the PR review process, the submitter (contributor) MUST address any comments raised by the reviewers and request a new review - and repeat this process until the PR is approved. Once the PR is merged, the module owner MUST ensure that the related GitHub Actions workflow has successfully published the new version of the module.
7.1. Publishing a new module
When publishing a net new module for the first time ever, the PR MUST be reviewed and approved by a member of the core team.
7.2. Publishing a new version of an existing module
When publishing a new version of an existing module (i.e., anything that is not being published for the first time ever), the PR approval logic is the following:
PR is submitted by a module owner
PR is submitted by anyone, other than the module owner
Module has a single module owner
AVM core team or in case of Terraform only, the owner of another module approves the PR
Module owner approves the PR
Module has multiple module owners
Another owner of the module (other than the submitter) approves the PR
One of the owners of the module approves the PR
In case of Bicep modules, if the PR includes any changes outside of the “modules/” folder, it first needs the module related code changes need to be reviewed and approved as per the above table, and only then does the PR need to be approved by a member of the core team. This way the core team’s approval does not act as a bypass from the actual code review perspective.
Subsections of Contribution Flow
Child Module Publishing
Child resources are resources that exist only within the scope of another resource. For example, a virtual network subnet cannot exist without a virtual network. The subnet is a child resource of the virtual network.
In the context of AVM, particularly AVM Bicep resource modules, child modules are modules deploying child resources. They are implemented within the scope of their corresponding parent resource modules. For example, the module avm/res/network/virtual-network/subnet deploys a virtual network subnet and is a child module of its parent virtual network module avm/res/network/virtual-network.
By default, child modules are not published to the public bicep registry independently from their parents. They need to be explicitly enabled for publishing to be directly referenced from the registry.
This page covers step-by-step guidelines to publish a bicep child module.
Important
The child module publishing process is currently in a pilot/preview phase. This means it may not be as smooth as the general module publishing.
The core team is currently working on additional automation, with the goal of improving efficiency in addressing child module publishing requests.
Note
Child module publishing currently only applies to resource modules.
Supporting child module publishing for other module categories, such as pattern and utility modules, is not planned at this time.
Quick guide
Use this section for a fast overview on how to publish a child module. For a step-by-step explanation with detailed instructions, refer to the following sections.
Child module template: Add enableTelemetry parameter and avmTelemetry deployment to child main.bicep template.
Parent module template: In the main.bicep template of the child module direct parent, add a enableReferencedModulesTelemetry variable with a value of false, and pass it as the enableTelemetry value down to the child module deployment.
Version: Add the version.json file to the child module folder and set version to 0.1.
Changelog: Add a new CHANGELOG.md file to the child module folder and update the changelog of all its versioned parents with a new patch version, up to the top-level parent.
Set-AVMModule: Run the Set-AVMModule utility explicitly on the affected bicep modules, i.e., the child module(s) and all their parent modules up to the top-level module, test your changes and raise a PR.
Prerequisites
Before jumping into the implementation, make sure the following prerequisites are in place:
Please understand the difference between publishing an existing child module and extending a parent module with a not yet implemented child module functionality.
The Bicep Child Module Proposal issue primarily intends to cover the former, i.e. to publish a child module already existing in the BRM (Bicep Registry Modules) repository source code. However, the same issue allows also to request the development of the child module functionality, although the best way to address new functionality is to raise a feature request via the the AVM Module issue.
Telemetry ID prefix assigned
Follow the below steps to check the child module telemetry ID prefix.
Note
If the Bicep Child Module Proposal issue was just created, please allow a few days for the telemetry ID prefix to be assigned before reaching out.
Search for the child module name in the ModuleName field.
Verify if the corresponding value exists in the TelemetryIdPrefix field. Note down the value as you will need it in the implementation phase.
If not found, please reach out to the core team, mentioning the @Azure/avm-core-team-technical-bicep via the Bicep Child Module Proposal issue.
Module registered in the MAR-file
Ensure that the child module is registered in the MAR file. If not, please reach out to the core team, mentioning the @Azure/avm-core-team-technical-bicep via the Bicep Child Module Proposal issue.
Note
The MAR-file can only be accessed by Microsoft FTEs. If you are missing access, please reach out to the parent module owner for help.
Implementation
The quickest way to get the child module published is to enable it yourself, contributing via a pull request to the BRM repository.
Note
Publishing a child module does not change the folder hierarchy of the parent and child modules. The child module remains in its existing location within the parent module’s folder structure. No files or folders need to be moved or reorganized.
Please follow the steps below:
Make sure the child module name is listed in the publishing allowed list child-module-publish-allowed-list.json. If not, add it to the file, keeping an alphabetical order. This step is relevant until the process is in a pilot phase.
Update the child module main.bicep template to support telemetry, as per SFR4, SFR3 and BCPFR4
Add the enableTelemetry parameter with a default value of true. Place it as the last param declaration, immediately before the first var declaration.
Add the avmTelemetry deployment, referencing below template. Make sure to replace the <ReplaceWith-TelemetryIdPrefix> placeholder with the assigned telemetry ID prefix value that you noted down when checking prerequisites.
Update the main.bicep template of the child module direct parent, as per BCPFR7.
Add the enableReferencedModulesTelemetry variable with a default value of false. Place it as the last var declaration, immediately before the first resource declaration.
var enableReferencedModulesTelemetry = false
Pass the enableReferencedModulesTelemetry variable as the enableTelemetry value down to the child module deployment.
enableTelemetry: enableReferencedModulesTelemetry
Add the version.json file to the child module folder and set version to 0.1.
Add a new CHANGELOG.md file to the child module folder, with the following sample content. Make sure to replace the <avm/res/path/to/child-module> placeholder with the name of the child module.
# Changelog
The latest version of the changelog can be found [here](https://github.com/Azure/bicep-registry-modules/blob/main/<avm/res/path/to/child-module>/CHANGELOG.md).
## 0.1.0
### Changes
- Initial version
### Breaking Changes
- None
Check the list of affected modules. Update the changelog of all the affected modules with a version.json. Add a new patch version for each. Refer below for an example content section:
Run the Set-AVMModule utility, calling it explicitly on all affected modules.
foreach ($modulePath in $affectedModulePaths) {
Set-AVMModule -ModuleFolderPath $modulePath
}
Test your changes via the top-level module pipeline, raise a PR and attach a status badge proving successful validation.
Note
Existing tests for the parent module do not need to be updated when publishing a child module. The changes required for child module publishing (telemetry support, version file, and changelogs) do not affect the module’s test cases.
Tip
Reference This pull request as an example for proposing a child module for publishing.
Custom CI Secrets
When working on a module, and more specifically its e2e deployment validation test cases, it may be necessary to leverage tenant-specific information such as:
(sensitive) principal credentials (e.g., a custom service principal’s application id and secret)
The challenge with the former is that the value would be different from the contributor’s test tenant compared to the Upstream AVM one. This requires the contributor to temporarily change the value to their own tenant’s value during the contribution’s creation & testing, and for the reviewer to make sure the value is changed back before merging a PR in. The challenge with the later is more critical as it would require the contributor to store sensitive information in source control and as such publish it.
To mitigate this challenge, the AVM CI provides you with the feature to store any such information in a custom Azure Key Vault and automatically pass it into your test cases in a dynamic & secure way.
Important
Since all modules must pass the tests in the AVM environment, it is important that you inform the maintainers when you add a new custom secret. The same secret must then also be set up in the upstream environment before the pull request is merged.
To make this matter not too complicated, we would like to ask you to emphasize this requirement in the description of your PR, for example by adding a text similar to:
- [ ] @avm-core-team-technical-bicep TODO: Add custom secret 'mySecret' to AVM CI
Example use case
Let’s assume you need a tenant-specific value like the object id of Azure’s Backup Management Service Enterprise Application for one of your tests. As you want to avoid hardcoding and consequently changing its value each time you want to contribute from your Fork to the main AVM repository, you want to instead have it be automatically pulled into your test cases.
To do so, you create a new parameter in your test case’s main.test.bicep file that you call, for example,
assuming that it would be provided with the correct value by the AVM CI. You consequently reference it in your test case as you would with any other Bicep parameter.
Next, you create a new secret of the same name with a prefix CI- in a previously created Azure Key Vault of your test subscription (e.g., CI-backupManagementServiceEnterpriseApplicationObjectId). Its value would be the object id the Enterprise Application has in the tenant of your test subscription.
Assuming that also the CI_KEY_VAULT_NAME GitHub Repository variable is configured correctly, you can now run your test pipeline and observe how the CI automatically pulls the secret and passes it into your test cases, IF, they have a parameter with a matching name.
Setup
Pre-Requisites
To use this feature, there are really only three prerequisites:
Create an Azure Key Vault in your test subscription
Grant the principal you use for testing in the CI at least `Key Vault Secrets User’ permissions on that Key Vault to enable it to pull secrets from it
Configure the name of that Key Vault as a ‘Repository variable’ CI_KEY_VAULT_NAME in your Fork.
The above will enable the CI to identify your Key Vault, look for matching secrets in it, and pull their values as needed.
Configuring a secret
Building upon the prerequisites you only have to implement two actions per value to dynamically populate them during deployment validation:
Create a @secure() parameter in your test file (main.test.bicep) that you want to populate and use it as you see fit.
For example:
@description('Required. My parameter\'s description. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-MySecret\'.')
@secure()
param mySecret string = ''
Important
It is mandatory to declare the parameter as secure() as Key Vault secrets will be pulled and passed into the deployment as SecureString values.
Also, it must have an empty default to be compatible with the PSRule scans that require a value for all parameters.
Configure a secret of the same name, but with a CI- prefix and corresponding value in the Azure Key Vault you set up as per the prerequisites.
How it works
Assuming you completed both the prerequisites & setup steps and triggered your module’s workflow, the CI will perform the following actions:
When approaching the deployment validation steps, the workflow will lookup the CI_KEY_VAULT_NAME repository variable
If it has a value, it will subsequently pull all available secret references (not their values!) from that Key Vault, filtered down to only the secrets that match the CI- prefix
It will then loop through these secret references and check if any match a parameter in the targeted test.main.bicep of the same name, but without the CI- prefix
Only for a match, the workflow with then pull the secret from the Key Vault and pass its value as a SecureString as a parameter into the template deployment.
When reviewing the log during or after a run, you can see each matching and pulled secret is/was added as part of the AdditionalParameters object as seen in the following:
Background: Why not simply use GitHub secrets?
When reviewing the above, you may wonder why an Azure Key Vault was used as opposed to simple GitHub secrets.
While the simplicity of GitHub secrets would be preferred, it unfortunately turned out that they would not provide us with the level of flexibility we need for our purposes.
Most notably, GitHub secrets are not automatically available in referenced GitHub actions. Instead, you have to declare every secret you want to use explicitly in the workflow’s template, requiring the contributor to update both the module’s workflow template as well as test files each time a new value would be added. This characteristic is not only unfortunate for our use case, but is also a lot more likely to lead to mistakes.
Further, with the use of OIDC via Managed Identities, the hurdle to bootstrap & populate an Azure Key Vault is significantly lowered.
Enable or Disable Workflows
When forking the BRM repository, all workflows from the CI environment are also part of your fork. In an earlier step it was explained, how to set them up correctly, to verify your module development.
Due to the trigger mechanism of the workflows, eventually all of them run at some point in time, creating and deleting resources on Azure in your environment. That will also happen for modules, you are not working on. This will create costs in your own subscription and it can also create a queue for workflow runs, due to the lack of enough free agents.
To limit those workflow runs, you can manually disable each pipeline you do not want to run. As this is a time consuming task, there is script in the BRM repository, to disable (or enable) pipelines in a batch process, that can also be run via a workflow. You can also use RegEx to specify which pipelines should be included and which should be excluded.
Browse to Actions and select the workflow from the list
Run the workflow platform.toggle-avm-workflows and set the following settings:
Enable or disable workflows to enable or disable workflows
RegEx which workflows are included include a specific set of workflows, using a RegEx.
RegEx which workflows are excluded exclude a specific set of workflows, using a RegEx.
Typical use cases
Disable all but one workflow
Enable or disable workflows to Disable
RegEx which workflows are included to avm\.(?:res|ptn|utl) (this is the default setting)
RegEx which workflows are excluded to avm.res.compute.virtual-machine (use the name of your own workflow. This example uses the workflow for virtual machine)
Disable all but multiple workflows
Enable or disable workflows to Disable
RegEx which workflows are included to avm\.(?:res|ptn|utl) (this is the default setting)
RegEx which workflows are excluded to (?:avm.res.compute.virtual-machine|avm.res.compute.image|avm.res.compute.disk) (use the names of your own workflows. This example uses the workflows for virtual machine, image, and disk)
Enable all workflows
Enable or disable workflows to Enable
RegEx which workflows are included to avm\.(?:res|ptn|utl) (this is the default setting)
RegEx which workflows are excluded to ^$ (this is the default setting)
Limitations
Please keep in mind, that the workflow run disables all workflows that match the RegEx at that point in time. If you sync your fork with the original repository and new workflows are there, they will be synced to your repository and will be enabled by default. So you will need to run the workflow to disable the new ones again after the sync.
Important
The workflow can only be triggered in forks.
Owner Contribution Flow
This section describes the contribution flow for module owners who are responsible for creating and maintaining Bicep Modules.
Important
This contribution flow is for Module Owners only.
As a Bicep Module Owner you need to be aware of the AVM Contribution Process Overview, Bicep specifications (including Bicep Interfaces) as these need to be followed during pull request reviews for the modules you own. The purpose of this Owner Contribution Flow is to simplify and list the most important activities of an owner and to help you understand your responsibilities as an owner.
Note
Additional internal content for ongoing module maintenance available for Microsoft FTEs, here.
Create a GitHub team as outlined in SNFR20 and add it to the respective parent team:
Naming convention:
avm-res-<RP>-<modulename>-module-owners-bicep
Example:
avm-res-compute-virtualmachine-module-owners-bicep and added avm-technical-reviewers-bicep as parent.
If a secondary or any additional owner is required, add them to the avm-res-<RP>-<modulename>-module-owners-bicep team.
Only fulltime Microsoft employees can be added at this time.
Info
Once the team have been created the AVM Core Team will review the team name and parent team membership for accuracy. A notification will automatically be sent to the AVM Core Team to inform them that their review needs to be completed.
Add the -owners- team to CODEOWNERS file as outlined in SNFR20.
Ensure your module has been tested before raising a PR. You can do this your own or in another module contributor’s environment - if any. Also, once a PR is raised, a GitHub workflow pipeline is required to be run successfully before the PR can be merged. This is to ensure that the module is working as expected and is compliant with the AVM specifications.
Note
If you’re the sole owner of the module, the AVM core team must review and approve the PR. To indicate that your PR needs the core team’s attention, apply the Β Needs: Core Team π§Β label on it!
Ensure that the module(s) you own are compliant with the AVM Bicep specifications and are working as expected.
Watch Pull Request (PR) activity for your module(s) in the BRM repository (Bicep Registry Modules repository - where all Bicep AVM modules are published) and ensure that PRs are reviewed and merged in a timely manner as outlined in SNFR11.
Watch AVM module issue and AVM question/feedback activity for your module(s) in the BRM repository.
2. Module Handover Activities
Under certain circumstances, you may find yourself unable to continue as the module owner. In such cases, it is advisable to designate a new module owner. The following steps outline this transition:
Leave a comment on the original module proposal, indicating that you’d like to hand the ownership over to somebody else. Mention the person who originally helped triage the issue or the @Azure/avm-core-team-technical-bicep team. You must wait for someone from the AVM Core Team to respond first, as the module index must be updated before you can continue handing over the ownership.
Add the new owner’s GitHub account as a “maintainer” on your modules GitHub team.
Remove your GitHub account from your module’s GitHub team.
If a new module owner cannot be identified then the module will need to be “Orphaned”. Please follow the step outlined when-a-module-becomes-orphaned.
As a module owner, it’s important that you receive notifications when any of your AVM modules experience activity or when you or any groups you belong to are explicitly mentioned (using the @ operator). This document describes how to configure your GitHub and Email settings to ensure you receive email notifications for these types of scenarios within GitHub.
Ensure your Default Notifications Email address is set to the email address you intend to use.
(Optional) If you would like to automatically watch repositories that you are active in, ensure Automatically watch repositories is set to “On.”
(Required) If you would like to automatically subscribe to team-level notifications whenever you join a new team, ensure Automatically watch teams is set to “On.”
(Required) To receive notifications whenever a change is made to a repository or conversation that you are Watching, ensure the Notify Me setting has at least Email enabled.
(Required)To receive notifications whenever you or a group you belong to are @mentioned, ensure the Notify Me setting has at least Email enabled.
Watch a Repository
Optionally, you may consider “watching” (following most or all activities in) an entire repository. The primary repository that owners should watch is the Bicep-Registry-Modules (BRM) repository. Notifications from this repository will notify you of issues concerning your module and any direct or team @mentions. It is important that you read and react to these messages.
To watch the BRM repository, visit Bicep-Registry-Modules, click the Watch button in the top-right of the page, then select Participating and @mentions. Optionally, if you would like to be notified for all activity within the repository, you can select All Activity.
Note
Enabling All Activity will result in a lot of notifications! If you choose to go this route, you should set up filters within your email client. See Configure Email Inbox Notification Filters.
Configure Email Inbox Notification Filters
GitHub uses a unique email address sender for each type of notification it sends. This allows us to set up filters within our email client to sort our inboxes depending on the type of notifications that was sent. The table below lists all of the relevant email addresses that may be useful for filtering notifications from GitHub.
Info
GitHub will use the following email addresses to Cc you if you’re subscribed to a conversation. The second Cc email address matches the notification reason.
This checklist can be used in the development of AVM Bicep Modules.
Before beginning any work a new module a valid Issue: New AVM Module Proposal needs to be created. Instructions for creating the module proposal are outlined in the issue template. Pay particular attention to the questions and associated links to fill out the proposal accurately. Please do not start work on your proposed module until you receive a notification that your proposal has been accepted.
Fork the bicep-registry-modules BRM repository. If you use an existing fork, ensure it’s up to date with origin/BRM.
Ensure all workflows are disabled by default once you forked the BRM repo, to prevent any accidental deployments into your Azure test environment resulted by an automated deployment.
Create a new branch from your forked repository to develop your module.
If you’re working on a new module you have to create its corresponding workflow file (see here).
In order to run your e2e tests in your fork, this workflow file has to be put into the main branch first, so it can be run against your feature branch (GitHub Workflows can only be run on feature branches when they are already present in the main branch).
Since all workflows are disabled by default you have to enable your module’s specific GitHub workflow to run your e2e tests.
In addition to testing your module via GitHub pipeline, you can also test-locally. The following helper script facilitates local testing.
β Local Test Helper Script
# Start pwsh if not started yetpwsh
# Set default directory$folder = "<your directory>/bicep-registry-modules"# Dot source functions. $folder/utilities/tools/Set-AVMModule.ps1
. $folder/utilities/tools/Test-ModuleLocally.ps1
# Variables$modules = @(
# "service-fabric/cluster", # Replace with your module"network/private-endpoint"# Replace with your module)
# Generate Readmeforeach ($module in $modules) {
Write-Output "Generating ReadMe for module $module" Set-AVMModule -ModuleFolderPath "$folder/avm/res/$module" -Recurse
# Set up test settings $testcases = "waf-aligned", "max", "defaults" $TestModuleLocallyInput = @{
TemplateFilePath = "$folder/avm/res/$module/main.bicep" ModuleTestFilePath = "$folder/avm/res/$module/tests/e2e/max/main.test.bicep" PesterTest = $true
ValidationTest = $false
DeploymentTest = $false
ValidateOrDeployParameters = @{
Location = '<your location>' SubscriptionId = '<your subscriptionId>' RemoveDeployment = $true
}
AdditionalTokens = @{
namePrefix = '<your prefix>' TenantId = '<your tenantId>' }
}
# Run testsforeach ($testcase in $testcases) {
Write-Output "Running test case $testcase on module $module" $TestModuleLocallyInput.ModuleTestFilePath = "$folder/avm/res/$module/tests/e2e/$testcase/main.test.bicep" Test-ModuleLocally @TestModuleLocallyInput
}
}
Create a PR and reference the status badge of your pipeline run - see here.
Note
If you’re the sole owner of the module, the AVM core team must review and approve the PR. To indicate that your PR needs the core team’s attention, apply the Β Needs: Core Team π§Β label on it!
After a pull request has been created, it is important to update the AVM module proposal issue associated with your module, with a link to the pull request you created in BRM and mention the person who helped triage your module or the @Azure/avm-core-team-technical-bicep team.
Once your BRM pull request has been approved and merged into main update the AVM module proposal issue associated with your module, with a Merged comment and mention the person who helped triage your module, or the @Azure/avm-core-team-technical-bicep team.
Generate Bicep Module Files
As per the module design structure (BCPNFR23), every module in the AVM library requires
a up-to-date ReadMe markdown (readme.md) file documenting the set of deployable resource types, input and output parameters and a set of relevant template references from the official Azure Resource Reference documentation
an up-to-date compiled template (main.json) file
The Set-AVMModule utility aims to simplify contributing to the AVM library, as it supports
idempotently generating the AVM folder structure for a module (including any child resource)
generating the module’s ReadMe file from scratch or updating it
compiling/building the module template
To ease maintenance, you can run the utility with a Recurse flag from the root of your folder to update all files automatically.
To do so, it searches for any required folder path / file missing and adds them. For several files, it will also provide some default content to get you started. The sources files for this action can be found here
compiles its bicep template
updates the readme (recursively, specified)
If the intended readMe file does not yet exist in the expected path, it is generated with a skeleton (with e.g., a generated header name)
The script then goes through all sections defined as SectionsToRefresh (by default all) and refreshes the sections’ content (for example, for the Parameters) based on the values in the ARM/JSON Template. It detects sections by their header and always regenerates the full section.
Once all are refreshed, the current ReadMe file is overwritten. Note: The script can be invoked combining the WhatIf and Verbose switches to just receive an console-output of the updated content.
How to use it
For details on how to use the function, please refer to the script’s local documentation.
Note
The script must be loaded (’dot-sourced’) before the function can be invoked.
. 'C:/dev/Set-AVMModule.ps1'Set-AVMModule (...)
Tip
For modules that require the generation of files on multiple-levels (for example, a module with child modules such as the ‘Key Vault’ module with its ‘Secret’ child module) it is highly recommended to make use of the -Recurse parameter.
This parameter will ensure that the script not only generates the files for the provided module folder path, but also all its nested module folder paths.
Tip
While readme files are always generated from scratch, you can add custom content is specific places that the script will preserve:
The module’s description in the main.bicep file’s metadata
The description of parameters & outputs
A section with the header ## Notes
If the utility finds a section with the heading ## Notes, it temporarily saves this content when it regenerates the readme file and then re-inserts (i.e. appends) the section toward the end of the readme file. This section may contain images, which must be stored in a subfolder /src in the root directory of the module.
Both for the text & images, please make sure to only add what provides tangible value as the content must be manually maintained and should not run stale. Further, for images, please make sure to only store them with an appropriate resolution & size to keep their impact on the repository’s size manageable.
Validate Module Locally
Use this script to test a module from your PC locally, without a CI environment. You can use it to run only the static validation (Pester tests), a deployment validation (dryRun) or an actual deployment to Azure. In the latter cases the script also takes care to replace placeholder tokens in the used module test & template files for you.
If the switch for Pester tests (-PesterTest) is provided the script will
Invoke the module test for the provided template file path and run all tests for it.
If the switch for either the validation test (-ValidationTest) or deployment test (-DeploymentTest) is provided alongside a HashTable for the token replacement (-ValidateOrDeployParameters), the script will
Either fetch all module test files of the module’s tests folder (default) or you can specify a single module test file by leveraging the -ModuleTestFilePath parameter instead.
Create a dictionary to replace all tokens in these module test files with actual values. This dictionary will consist
of the subscriptionID & managementGroupID of the provided ValidateOrDeployParameters object,
add all key-value pairs of the -AdditionalTokens object to it,
and optionally also add all key-value pairs specified in the settings.yml, under the ’local tokens settings'.
If the -ValidationTest parameter was set, it runs a deployment validation using the Test-TemplateDeployment script.
If the -DeploymentTest parameter was set, it runs a deployment using the New-TemplateDeployment script (with no retries).
As a final step, it rolls the module test files back to their original state if either the -ValidationTest or -DeploymentTest parameters were provided.
How to use it
For details on how to use the function, please refer to the script’s local documentation.
Note
The script must be loaded (’dot-sourced’) before the function can be invoked.
Important: As the script emulates the testing logic of the CI environment, also tokens such as #_namePrefix_# are replaced by the script. However, in addition to the CI environment, it also reverses the token replacement to recover the files’ original state. As such, ensure that you use a namePrefix value that is unlikely to overlap with any string value in module folder you want to test.
For example, do not use avm, as the reverse token replacement would incorrectly replace the deployment name avmTelemetry found in each module to #_namePrefix_#Telemetry.
Bicep Contribution Prerequisites
GitHub Account Link and Access
You need to have a personal GitHub account which is linked to your Microsoft corporate identity. Once the link step is complete you must join the Azure organization.
Recommended Learning
Before you start contributing to the AVM, it is highly recommended that you complete the following Microsoft Learn paths, modules & courses:
To enhance streamlined integration during interactions with upstream repositories, GitHub Desktop will automatically configure your local git repository to use the upstream repository as a remote.
Contribution Q&A
Tip
Check out the FAQ for more answers to common questions about the AVM initiative in general.
Proposing a module
Who can propose a new module and where can I submit a new module proposal / request?
Everyone can propose a module
To propose a new module, simply create an issue/complete the form here.
Can I just propose / create any module?
For example, can I propose one for managed disks or NICs or diagnostic settings? What about patterns?
No, you cannot propose or create just any module. You can only propose modules that are aligned with requirements documented in the module specifications section.
Below, we provide some guidance on what modules you can / cannot propose.
Resource modules: resource modules have bring extra value to the end user (can’t just be simple wrappers) and MUST mapped 1:1 to RPs (resource providers) and top level resources. You MUST follow the module specifications and your modules SHOULD be WAF aligned.
Good examples:
Virtual machine: the VM module is highly complex and therefore, it brings extra value to the end user by providing a wide variety of features (e.g., diagnostics, RBAC, domain join, disk encryption, backup and more).
Storage account: even though, this module is mainly built around one RP, it brings extra value by providing easy access to its child resources, such as file/table/queue services, as well as additional standard interfaces (e.g., diagnostics, RBAC, encryption, firewall, etc.).
Bad examples:
NIC or Public IP (PIP) module: these would be simple wrappers around the NIC/PIP resource and wouldn’t bring any extra value. NICs and PIPs SHOULD be surfaced as part of the VM module (or any other primary resources that require them).
Diagnostic settings: these are too low-level “sub resources”, and highly dependent on their “primary resource’s” RP defined as “interfaces” and therefore MUST be used as part of a resource module holding a primary resource - see Diagnostic Settings documentation about the correct implementation.
Pattern modules: In case of pattern modules, ideally you should start from architectural patterns, published in the Azure Architecture Center, and build your pattern module by leveraging resource modules that are required to implement the pattern. AVM does not provide architectural guidance on how you should design your pattern, but you MUST follow the module specifications and your modules SHOULD be WAF aligned.
Good examples:
Landing zone accelerators for N-tier web application; AKS cluster; SAP: there are numerous examples for these architectures in Azure Architecture Center that already have baked in guidance / smart defaults that are WAF Aligned, therefore these are good candidates for pattern modules. Module owners MAY leverage resource modules to implement the pattern.
Hub and spoke topology: it’s a common pattern that is used by many customers and there are great examples available through Azure Architecture Center, as well as Azure Landing Zones. Also a good candidate for a pattern module.
Bad examples:
A pair of Virtual machines: being a simple wrapper, this solution wouldn’t bring any extra value as it doesn’t provide a complete solution.
Key Vault that deploys automatically generated secrets: this is aligned with the definition of a resource modules, therefore it should be categorized as such.
Where do I need to go to make sure the module I’d like to propose is not already in the works?
The AVM core team maintains the list of Bicep and Terraform modules and tracks the status of each module. Based on this list, you can check if the module you’d like to build is already in the works (e.g., it’s being worked on in a feature branch but hasn’t been published yet).
To see the formatted lists with additional information, please visit the AVM Module Indexes page.
I need a new module but I cannot own/author it for various reasons, what should I do?
You sign up to be a module owner (and optionally, you can find additional contributors to help you).
You find / request someone else to be the module owner (and optionally, you can be a contributor).
You propose a module and wait until the AVM core team finds a module owner for you (who then can optionally leverage the help of additional contributors).
As these options are increasingly more time consuming, we recommend you to start with considering option 1 and only if you cannot own the module, should you move to option 2 and then 3.
How long will it take for someone to respond and a module to be created/updated and published?
While there are SLAs defined for providing support for existing modules, there are currently no SLAs in place for the creation of new modules. The AVM core team is a small team and is currently working on automating the module creation process to make it as easy as possible for module owners to create and publish modules on their own.
Beside of providing program level governance, the AVM core team is mainly responsible for defining the module specifications, providing tooling (such as test frameworks and pipelines), guidance and support to module owners, as well as facilitating the creation of new modules by maintaining the module catalog and identifying volunteers for owning the modules. However, modules will be created and maintained by a broader community of module owners.
How do I let the AVM team know I really need an AVM module to unblock me / my project / my company?
If you’re an external user, you can propose a module here and provide as much context as possible under the “Module Details” section (e.g., why do you need the module, what’s the business impact of not having it, etc.).
If you’re a Microsoft employee and have already proposed a module here, you can reach out to the AVM core team directly via Teams to provide more details internally.
The AVM core team will then triage the request and get back to you with next steps. You can accelerate the process of creating the module by volunteering to be a module owner.
Developing a module
Who is developing a modules?
Every module has an owner that is responsible for module development and maintenance. One owner can own one or multiple modules. An owner can develop modules alone or lead a team that will develop a module. If you want to join a team and to contribute on specific module, please contact module owner.
At this moment, only Microsoft FTEs can be module owners.
What do I need so I can start developing a module?
Feel free to reach out to the AVM Core team in case that additional help is needed.
What do I do about existing modules that are available doing a similar thing to my module that I am proposing to develop and release?
As part of the Module Proposal process, the AVM core team will work with you to triage your proposal. We also want to make sure that no similar existing modules from known Microsoft projects are already on their way to be migrated to AVM.
If there aren’t any, then you can proceed with developing your module from scratch once given approval to proceed by the AVM core team.
However, if there are existing modules from Microsoft projects we would invite you to help us complete the migration to AVM of this module; this may also entail working with the existing module owner/team.
For existing modules that may not be directly owned and developed by Microsoft or their employees you should first review the license applied to the GitHub repository hosting the module and understand its terms and conditions. More information on GitHub repositories and licenses can be found here in Licensing a repository Most modules will use a license that will allow you to take inspiration and copy all or parts from the module source code. However, to confirm, you should always check the license and any conditions you may have to meet by doing this.
What are the mandatory labels that needs to be used while managing issues, pull requests and discussions on GitHub repositories where module are held?
Where module will live? Do I need to create separate repo or to place it in specific folder?
Bicep
For Bicep, both Resource and Pattern, AVM Modules will be homed in the Azure/bicep-registry-modules repository and live within an avm directory that will be located at the root of the repository.
If you are module owner, it is expected that you will fork the Azure/bicep-registry-modules repository and work on a branch from within their fork, before then creating a Pull Request (PR) back into the Azure/bicep-registry-modules repositories main branch. In Bice contribution guide, you can discover Directory and File structure that will be used and examples.
Terraform
Each Terraform AVM module will have its own GitHub Repository in the Azure GitHub Organization. This repo will be created by the Module Owners and the AVM Core team collaboratively, including the configuration of permissions. To read more about how to start, navigate to Terraform AVM contribution guide.
I get the error ‘The repository ********** already exists on this account’ when I try to create a new repository, what should I do?
If you get this error, it means that the repository already exists in the Azure GitHub Organization. This can happen if someone has already created a repository with the same name in the past and then archived it.
To determine if this is the case you’ll need to navigate to the Microsoft Open Source Management Portal, then search for the repository name you are trying to create. Click on the repository and you will find the owner. Reach out the owner to ask them to transfer the repo to you or delete it. You’ll want them to delete it if it was not created from the template.
Where can I test my module during development?
During initial module development module owners/developers need to use your own environment (Azure subscriptions) to test module. In later phase, during publishing process, we will conduct automated test that will use AVM dedicated environment.
Updating and managing a module
I’m already using a module today, but its missing a feature, what should I do?
You should use GitHub issues to propose changes or improvements for specific module. Issue request will be routed to module owner that MUST respond to logged issues as per the defined support statement. In case that module currently don’t have owner, AVM Core Team will handle request.
I am using module without owner. What will happened if I need update?
AVM core team will work to assign owner for every module, but it can happen during a time that there are modules without owner. If you would like to own that module, feel free to ask to take ownership. At this moment, only Microsoft FTEs can be module owners.
How will the support SLAs be automatically enforced?
All issues created in a module repo will be automatically be picked up and tracked by the GitHub Policy Service. This service will take the necessary steps when escalation is needed as per the SLAs defined in the Module Support chapter.
Process Overview
This page provides an overview of the contribution process for AVM modules.
New Module Proposal & Creation
Important
Each AVM module MUST have a Module Proposal issue created and approved by the AVM core team before it can be created/migrated!
---
config:
nodeSpacing: 20
rankSpacing: 20
diagramPadding: 5
padding: 5
useWidth: 100
flowchart:
wrappingWidth: 400
padding: 5
---
flowchart TD
ModuleIdea[Consumer has an idea for a new AVM Module] -->CheckIndex(Check AVM Module Indexes)
click CheckIndex "/Azure-Verified-Modules/indexes/"
CheckIndex -->IndexExistenceCheck{Is the module<br>in the index?}
IndexExistenceCheck -->|No|A
IndexExistenceCheck -->|Yes|EndExistenceCheck(Review existing/proposed AVM module)
EndExistenceCheck -->OrphanedCheck{ Is the module<br>orphaned? }
click OrphanedCheck "/Azure-Verified-Modules/specs/shared/module-lifecycle/#orphaned-avm-modules"
OrphanedCheck -->|No|ContactOwner[Contact module owner,<br> via GitHub issues on the related <br>repo, to discuss enhancements/<br>bugs/opportunities to contribute etc.]
OrphanedCheck -->|Yes|OrphanOwnerYes(Locate the related issue <br> and comment on:<br> - A feature/enhancement suggestion <br> - Indicating you wish to become the owner)
click OrphanOwnerYes "/Azure-Verified-Modules/specs/shared/module-lifecycle/#orphaned-avm-modules"
OrphanOwnerYes -->B
A[[ Create Module Proposal ]] -->|GitHub Issue/Form Submitted| B{ AVM Core Team<br>Triage }
click A "https://aka.ms/avm/moduleproposal"
click B "/Azure-Verified-Modules/help-support/issue-triage/avm-issue-triage/#avm-core-team-triage-explained"
B -->|Module Approved for Creation| C[["Module Owner(s) Identified & assigned to GitHub issue/proposal" ]]
B -->|Module Rejected| D(Issue closed with reasoning)
C -->E[[ Module index CSV files updated by AVM Core Team]]
click E "/Azure-Verified-Modules/indexes/"
E -->E1[[Repo/Directory Created following the <br> Contribution Guide ]]
click E1 "/Azure-Verified-Modules/contributing/"
E1 -->F("Module Developed by Owner(s) & their Contributors")
F -->G[[ Module & AVM Compliance Tests ]]
click G "/Azure-Verified-Modules/spec/SNFR3"
G -->|Tests Fail|I(Modules/Tests Fixed <br> To Make Them Pass)
I -->F
G -->|Tests Pass|J[[Version 0.1.0 created]]
J -->K[[Publish to Bicep/Terraform Registry]]
K -->L(Take Feedback from v0.1.0 Consumers)
L -->M{Anything<br>to be resolved <br> before 1.0.0<br>release? }
click M "/Azure-Verified-Modules/contributing/process/#avm-preview-notice"
M -->|Yes|FixPreV1("Module feedback incorporated by Owner(s) & their Contributors")
FixPreV1 -->PreV1Tests[[Self & AVM Module Tests]]
PreV1Tests -->|Tests Fail|PreV1TestsFix(Modules/Tests Fixed To Make Them Pass)
PreV1TestsFix -->N
M -->|No|N[[Publish 1.0.0 Release]]
N -->O[[Publish to IaC Registry]]
O -->P[[ Module BAU Starts ]]
click P "/Azure-Verified-Modules/help-support/module-support/"
Provide details for module proposals
When proposing a module, please include the information in the description that is mentioned for the triage process here:
The AVM framework continues to evolve, and several elements, such as Continuous Integration (CI) processes, module specifications and corresponding specificationβvalidation coverage, are not yet fully implemented. Hence, modules MUST NOT be published at version 1.0.0 or higher at this time.
All module MUST be published as a 0.x.y minor version (e.g., 0.1.0, 0.1.1, 0.2.0, etc.) until the AVM team provides guidance that publishing v1.0.0 is allowed.
However, it is important to note that this DOES NOT mean that the modules cannot be consumed and utilized. They CAN be leveraged in all types of environments (dev, test, prod etc.). Consumers can treat them just like any other IaC module and raise issues or feature requests against them as they learn from the usage of the module. Consumers should also read the release notes for each version, if considering updating to a more recent version of a module to see if there are any considerations or breaking changes etc.
Module Owner Has Issue/Is Blocked/Has A Request
In the event that a module owner has an issue or is blocked due to specific AVM missing guidance, test environments, permission requirements, etc. they should follow the below steps:
Tip
Common issues/blockers/asks/request are:
Subscription level features
Resource Provider Registration
Preview Services Enablement
Entra ID (formerly Azure Active Directory) configuration (SPN creation, etc.)
Please note for module specific issues, these should be logged in the module’s source repository, not the AVM repository.
Terraform Contribution Guide
Important
Every new AVM Terraform module MUST use AzAPI for all control-plane resources and supported data-plane operations. AzureRM is permitted only for a specific unsupported data-plane/non-ARM API operation under the narrow TFFR3 exception. This rule applies to the root module, submodules, examples, end-to-end tests, Terraform tests, fixtures, and documentation snippets; supporting control-plane resources in every surface must use AzAPI.
While this page describes and summarizes important aspects of contributing to AVM, it only references some of the shared and language specific requirements.
Therefore, this contribution guide MUST be used in conjunction with the Terraform specifications. All AVM modules MUST meet the applicable requirements in those specifications.
Summary
This section lists AVM’s Terraform-specific contribution guidance.
AVM TFLint Rules β custom rules, their requirements, and supported overrides
Repository Setup β creating a new module repository (owners only)
Subsections of Terraform Modules
Prerequisites
GitHub Account
To contribute, you need a GitHub account. If you are a Microsoft employee, your account must be linked to your corporate identity and you must be a member of the Azure organization.
Module Owner Access (Microsoft FTEs only)
Note
This step is only required if you are (or are becoming) a Terraform module owner. External contributors and one-off contributors do not need this access.
Access for Terraform module owners is granted via the Azure Verified Modules (AVM) Module Contributors Entra access package. Request access here:
Once approved, you will be added to the azure-verified-modules-module-contributors Entra group, which is the source of truth for who is authorized to own and approve changes on AVM Terraform module repositories.
Tip
Until your access request is approved, you can continue to contribute by using JIT elevation and by raising PRs that are approved by an existing module owner.
Required Tooling
Tip
Avm.Authoring supports Windows, Linux, and macOS. Use PowerShell 7.4 or later on every platform.
Install-PSResource Avm.Authoring
Import-Module Avm.Authoring
avm doctor
Install-Module Avm.Authoring -Scope CurrentUser remains available for environments that use PowerShellGet v2. Run avm update to upgrade an existing installation. The module downloads, verifies, and caches its managed tools, including Terraform, on demand. Run avm doctor --install to preload every supported tool. Docker and Podman are not required.
This guide covers the end-to-end contribution flow for AVM Terraform modules. Whether you are a module owner or an external contributor, the core workflow is the same β the key differences are called out using tabs below.
Important
Every new AVM Terraform module MUST use AzAPI for every control-plane resource and supported data-plane operation. AzureRM is permitted only for a specific unsupported data-plane/non-ARM API operation under the narrow TFFR3 exception. The same rule applies to submodules, examples, end-to-end tests, Terraform tests, fixtures, and documentation snippets.
This guide MUST be used in conjunction with the Terraform specifications. All AVM modules must meet the requirements described in those specifications.
Install-Module Avm.Authoring -Scope CurrentUser is also supported for environments that use PowerShellGet v2. Run avm with no arguments to list the supported verbs, then verify the installed version and diagnose your local environment:
avm
avm version
avm doctor
Run avm update whenever a newer Avm.Authoring release is available. Avm.Authoring downloads, verifies, and caches Terraform, TFLint, terraform-docs, Conftest, and mapotf on demand. Docker or Podman is not required. You can inspect or manage the tool cache:
avm tool list
avm tool which terraform
avm tool install terraform
avm doctor --install
Warning
The local Avm.Authoring migration and the centrally managed CI workflow rollout are separate changes. Do not pre-emptively rename required checks. After the updated workflow has run on a pull request, update branch protection to use the exact check names reported by that workflow. Per-example checks are derived from the repository’s example folders.
Overview
---
config:
nodeSpacing: 20
rankSpacing: 20
diagramPadding: 50
padding: 5
flowchart:
wrappingWidth: 300
padding: 5
layout: elk
elk:
mergeEdges: true
nodePlacementStrategy: LINEAR_SEGMENTS
---
flowchart TD
Z("1 - Fork [optional]")
click Z "#1-fork-optional"
A(2 - Branch)
click A "#2-branch"
B(3 - Implement your code change)
click B "#3-implement-your-code-change"
C(4 - Run avm pre-commit)
click C "#4-run-avm-pre-commit"
C2(5 - Run pr-check and test tiers locally)
click C2 "#5-run-pr-check-and-test-tiers-locally"
D(6 - Raise or Update PR)
click D "#6-raise-or-update-pr"
E("7 - Approve and monitor CI tests [owner]")
click E "#7-approve-and-monitor-ci-tests"
F{Tests passing?}
G(8 - Review and merge PR)
click G "#8-review-and-merge-pr"
H(9 - Cut a release)
click H "#9-cut-a-release"
Z --> A
A --> B
B --> C
C --> C2
C2 --> D
D --> E
E --> F
F -->|no| B
F -->|yes| G
G --> H
1. Fork [optional]
Note
This step is only needed if you do not have write access to the module repository. Module owners and invited collaborators can skip to step 2.
A fork is your own copy of the repository under your GitHub account. It lets you make changes without needing write access to the upstream repo. Once your changes are ready, you raise a pull request from your fork back to the original repository.
Navigate to the module repository in the Azure GitHub organization.
Click the Fork button in the top right.
Select your GitHub account (or organization) as the destination.
Click Create fork.
Clone your fork locally:
git clone https://github.com/<your-username>/terraform-azure-avm-res-<rp>-<modulename>.git
cd terraform-azure-avm-res-<rp>-<modulename>
Keep your fork in sync with the upstream repository before creating a new branch. You can do this from the GitHub UI by clicking Sync fork on your fork’s main page, or locally:
Create a branch from main to work on your changes:
git checkout -b <your-branch-name>
If this is a new module and the repository does not exist yet, module owners should first follow the Repository Creation Process.
Note
If the module repository does not exist yet, check the Terraform Resource Modules index for the module owner’s contact details (PrimaryModuleOwnerGHHandle column).
3. Implement your code change
Before writing code, review the Terraform specifications and composition guidelines to ensure your contribution complies with AVM’s design principles. For a new module, confirm first that every control-plane resource and supported data-plane operation uses AzAPI. Any AzureRM block must satisfy and document the unsupported data-plane exception in TFFR3.
Once you’ve made your changes, stage, commit, and push them:
git add -A
git commit -m "feat: description of your change"git push
Lifecycle hooks
Some examples need setup work before Terraform runs β deploying prerequisites, generating a terraform.tfvars, or seeding a random prefix. AVM supports optional hook scripts for this:
Terraform configurations created for examples, end-to-end tests, tests, or fixtures are part of the module repository and MUST follow TFFR3. Use AzAPI for every supporting control-plane resource. AzureRM may be configured or exercised only when the test covers the module’s documented unsupported data-plane operation; it must not be used to make setup more convenient.
Hook
Location
Runs
pre.ps1
examples/<name>/
before Terraform commands for the example during policy checks and e2e tests
post.ps1
examples/<name>/
after the example, including when its pre-hook or Terraform initialization fails
tflint-pre.ps1
examples/<name>/
after terraform init, before TFLint
setup.ps1
tests/<tier>/ or modules/<name>/tests/<tier>/
before terraform init and terraform test for the target
Warning
Hooks must be PowerShell. Avm.Authoring rejects per-example pre.sh, post.sh, and tflint-pre.sh files, plus setup.sh and teardown.sh files under tests/<tier>/, on presence alone. Adding a .ps1 while leaving the corresponding .sh in place still fails before Terraform runs:
The terraform unit test engine runs PowerShell hooks only.
Refactor these shell hooks to '.ps1': tests/unit/setup.sh
Legacy root-level examples/setup.sh and examples/teardown.sh files are different: Avm.Authoring does not execute or reject them, and there is no global PowerShell equivalent. Move required logic into idempotent per-example pre.ps1 and post.ps1 hooks. Coordinate removal of legacy global hooks with the repository’s centrally managed CI workflow migration.
Each hook runs in its own isolated pwsh subprocess, so environment variables it exports do not reach subsequent Terraform commands. An e2e pre.ps1 can pass values by writing KEY=VALUE lines to examples/<name>/.env; the runner reads the file after the hook and passes the values to the example’s Terraform subprocesses. A unit or integration setup.ps1 uses a .env file at its target root: the repository root or modules/<name>/. For these test hooks, the .env file is two directories above setup.ps1, not beside it.
Because hooks are invoked from an isolated process, anchor paths on $PSScriptRoot rather than relying on the current working directory. Note also that PowerShell does not stop on a failed native command the way set -e does in bash β check $LASTEXITCODE after each Terraform call and throw so a failed hook surfaces immediately instead of later as a confusing downstream error.
4. Run avm pre-commit
Before raising a pull request, run pre-commit to update your files:
avm pre-commit
For Terraform modules, this command:
Synchronizes the centrally governed managed files, which can add, update, or remove files.
Applies deterministic fixes for AVM convention rules.
Runs mapotf transformations.
Formats Terraform files.
Regenerates documentation.
The command intentionally updates the working tree. Review every change it makes, then commit and push again:
git add -A
git commit -m "chore: pre-commit fixes"git push
5. Run pr-check and test tiers locally
After committing the pre-commit changes, run the broader pull request checks:
az login
avm pr-check
avm pr-check requires a clean Git working tree and Azure credentials because its Conftest policy checks create Terraform plans for the examples. It checks managed-file, formatting, and transformation drift, then runs TFLint, Conftest policy checks, AVM convention checks, terraform validate, and documentation drift checks. It does not run the Terraform test tiers; those remain separate commands so failures are reported independently.
Unit testing
AVM convention checks require a unit test fixture under tests/unit. Use mocked providers to keep unit tests fast and free of external dependencies:
avm test unit
The command also runs unit test tiers found under direct modules/<name>/ submodules.
Integration testing
Integration tests under tests/integration deploy real resources and require Azure credentials:
az login
avm test integration
The command also runs integration test tiers found under direct modules/<name>/ submodules. A repository without integration tests reports the tier as skipped rather than passed.
Local e2e testing
Run the e2e test tier to deploy, check idempotency, and destroy resources for each example:
az login
avm test e2e
This tier requires real Azure credentials. Azure CLI authentication is sufficient for local development; no environment variables or service principals are needed. To run one example while iterating:
avm test e2e --example <name>
An example containing .e2eignore is excluded. Apply failures caused by transient region, SKU capacity, or quota errors are destroyed and retried up to two times by default; use -MaxRetry 0 to disable retries.
Local e2e testing is especially useful for external contributors, since only module owners can approve credentialed CI e2e runs.
6. Raise or Update PR
Tip
Raise your PR early β don’t wait until everything is perfect. An early PR lets you run validation and test tiers in CI and get feedback sooner. You can continue pushing commits to the same branch.
Navigate to the upstream repository on GitHub and click New pull request.
Set the base repository to the upstream AVM repo and base branch to main.
Set your head repository and compare branch to your fork and branch.
Click Create pull request.
Navigate to the repository on GitHub and click New pull request.
Set the base branch to main and the compare branch to your branch.
Click Create pull request.
7. Approve and monitor CI tests
Note
Credentialed CI jobs require approval from a module owner. Unit tests do not require Azure credentials, but external contributors should still run avm pr-check and all applicable test tiers locally before this step.
Once a PR is created, CI workflows are triggered automatically. A centrally managed Azure test subscription is provided for credentialed jobs, so contributors do not configure CI credentials themselves.
What CI runs
The centrally managed workflow keeps validation and test tiers in separate jobs so a failure produces an actionable signal:
PR validation β runs the equivalent of avm pr-check: managed-file and generated-file drift checks, Terraform formatting, mapotf transformations, TFLint, Conftest policy checks, AVM convention checks, terraform validate, and documentation checks.
Unit tests β runs avm test unit.
Integration tests β runs avm test integration when the repository has integration tests.
End-to-end tests β discovers runnable examples and tests each example independently with the equivalent of avm test e2e --example <name>.
Each e2e job deploys the example, checks idempotency with terraform plan, and destroys the resources. Examples containing .e2eignore are excluded.
If tests fail
Go back to step 3 β fix the issue, run avm pre-commit again, push your changes, and the CI tests will re-run automatically on the same PR.
Running e2e for external contributions
When approving a PR from an external contributor:
Review the code for security β check for any malicious code or changes to workflow files before running tests. If found, close the PR and report the contributor.
Create a release branch from main (e.g. release/<description>).
Change the PR’s base branch to the release branch and merge it.
Create a new PR from the release branch to main β this triggers the validation and test jobs.
Approve the run and wait for results.
If tests fail, send back to the contributor to fix and repeat from step 3.
Running e2e for your own contributions
For your own PRs, the tests trigger automatically β approve the run and wait for results.
8. Review and merge PR
Important
PR approvals are enforced on all AVM Terraform module repositories. A PR cannot be merged until it has been approved by an authorized module owner.
Finding an approver
First port of call β find a friendly module owner. Look up another active Terraform module owner from the azure-verified-modules-module-contributors Entra group and request a review from them directly. This is the fastest path to approval.
If no module owner is available, fall back to the AVM core team:
Assign the @Azure/azure-verified-modules-engineering-owners GitHub team as a reviewer on the PR.
Apply the Β Needs: Core Team π§Β label so the request is picked up during core team triage.
PR is submitted by a module owner
PR is submitted by anyone, other than the module owner
Module has a single module owner
AVM core team or in case of Terraform only, the owner of another module approves the PR
Module owner approves the PR
Module has multiple module owners
Another owner of the module (other than the submitter) approves the PR
One of the owners of the module approves the PR
Address any review comments and push updates to your branch.
Request a re-review once changes are made.
The module owner will merge the PR once approved and tests pass.
For a brand new module being published for the first time, get the module reviewed by the AVM Core team by following the AVM Review Process before merging.
Owner responsibilities
Watch PR and issue activity for your module and respond in a timely manner as per SNFR11.
After the PR is merged to main, create a release via GitHub Releases:
Go to the Releases tab and click Draft a new release.
Set Target to the main branch.
Type a new tag (e.g. v0.1.0 for first publish, or increment for subsequent releases). Tags MUST include the v prefix.
Use Generate release notes and credit external contributors.
Click Publish release.
First module publish
For a brand new module, contact the AVM core team (e.g. via the AVM - Module Triage project) to request initial publication to the HashiCorp Registry. Subsequent releases are published automatically.
Important
Continue publishing in the v0.x.y range (e.g., v0.1.0, v0.1.1, v0.2.0) until the AVM team notifies you that v1.0.0 is allowed.
Common mistakes to avoid
Search and update TODO comments that come from the template β remove them once addressed.
Do not commit terraform.lock.hcl β it is excluded by .gitignore.
Update _header.md and SUPPORT.md.
Do not commit terraform.tfvars files.
Do not commit .env files created by lifecycle hooks.
Do not add shell (.sh) lifecycle hooks β see Lifecycle hooks.
Terraform Composition
Important
AzAPI is the required Azure provider for new AVM Terraform modules. Every new resource, pattern, or utility module MUST use the Azure/azapi provider for every control-plane resource and supported data-plane operation.
AzureRM is permitted only for a specific unsupported data-plane/non-ARM API operation under the narrow TFFR3 exception. The exception applies only to that operation; supporting control-plane resources in the module, submodules, examples, end-to-end tests, Terraform tests, fixtures, and documentation snippets MUST use AzAPI.
This guide MUST be used in conjunction with the Terraform specifications. All AVM modules MUST meet the applicable requirements in those specifications.
This repo will be created by the Module Owners and the AVM Core team collaboratively, including the configuration of permissions as per SNFR9
Directory and File Structure
Below is the directory and file structure expected for each AVM Terraform repository/module. See the Terraform AVM template repository. The azurerm segment in this legacy repository name is not a provider choice; modules created from the template MUST use AzAPI.
tests/ - (for unit tests and integration tests using Terraform test)
unit/ - (.tftest.hcl files for required unit testing with Terraform test)
setup.ps1 - (optional setup hook)
integration/ - (optional .tftest.hcl files for integration testing with Terraform test)
setup.ps1 - (optional setup hook)
modules/ - (for sub-modules only if used; each submodule root MUST be a direct modules/<name>/ child)
examples/ - (all examples must deploy successfully without requiring input and use AzAPI for supporting control-plane resources; AzureRM may appear only when exercising the module’s documented unsupported data-plane exception - these are customer facing and run as end-to-end tests)
<at least one folder> - (at least one example that uses the variable defaults minimum/required parameters/variables only)
pre.ps1 - (optional setup hook)
post.ps1 - (optional cleanup hook)
tflint-pre.ps1 - (optional setup hook for TFLint)
.e2eignore - (optional marker that excludes the example from e2e testing)
<other folders for examples as required> - (each example root MUST be a direct examples/<name>/ child)
/... - (Module files that live in the root of module directory)
_header.md - (required for documentation generation)
_footer.md - (required for documentation generation)
main.tf
locals.tf
variables.tf
outputs.tf
terraform.tf
README.md (autogenerated)
main.resource1.tf (If a larger module you may chose to use dot notation for each resource)
locals.resource1.tf
See Lifecycle hooks for hook execution, environment, and migration guidance.
Nested Terraform module and example roots are prohibited. Avm.Authoring convention validation enforces the one-layer modules/* and examples/* structure; see TFRMNFR1.
Code Styling
This section points to conventions to be followed when developing a module.
This section is only relevant for contributions to resource modules.
To meet RMFR4 and RMFR5 AVM resource modules must leverage consistent interfaces for all the optional features/extension resources supported by the AVM module primary resource.
Every Terraform AVM module MUST be built with AzAPI, and every resource module MUST implement the following AzAPI patterns. The cross-references point at the normative specs β this section only summarises them so that nothing here is missed during scaffolding.
Use Azure/azapi for every control-plane resource and supported data-plane operation in the module, submodules, examples/e2e tests, Terraform tests, fixtures, and documentation snippets. AzureRM is permitted only for a documented data-plane/non-ARM operation that AzAPI cannot implement.
Expose the parent scope as a single required parent_id string variable. Do not expose resource_group_name or any other scope-specific input. Validate with provider::azapi::parse_resource_id against the expected parent type.
Implement every ARM subresource as a Terraform submodule under a direct modules/<subresource-singular-name>/ child. Parent modules MUST reference submodules, and submodules MUST be independently consumable. Keep submodule primary resources single-instance only (no count / for_each on azapi_resource.this); cardinality belongs at the module call site. Nested module roots are prohibited.
Name the primary azapi_resourcethis. Satellite resources MUST be named after what they represent (e.g. azapi_resource.lock, azapi_resource.role_assignment, azapi_resource.diagnostic_setting, azapi_resource.private_endpoint), not this.
Always set response_export_values on every AzAPI resource (use [] when nothing needs exporting). Include any read-only properties the module’s outputs or downstream resources depend on.
Set replace_triggers_refs only when body paths require replacement. The non-empty static list MUST contain valid, unique JMESPath expressions; omit the argument when no paths are needed. name and location are already triggers, so don’t repeat them.
Source the type argument of every AzAPI resource from a single resource_types object variable instead of hard-coding type strings. Use one optional key per resource, defaulted to the tested API version, and cascade the relevant subset to each submodule.
Expose an ignore_body_changes object variable so consumers can suppress diffs on body paths derived from non-static values. Use one optional list(string) key per resource (same key naming as resource_types), collapse empty lists to null, and cascade the relevant nested slot to each submodule β never the parent’s own paths.
Validate every variable (or nested attribute) that holds an Azure ARM resource ID using can(provider::azapi::parse_resource_id("Microsoft.X/y", value)). Hand-rolled regex / startswith / length checks MUST NOT be used.
Use the standard file layout (terraform.tf, variables.tf, outputs.tf, main.tf, locals.tf). Larger modules MAY split main.tf into main.<topic>.tf files.
The interface schema files under static/includes/interfaces/tf/ are the canonical, copy-pasteable templates for the variables described by these specs. Treat them as authoritative.
Telemetry
To meet the requirements of SFR3 & SFR4, we use the modtm telemetry provider. 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 enabled by default through main.telemetry.tf, which is generated and maintained by Avm.Authoring. You do not need to change this configuration.
Make sure that the modtm provider is listed under the required_providers section in the module’s terraform.tf file using the following entry. This is also validated by the linter.
The AVM module review is a critical step before an AVM Terraform module gets published to the Terraform Registry and made publicly available for customers, partners and wider community to consume and contribute to. It serves as a quality assurance step to ensure that the AVM Terraform module complies with the Terraform specifications of AVM. The below process outlines the steps that both the module owner and module reviewer need to follow.
Important
A new module is not eligible for review or publication unless it uses AzAPI for every control-plane resource and supported data-plane operation. Reviewers MUST reject any AzureRM block that does not implement and document the narrow unsupported data-plane/non-ARM API exception in TFFR3. Review the root module, submodules, examples/end-to-end tests, Terraform tests, fixtures, and documentation snippets; all supporting control-plane resources must use AzAPI.
The module owner completes the development of the module in their branch or fork.
The module owner submits a pull request (PR) titled AVM-Review-PR and ensures that all checks are passing on that PR as that is a pre-requisite to request a review.
The module owner assigns the @Azure/azure-verified-modules-engineering-owners GitHub team as reviewer on the PR.
The module owner leaves the following comment as it is on the module proposal in the AVM - Module Triage project by searching for their module proposal by name there.
β AVM Terraform Module Review Request
I have completed my initial development of the module and I would like to request a review of my module before publishing it to the Terraform Registry. The latest code is in a PR titled [AVM-Review-PR](REPLACE WITH URL TO YOUR PR) on the module repo and all checks on that PR are passing.
The AVM team moves the module proposal from “In Development” to “In Review” in the AVM - Module Triage project.
The AVM team will assign a module reviewer who will open a blank issue on the module titled “AVM-Review” and populate it with the below mark down. This template already marks the specs as compliant which are covered by the checks that run on the PR. There are some specs which don’t need to be checked at the time of publishing the module therefore they are marked as NA.
β AVM Terraform Module Review Issue
Dear module owner,
As per the module ownership requirements and responsibilities at the time of [assignment](REPLACE WITH THE LINK TO THE AVM MODULE PROPOSAL), the AVM Team is opening this issue, requesting you to validate your module against the below AVM specifications and confirm its compliance.
Please don’t close this issue and merge your AVM-Review-PR until advised to do so. This review is a prerequisite for publishing your module’s v0.1.0 in the Terraform Registry. The AVM team is happy to assist with any questions you might have.
Requested Actions
Complete the below task list by ticking off the tasks.
Complete the below table by updating the Compliant column with Yes, No or NA as possible values.
Please use the comments columns to provide additional details especially if the Compliant column is updated to No or NA.
Tasks
Address comments on AVM-Review-PR if any
Ensure that all checks on AVM-Review-PR are passing
Confirm every control-plane resource and supported data-plane operation uses AzAPI across the root module, submodules, examples/e2e tests, Terraform tests, fixtures, and documentation snippets. Any AzureRM block must implement and document the narrow TFFR3 unsupported data-plane exception.
Tick this to acknowledge specs with comment “Module Owner to action this spec post-publish as appropriate” in the table below.
Please update the _header.md file as it contains instructions which - once actioned - need to be replaced with Module Name and Description.
The module reviewer can update the Compliance column for specs in line 42 to 47 to NA, in case the module being reviewed isn’t a pattern module.
The module reviewer reviews the code in the PR and leaves comments to request any necessary updates.
The module reviewer assigns the AVM-Review issue to the module owner and links the AVM-Review Issue to the AVM-Review-PR so that once the module reviewer approves the PR and the module owner merges the AVM-Review-PR, the AMV-Review issue is automatically closed. The module reviews responds to the module owner’s comment on the Module Proposal in AVM Repo with the following
Thank you for requesting a review of your module. The AVM module review process has been initiated, please perform the **Requested Actions** on the AVM-Review issue on the module repo.
The module owner updates the check list and the table in the AVM-Review issue and notifies the module reviewer in a comment.
The module reviewer performs the final review and ensures that all checks in the checklist are complete and the specifications table has been updated with no requirements having compliance as ‘No’.
The module reviewer approves the AVM-Review-PR, and leaves the following comment on the AVM-Review issue with the following comment.
Thank you for contributing this module and completing the review process per AVM specs. The AVM-Review-PR has been approved and once you merge it that will close this AVM-Review issue. Please create a release with an initial minor version of `v0.1.0` (tags **MUST** include the `v` prefix) and then contact the AVM core team to publish this module to the HashiCorp Terraform Registry via HCP Terraform. Please continue publishing future versions in the v0.x.y minor range (e.g., `v0.1.0`, `v0.1.1`, `v0.2.0`, etc.) until the AVM team notifies you that publishing `v1.0.0` is allowed.
**Requested Action**: Once the AVM core team has published the module, please update your [module proposal](REPLACE WITH THE LINK TO THE MODULE PROPOSAL) with the following comment.
"The initial review of this module is complete, and the module has been published to the registry by the AVM core team. Requesting AVM team to close this module proposal and mark the module available in the module index.
Terraform Registry Link: <REPLACEWITHTHELINKOFTHEMODULEINTERRAFORMREGISTRY>
GitHub Repo Link: <REPLACEWITHTHELINKOFTHEMODULEINGITHUB>"
Once the module owner perform the requested action in the previous step, the module reviewer updates the module proposal by performing the following steps:
Assign label Status: Module Available :green_circle: to the module proposal.
Update the module index excel file and CSV file by creating a PR to update the module index and links the module proposal as an issue that gets closed once the PR is merged which will move the module proposal from “In Review” to “Done” in the AVM - Module Triage project.
Advanced Topics & FAQ
This page covers advanced scenarios and frequently asked questions that go beyond the standard contribution flow.
Offline and air-gapped module mirroring
The offline sync utility mirrors AVM Terraform modules and rewrites registry dependencies as git references for offline or air-gapped environments.
<br
This utility is an example for advanced users familiar with PowerShell, Git, and Terraform module management. It is provided as-is and is not supported for production use.
Using a custom Azure test subscription
By default, CI end-to-end tests run against a centrally managed Azure subscription. If your module requires a different environment (e.g. due to quota limits or tenant-level deployments), you can override the defaults.
Create a user-assigned managed identity in your target Azure environment.
Create GitHub federated credentials for the managed identity, using the module’s GitHub organization and repository. Select entity type environment and set the name to test.
TFLint checks AVM spec compliance using the AVM custom ruleset. See the AVM TFLint rules guide for every enabled AVM rule, its applicability, exact disable block, and override precedence.
To override a rule, create one of the following HCL files in the root of your module:
File
Scope
avm.tflint.override.hcl
Root module
avm.tflint_module.override.hcl
Submodules
avm.tflint_example.override.hcl
Examples
modules/<name>/avm.tflint.override.hcl
One direct submodule
examples/<name>/avm.tflint.override.hcl
One direct example
Example:
# Disable the required resource id output rule β this is a pattern module.
rule"required_output_rmfr7" {
enabled =false}
Include a comment explaining why the rule is disabled.
The target-directory override takes precedence over the matching repository-wide scope override and applies only to that direct submodule or example. AVM permits only modules/* and examples/* Terraform roots; nested module or example roots are prohibited and rejected by Avm.Authoring convention validation. Use a target override instead of weakening an all-submodule or all-example override.
Excluding examples from end-to-end testing
Create a file called .e2eignore in the example directory. Its contents should explain why the example is excluded from tests.
Global test setup and teardown
Avm.Authoring has no global setup or teardown hook. It does not execute or reject the legacy files:
examples/setup.sh
examples/teardown.sh
Move required setup and cleanup into idempotent per-example pre.ps1 and post.ps1 hooks. Coordinate removal of legacy global scripts with the repository’s centrally managed CI workflow migration because older workflows can still invoke them.
Per-example pre and post scripts
For example-specific setup/teardown:
examples/<example_name>/pre.ps1 (optional) β runs before Terraform commands for the example.
examples/<example_name>/post.ps1 (optional) β always runs after the example, including after a pre-hook or initialization failure.
Shell equivalents are rejected. Each PowerShell hook runs in an isolated process; see Lifecycle hooks for .env, path, and error-handling guidance.
Repository synchronization PRs
Repository sync regularly compares each module repository with the shared managed files and opens a PR when updates are available. These PRs are normally merged automatically. Module owners will be informed about one-off PRs that require intervention.
These PRs do not change module code, so no new release is needed.
Eventual consistency
The Azure Resource Manager API can be eventually consistent. For example, data plane role assignments may not be available immediately after creation.
Use the AzAPI provider’s retry functionality to handle eventual consistency instead of arbitrary time_sleep delays. The AzAPI provider supports configurable retry with retry blocks that can match on specific error codes, providing a more reliable and efficient approach.
Repository Creation Process
Important
This page is for module owners only. If you are an external contributor, skip to the contribution flow.
Important
Every repository created through this process MUST use AzAPI for every control-plane resource and supported data-plane operation. AzureRM is permitted only for a specific unsupported data-plane/non-ARM API operation under the narrow TFFR3 exception. The exception must be documented and applies only to that operation in the root module, submodules, examples, end-to-end tests, Terraform tests, fixtures, and documentation snippets.
Important
If this process is not followed exactly, it may result in your repository and any in-progress code being permanently deleted.
1. Add yourself to the Module Owners Team and Open Source orgs
If you have already completed these steps, skip to step 2.
Open the Open Source Portal and ensure your GitHub account is linked to your Microsoft account.
Open the Open Source Portal and ensure you are a member of the Azure and Microsoft organizations.
The script will pause and prompt you to configure the Open Source Portal. Follow the link in the script output.
β If you see the Complete Setup link
Click Complete Setup and use the following settings:
Question
Answer
Classify the repository
Production
Assign a Service tree or Opt-out
Azure Verified Modules / AVM
Direct owners
Add yourself, jaredholgate, and jatracey. Add azure-verified-modules-module-owners as fallback security group. You add yourself temporarily so you can configure JIT in step 4; you will remove yourself afterwards.
Public open source licensed project?
Yes
What type of open source?
Sample code
License
MIT
All code created by your team?
Yes
Telemetry?
Yes, telemetry
Cryptography?
No
Project name
Azure Verified Module (Terraform) for ‘module name’
Project version
1
Project description
Azure Verified Module (Terraform) for ‘module name’. Part of AVM project - https://aka.ms/avm
Business goals
Create IaC module accelerating Azure deployment using Microsoft best practice.
Used in a Microsoft product?
Open source, can be leveraged in Microsoft services.
Security best practice?
Yes, use just-in-time elevation
Maintainer / Write permissions
Leave empty
Repository template / .gitignore
Uncheck both
Click Finish setup + start business review, then View repository, then Elevate your access.
β If you do NOT see the Complete Setup link
Go to the Compliance tab and fill out:
Direct owners: Add yourself, jaredholgate, and jatracey. Add azure-verified-modules-module-owners as fallback. You add yourself temporarily so you can configure JIT in step 4; you will remove yourself afterwards.
Classify the repository: Production
Service tree: Azure Verified Modules / AVM
Go back to Overview and click Elevate your access if available.
Return to the terminal and type yes to complete repository configuration.
Create a PR to install the Azure Verified Modules GitHub App.
4. Upgrade just-in-time access to JITv2
New repositories default to JIT v1. AVM repositories must be upgraded to JIT v2 and tied to the shared service-AVM-azure-verified-modules-module-owners rule, so that just-in-time elevation is governed centrally by the AVM team rather than by a repository-specific rule.
This is a one-off manual action in the Open Source Portal. You need Direct Owner access to the repository (configured in the previous step) to complete it.
Migrate the repository to JIT v2
Open the repository overview on the Open Source Portal: https://repos.opensource.microsoft.com/orgs/Azure/repos/<module name>.
In the right-hand sidebar, find the Improved Just-in-time (New) panel and click Next.
Review the concepts (Rule Version, Rule, Tie) and click Next.
Leave Require approval for elevation selected and click Upgrade <module name> now.
This migrates the repository to JIT v2 and creates a temporary repository-scoped starter rule. Reload the page and confirm the Just-in-time elevation section now shows JIT version: JIT v2.
Tie the repository to the shared AVM rule
On the repository overview, click Advanced JIT options, then select Propose a new tie.
Under Propose tying a new rule to this repository, enter the Rule ID service-AVM-azure-verified-modules-module-owners and click Review.
Confirm the details and click Create tie.
The tie is created in a pending approval state, so the temporary repository-scoped rule stays active until the tie is approved.
Info
The pending tie must be approved by an owner of the service-AVM-azure-verified-modules-module-owners rule (an AVM core team member). Ask the AVM core team to approve it. Once approved, just-in-time elevation for the repository is governed by the shared AVM rule and the temporary starter rule can be ignored.
Remove yourself as a Direct Owner
You were added as a Direct Owner so you could perform the JIT configuration above. Once you have finished both the JIT v2 upgrade and the shared-rule tie, remove your own account so that only jaredholgate and jatracey remain as Direct Owners.
On the Open Source Portal, open the repository’s Compliance tab.
Under Direct owners, remove your own account, leaving only jaredholgate and jatracey.
Info
Module owners retain day-to-day access through the azure-verified-modules-module-owners security group and just-in-time elevation, so you do not need to remain a Direct Owner.
5. Wait for the GitHub App and repository sync
After the app is installed, repository sync applies the shared repository configuration and managed files to complete the setup.
AVM TFLint Rules
This reference covers the custom AVM TFLint ruleset rules. It does not repeat rules provided by the standard Terraform TFLint plugin. AVM rules are enabled by default. An override is an exception to an AVM requirement and should be narrow, temporary where possible, and explained in the override file.
Rule applicability and overrides
Rules run in the scope that contains the applicable Terraform configuration:
All module scopes - the root module, each submodule, and each example independently.
Module scopes - the root module and each submodule independently; examples are excluded.
Root module - the published module root only.
To disable a rule, use the exact HCL shown in the Disable column. The configuration files and precedence rules are documented in TFLint configuration overrides.
Applicable AzAPI resources expose and apply timeouts.
All module scopes
rule "timeouts" { enabled = false }
Rule guidance
azapi data response export values
Applies TFFR4 to AzAPI data sources: declare response_export_values, including [] when no response fields are needed.
azapi replace triggers refs
Applies TFFR5. Omit replace_triggers_refs when no body paths require replacement. When present, it must be a non-empty static list of valid JMESPath expressions that identify body paths requiring replacement. Entries cannot be blank or duplicated, and cannot include name or location, because AzAPI already replaces the resource when either changes. When the body is statically evaluable, the rule verifies that each declared path resolves against it.
Authors remain responsible for identifying the properties that actually require replacement. Current Bicep-generated schemas do not reliably preserve create-only versus updateable mutability, so this rule validates declared paths but cannot prove that the list is semantically complete.
azapi resource tag
Applies TFFR9: set tags = var.tags exactly on types supported by the embedded AVM-generated capability snapshot, and omit tags for unsupported types. The rule skips dynamic or otherwise unevaluable type expressions.
The ruleset embeds its AVM-generated capability snapshot and works standalone. It does not consume, import, or query AzAPI, and does not accept an external snapshot path.
A weekly ruleset workflow compares the embedded snapshot with upstream data and opens a ruleset pull request when that data changes. Updated capability data ships with the next ruleset release. All users receive snapshot updates by upgrading the ruleset release.
azapi response export values
Applies TFFR4: every applicable managed AzAPI resource declares response_export_values, including [] when no fields are exported.
Applies TFNFR40: represent JSON or YAML structured values with jsonencode or yamlencode.
terraform module provider declaration
Applies TFNFR27: a published module contains no provider blocks; aliases are declared only through configuration_aliases and configured by its consumer.
terraform sensitive variable no default
Applies TFNFR23: a sensitive variable may default only to an empty collection.
terraform tf file
Applies TFNFR39: a module has exactly one terraform block and it is in terraform.tf.
The standard Terraform TFLint plugin validates required_version and provider requirement declarations. TFNFR25 and TFNFR26 explain the complementary AVM file-layout and ordering requirements.
TFLint configuration overrides
Avm.Authoring loads the following repository-root override files:
File
Default scope
avm.tflint.override.hcl
All root-module checks
avm.tflint_module.override.hcl
All submodule checks
avm.tflint_example.override.hcl
All example checks
modules/<name>/avm.tflint.override.hcl
One direct submodule
examples/<name>/avm.tflint.override.hcl
One direct example
Each file contains normal TFLint rule configuration. For example:
Avm.Authoring merges overrides in this order: the immutable AVM base configuration, the matching repository-root all-scope override, then the target-directory override. A submodule or example override is loaded only for its target directory and takes precedence over the matching all-submodule or all-example file. Use it when an exception is specific to one direct child module or example; do not weaken the corresponding repository-wide default.
AVM permits only one directory layer for Terraform submodule and example roots: modules/* and examples/*. Nested Terraform module or example roots are prohibited, so target overrides apply only to those direct scopes. Avm.Authoring convention validation enforces this structure.
Website Contribution Guide
Looking to contribute to the AVM Website, well you have made it to the right place/page. π
Follow the below instructions, especially the pre-requisites, to get started contributing to the library.
Context/Background
Before jumping into the pre-requisites and specific section contribution guidance, please familiarize yourself with this context/background on how this library is built to help you contribute going forward.
This site is built using Hugo, a static site generator, that’s source code is stored in the AVM GitHub repo (link in header of this site too) and is hosted on GitHub Pages, via the repo.
The reason for the combination of Hugo & GitHub pages is to allow us to present an easy to navigate and consume library, rather than using a native GitHub repo, which is not easy to consume when there are lots of pages and folders. Also, Hugo generates the site in such a way that it is also friendly for mobile consumers.
But I don’t have any skills in Hugo?
That’s okay and you really don’t need them. Hugo just needs you to be able to author markdown (.md) files and it does the rest when it generates the site π
Pre-Requisites
Read and follow the below sections to leave you in a “ready state” to contribute to AVM.
A “ready state” means you have a forked copy of the Azure/Azure-Verified-Modules repo cloned to your local machine and open in VS Code.
Run and Access a Local Copy of AVM Website During Development
When in VS Code you should be able to open a terminal and run the below commands to access a copy of the AVM website from a local web server, provided by Hugo, using the following address http://localhost:1313/Azure-Verified-Modules/:
cd docs
hugo server -D // you can add "--poll 700ms", if file changes are not detected
Software/Applications
To contribute to this website, you will need the following installed:
Tip
You can use winget to install all the pre-requisites easily for you. See the below section
Steps to do before contributing anything (after pre-requisites)
Run the following commands in your terminal of choice from the directory where you fork of the repo is located:
git checkout main
git pull
git fetch -p
git fetch -p upstream
git pull upstream main
git push
Doing this will ensure you have the latest changes from the upstream repo, and you are ready to now create a new branch from main by running the below commands:
git checkout main
git checkout -b <YOUR-DESIRED-BRANCH-NAME-HERE>
Top Tips
Sometimes the local version of the website may show some inconsistencies that don’t reflect the content you have created
If this happens, simply kill the Hugo local web server by pressing CTRL + C and then restart the Hugo web server by running hugo server -D from the docs/ directory.