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.

Subsections of Bicep Modules

Bicep Composition

Important

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.

Directory and File Structure

Each Bicep AVM module that lives within the Azure/bicep-registry-modules (BRM) repository in the avm directory will have the following directories and files:

  • tests/ - (for unit tests and additional E2E/integration if required - e.g. Pester etc.)
    • e2e/ - (all examples must deploy successfully - these will be used to automatically generate the examples in the README.md for the module)
  • modules/ - (for sub-modules only if used and NOT children of the primary resource. e.g. RBAC role assignments)
  • /... - (Module files that live in the root of module directory)
    • main.bicep (AVM Module main .bicep file and entry point/orchestration module)
    • main.json (auto generated and what is published to the MCR via BRM)
    • version.json (BRM requirement)
    • README.md (auto generated AVM Module documentation)

Directory and File Structure Example

/ Root of Azure/bicep-registry-modules
β”‚
β”œβ”€β”€β”€avm
β”‚   β”œβ”€β”€β”€ptn
β”‚   β”‚   └───apptiervmss
β”‚   β”‚       β”‚   main.bicep
β”‚   β”‚       β”‚   main.json
β”‚   β”‚       β”‚   README.md
β”‚   β”‚       β”‚   version.json
β”‚   β”‚       β”‚
β”‚   β”‚       β”œβ”€β”€β”€modules
β”‚   β”‚       └───tests
β”‚   β”‚           β”œβ”€β”€β”€unit (optional)
β”‚   β”‚           └───e2e
β”‚   β”‚               β”œβ”€β”€β”€defaults
β”‚   β”‚               β”œβ”€β”€β”€waf-aligned
β”‚   β”‚               └───max
β”‚   β”‚
β”‚   └───res
β”‚       └───compute
β”‚           └───virtual-machine
β”‚               β”‚   main.bicep
β”‚               β”‚   main.json
β”‚               β”‚   README.md
β”‚               β”‚   version.json
β”‚               β”‚
β”‚               β”œβ”€β”€β”€modules
β”‚               └───tests
β”‚                   β”œβ”€β”€β”€unit (optional)
β”‚                   └───e2e
β”‚                       β”œβ”€β”€β”€defaults
β”‚                       β”œβ”€β”€β”€waf-aligned
β”‚                       └───max
β”œβ”€β”€β”€other repo dirs...
└───other repo files...

For a new module (res or ptn), the files can be created automatically, once the parent folder exists. This example shows how to create a res module res/compute/virtual-machine.

Set-Location -Path ".\avm\"
New-Item -ItemType Directory -Path ".\res\compute\virtual-machine"
Set-AVMModule -ModuleFolderPath .\res\compute\virtual-machine

Code Styling

This section points to conventions to be followed when developing a Bicep template.

Casing

Use camelCasing as per BCPNFR8.

Input Parameters and Variables

Make sure to review all specifications of Category: Inputs/Outputs within the Bicep specification pages.

Tip

See examples in specifications SNFR14 and BCPNFR1.

Resources

Resources are primarily leveraged by resource modules to declare the primary resource of the main resource type deployed by the AVM module.

Make sure to review all specifications covering resource properties and usage.

Tip

See examples in specifications SFR1 and RMFR1.

Modules

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.

Tip

See examples in specification RMFR7.

Interfaces

Note

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):

// main.bicep:
param item object?

// main.test.bicep:
name: 'name'
item:
  {
    variant: 'Large'
    osType: 'Windows'
  }

Testing

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).

module testDeployment '../../../main.bicep' = [
  for iteration in ['init', 'idem']: {
    scope: resourceGroup
    name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
    params: {
      name: '${namePrefix}${serviceShort}001'
      item: {
        variant: 'Large'
        osType: 'Linux'
      }
    }
  }
]

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.

module testDeployment '../../../main.bicep' = [
  for iteration in ['init', 'idem']: {
    scope: resourceGroup
    name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}'
    params: {
      name: '${namePrefix}${serviceShort}001'
      location: resourceLocation
      item:{
        osType: 'Linux'
        variant: {
          size: 'Large'
        }
      }
    }
  }
]

Summary

Changes to modules (resource or pattern) can bei implemented in two ways.

  1. 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)
  2. 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"
  A --> B
  B --> C
  C --> D
  D --> E
  E -->|yes|F
  E -->|no|D

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.

---

config:
  logLevel: debug
  gitGraph:
    rotateCommitLabel: false
---

gitGraph LR:
  commit id:"Fork Repo"
  branch anchor
  checkout anchor
  commit id:"Sync Upstream/main" type: HIGHLIGHT
  branch avm-type-provider-resource-workflow
  checkout avm-type-provider-resource-workflow
  commit id:"Add Workflow File for Resource/Pattern"
  branch avm-type-provider-resource
  checkout main
  merge avm-type-provider-resource-workflow id: "merge workflow for GitHub Actions Testing" type: HIGHLIGHT
  checkout avm-type-provider-resource
  commit id:"Init"
  commit id:"Patch 1"
  commit id:"Patch 2"
  checkout main
  merge avm-type-provider-resource
Tip

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:

  1. Forks the Azure/bicep-registry-modules to your GitHub Account.
  2. Clones the repo locally to your machine, based on the location you specify in the parameter: -GitHubRepositoryPathForCloneOfForkedRepository.
  3. Prompts you and takes you directly to the place where you can enable GitHub Actions Workflows on your forked repo.
  4. Disables all AVM module workflows, as per Enable or Disable Workflows.
  5. Creates an User-Assigned Managed Identity (UAMI) and federated credentials for OIDC with your forked GitHub repo and grants it the RBAC roles of User Access Administrator & Contributor 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.
  6. 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

  1. 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.
  2. 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.
βž• New-AVMBicepBRMForkSetup.ps1 - PowerShell Helper Script

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" }

<#
.SYNOPSIS
This 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.

.DESCRIPTION
This 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 -ne 0) {
  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 account
do {
  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 locally
Write-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 -ne 0) {
  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 OIDC
if ($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 'Contributor' and 'User Access Administrator' 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 'User Access Administrator' -Scope "/subscriptions/$($GitHubSecret_ARM_SUBSCRIPTION_ID)" -ErrorAction Stop
  New-AzRoleAssignment -ApplicationId $newSpn.AppId -RoleDefinitionName 'Contributor' -Scope "/subscriptions/$($GitHubSecret_ARM_SUBSCRIPTION_ID)" -ErrorAction Stop
  Write-Host "RBAC Role Assignments of 'Contributor' and 'User Access Administrator' 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 'Contributor' and 'User Access Administrator' 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 'User Access Administrator' -Scope "/providers/Microsoft.Management/managementGroups/$($GitHubSecret_ARM_MGMTGROUP_ID)" -ErrorAction Stop
    New-AzRoleAssignment -ApplicationId $newSpn.AppId -RoleDefinitionName 'Contributor' -Scope "/providers/Microsoft.Management/managementGroups/$($GitHubSecret_ARM_MGMTGROUP_ID)" -ErrorAction Stop
    Write-Host "RBAC Role Assignments of 'Contributor' and 'User Access Administrator' 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 OIDC
if ($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 ''

  # 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 'Starting 120 second sleep to allow the UAMI to be created and available for RBAC Role Assignments (eventual consistency) ...' -ForegroundColor Yellow
  Start-Sleep -Seconds 120

  Write-Host "Creating RBAC Role Assignments of 'Contributor' and 'User Access Administrator' 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 'User Access Administrator' -Scope "/subscriptions/$($GitHubSecret_ARM_SUBSCRIPTION_ID)" -ErrorAction Stop
  New-AzRoleAssignment -ObjectId $newUAMI.PrincipalId -RoleDefinitionName 'Contributor' -Scope "/subscriptions/$($GitHubSecret_ARM_SUBSCRIPTION_ID)" -ErrorAction Stop
  Write-Host "RBAC Role Assignments of 'Contributor' and 'User Access Administrator' 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 'Contributor' and 'User Access Administrator' 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 'User Access Administrator' -Scope "/providers/Microsoft.Management/managementGroups/$($GitHubSecret_ARM_MGMTGROUP_ID)" -ErrorAction Stop
    New-AzRoleAssignment -ObjectId $newUAMI.PrincipalId -RoleDefinitionName 'Contributor' -Scope "/providers/Microsoft.Management/managementGroups/$($GitHubSecret_ARM_MGMTGROUP_ID)" -ErrorAction Stop
    Write-Host "RBAC Role Assignments of 'Contributor' and 'User Access Administrator' 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 10

  if ($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 120
Write-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/'

1. Fork the module source repository

Tip

Checkout the PowerShell Helper Script that can do this step automatically for you! πŸ‘


Note

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
  1. Navigate to the repository’s Settings.

  2. In the list of settings, expand Environments. You can create a new environment by selecting New environment on the top right.

  3. In the opening view, provide avm-validation for the environment Name. Click on the Configure environment button.

AddEnvironment AddEnvironment

Please ref the following link for additional details: Creating an environment

2. Configure a deployment identity in Azure

AVM tests its modules via deployments in an Azure subscription. To do so, it requires a deployment identity with access to it.

Deprecating the Service Principal + Secret authentication method

Support for the ‘Service Principal + Secret’ authentication method has been deprecated and will be decommissioned in the future.

It is highly recommended to start leveraging Option 1 below to adopt OpenID Connect (OIDC) authentication and align with security best practices.

βž• Option 1 [Recommended]: OIDC - Configure a federated identity credential
  1. 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 might find the following links useful:

    OIDCIdentityRoles OIDCIdentityRoles

Additional roles

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.

  1. 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.
      OIDCFederatedCredentials OIDCFederatedCredentials
    • In the Federated credential scenario dropdown box, select GitHub Actions deploying Azure resources
      OIDCScenario OIDCScenario
    • 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.
      OIDCAdd OIDCAdd
    • 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:
        • Issuer = https://token.actions.githubusercontent.com
        • Subject = repo:<GitHub Org>/<GitHub Repo>:environment:avm-validation
        • Audience = api://AzureADTokenExchange (although this is default in the API so not required to set)
      • Configure a federated identity credential on a user-assigned managed identity
  2. Note down the following pieces of information
    • Client ID
    • Tenant ID
    • Subscription ID
    • Parent Management Group ID
      OIDCInfo OIDCInfo

Additional references:

βž• Option 2 [Deprecated]: Configure Service Principal + Secret
  1. 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 might find the following links useful:
  2. Note down the following pieces of information
    • Application (Client) ID
    • Service Principal Object ID (not the object ID of the application)
    • Service Principal Secret (password)
    • Tenant ID
    • Subscription ID
    • Parent Management Group ID

3. Configure your CI environment

Tip

Checkout the PowerShell Helper Script that can do this step automatically for you! πŸ‘

To configure the forked CI environment you have to perform several steps:

3.1. Set up secrets

3.1.1 Shared repository secrets

To use the Continuous Integration environment’s workflows you should set up the following repository secrets:

Secret NameExampleDescription
ARM_MGMTGROUP_ID11111111-1111-1111-1111-111111111111The 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_ID22222222-2222-2222-2222-222222222222The 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_ID33333333-3333-3333-3333-333333333333The 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_NAMEPREFIXcntsoRequired. 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
  1. Navigate to the repository’s Settings.

    NavigateToSettings NavigateToSettings

  2. 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.

    NavigateToSecrets NavigateToSecrets

  3. 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.

    AddSecret AddSecret

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 NameExampleDescription
VALIDATE_CLIENT_ID44444444-4444-4444-4444-444444444444The login credentials of the deployment principal used to log into the target Azure environment to test in. The format is described here.
VALIDATE_SUBSCRIPTION_ID22222222-2222-2222-2222-222222222222Same 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_ID33333333-3333-3333-3333-333333333333Same 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
  1. Navigate to the repository’s Settings.

    NavigateToSettings NavigateToSettings

  2. In the list of settings, select Environments. Click on the previously created avm-validation environment.

    NavigateToEnvironments NavigateToEnvironments

  3. In the Environment secrets Section click on the Add environment secret button.

    NavigateToEnvSecrets NavigateToEnvSecrets

  4. 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.
    AddEnvSecret AddEnvSecret

βž• Option 2 [Deprecated]: Authenticate via Service Principal + Secret

Create the following environment repository secret:

Secret NameExampleDescription
AZURE_CREDENTIALS{"clientId": "44444444-4444-4444-4444-444444444444", "clientSecret": "<placeholder>", "subscriptionId": "22222222-2222-2222-2222-222222222222", "tenantId": "33333333-3333-3333-3333-333333333333" }The login credentials of the deployment principal used to log into the target Azure environment to test in. The format is described here. 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:

{"clientId": "<client_id>", "clientSecret": "<client_secret>", "subscriptionId": "<subscriptionId>", "tenantId": "<tenant_id>" }

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. If you’re interested, you can find more information about this object here.

3.2. Enable actions

Finally, ‘GitHub Actions’ are disabled by default and hence, must be enabled first.

To do so, perform the following steps:

  1. Navigate to the Actions tab on the top of the repository page.

  2. Next, select ‘I understand my workflows, go ahead and enable them’.

    EnableActions EnableActions

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.

  1. Navigate to the Settings tab on the top of your repository page.

  2. Within the section Code and automation click on Actions and General

  3. Make sure to enable Read and write permissions

    WorkflowPermissions WorkflowPermissions

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 comment
name: "avm.[res|ptn|utl].[provider-namespace].[resource-type]"

on:
  workflow_dispatch:
    inputs:
      staticValidation:
        type: boolean
        description: "Execute static validation"
        required: false
        default: true
      deploymentValidation:
        type: boolean
        description: "Execute deployment validation"
        required: false
        default: true
      removeDeployment:
        type: boolean
        description: "Remove deployed module"
        required: false
        default: true
      customLocation:
        type: string
        description: "Default location overwrite (e.g., eastus)"
        required: false
  push:
    branches:
      - main
    paths:
      - ".github/actions/templates/avm-**"
      - ".github/workflows/avm.template.module.yml"
        # >>> 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]/**"
      - "utilities/pipelines/**"
      - "!utilities/pipelines/platform/**"
      - "!*/**/README.md"

env:
  # >>> UPDATE to for example "avm/res/key-vault/vault" and remove this comment
  modulePath: "avm/[res|ptn|utl]/[provider-namespace]/[resource-type]"
  # >>> Update to for example ".github/workflows/avm.res.key-vault.vault.yml" and remove this comment
  workflowPath: ".github/workflows/avm.[res|ptn|utl].[provider-namespace].[resource-type].yml"

concurrency:
  group: ${{ github.workflow }}

jobs:
  ###########################
  #   Initialize pipeline   #
  ###########################
  job_initialize_pipeline:
    runs-on: ubuntu-latest
    name: "Initialize pipeline"
    steps:
      - name: "Checkout"
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: "Set input parameters to output variables"
        id: get-workflow-param
        uses: ./.github/actions/templates/avm-getWorkflowInput
        with:
          workflowPath: "${{ env.workflowPath}}"
      - name: "Get module test file paths"
        id: get-module-test-file-paths
        uses: ./.github/actions/templates/avm-getModuleTestFiles
        with:
          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 OIDC
      contents: write # For release tags
    needs:
      - job_initialize_pipeline
    uses: ./.github/workflows/avm.template.module.yml
    with:
      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
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.

    Tip

Dependency file (dependencies.bicep) guidelines:

  • 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.

    Tip
    Tip

    πŸ“œ 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

There are a number of additional scripts and utilities available here 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:

  1. Provide a meaningful title in the form of feat: <module name> to align with the Semantic PR Check.

  2. Provide a meaningful description.

  3. Follow instructions you find in the PR template.

  4. 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.

    BadgeDropdown BadgeDropdown

  5. In the opening pop-up, you first need to select your branch and then click on the Copy status badge Markdown

    StatusBadge StatusBadge

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!

Subsections of Contribution Flow

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:

  • Entra-ID-provided Enterprise Application object ids (e.g., Backup Management Service, Azure Databricks, etc.)
  • (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,

@secure()
param backupManagementServiceEnterpriseApplicationObjectId string = ''

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:

  1. Create an Azure Key Vault in your test subscription
  2. 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
  3. 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.

RequiredGitHubVariable RequiredGitHubVariable

Configuring a secret

Building upon the prerequisites you only have to implement two actions per value to dynamically populate them during deployment validation:

  1. 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.

  1. 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.

ExampleSecretsInKeyVault ExampleSecretsInKeyVault

How it works

Assuming you completed both the prerequisites & setup steps and triggered your module’s workflow, the CI will perform the following actions:

  1. When approaching the deployment validation steps, the workflow will lookup the CI_KEY_VAULT_NAME repository variable
  2. 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
  3. 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
  4. 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:

ExamplePipelineLog ExamplePipelineLog

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.

Location

You can find the script under utilities/pipelines/platform/Switch-WorkflowState.ps1)
You can find the workflow under .github/workflows/platform.toggle-avm-workflows.yml

How it works

Browse to Actions and select the workflow from the list

SelectToggleWorkflows SelectToggleWorkflows

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.

RunToggleWorkflows RunToggleWorkflows

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.

1. Owner Activities and Responsibilities

Familiarise yourself with the responsibilities as Module Owner outlined in Team Definitions & RACI and Module Owner Responsibilities in the BRM Issue Triage.

  1. Create GitHub teams as outlined in SNFR20 and add respective parent teams:

    Segments:

    • avm-res-<RP>-<modulename>-module-owners-bicep
    • avm-res-<RP>-<modulename>-module-contributors-bicep

    Examples:

    • avm-res-compute-virtualmachine-module-owners-bicep and added avm-technical-reviewers-bicep as parent.
    • avm-res-compute-virtualmachine-module-contributors-bicep and added avm-module-contributors-bicep as parent.

    If a secondary owner is required, add the secondary owner to the avm-res-<RP>-<modulename>-module-owners-bicep team.

    Only fulltime Microsoft employees can be added at this time.

    Info

    Once the teams 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.

  2. Add teams to CODEOWNERS file as outlined in SNFR20.

  3. 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!

  4. Ensure that the module(s) you own are compliant with the AVM Bicep specifications and are working as expected.

  5. 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.

  6. 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 teams.
  • Remove your GitHub account from your module’s GitHub teams.

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.

3. Adopting an Orphaned Module

When adopting an orphaned module the when-a-new-owner-is-identified steps must be followed.

4. GitHub Notification Settings

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.

Enable Global GitHub Notifications

Visit the GitHub Notifications Settings Page while logged in with your GitHub account.

GitHubNotificationsSettingsPage GitHubNotificationsSettingsPage

  1. Ensure your Default Notifications Email address is set to the email address you intend to use.
  2. (Optional) If you would like to automatically watch repositories that you are active in, ensure Automatically watch repositories is set to “On.”
  3. (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.”
  4. (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.
  5. (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.

GitHubNotificationsPage GitHubNotificationsPage

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.

Type of NotificationGitHub Email AddressNotification Reason
@Mentionsmention@noreply.github.comYou were mentioned on an issue or pull request.
@Team Mentionteam_mention@noreply.github.comA team you belong to was mentioned on an issue or pull request
Subscribedsubscribed@noreply.github.comThere was an update in a repository you’re watching.
Assignassign@noreply.github.comYou were assigned to an issue or pull request.
Commentcomment@noreply.github.comYou commented on an issue or pull request.

For a full list of GitHub notification types, see Filtering Email Notifications.

5. Contribution Checklist

This checklist can be used in the development of AVM Bicep Modules.

  1. 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.

  2. 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.
  3. Create a new branch from your forked repository to develop your module.

  4. 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.
  5. Implement your contribution.

  6. Create, update, and run 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 yet
    
    pwsh
    
    # 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 Readme
    
    foreach ($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 tests
    
        foreach ($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
        }
    }
  7. 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!

  8. 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.

  9. 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 (BCPFR3), 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.

Location

You can find the script under utilities/tools/Set-AVMModule.ps1

How it works

Using the provided template path, the script

  1. validates the module’s folder structure
    • 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
  2. compiles its bicep template
  3. updates the readme (recursively, specified)
    1. 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)
    2. 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.
    3. 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 towards 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.

Location

You can find the script under utilities/tools/Test-ModuleLocally.ps1

How it works

If the switch for Pester tests (-PesterTest) is provided the script will

  1. 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

  1. 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.
  2. 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'.
  3. If the -ValidationTest parameter was set, it runs a deployment validation using the Test-TemplateDeployment script.
  4. If the -DeploymentTest parameter was set, it runs a deployment using the New-TemplateDeployment script (with no retries).
  5. 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.

. 'C:/dev/Test-ModuleLocally.ps1'
Test-ModuleLocally (...)
Important

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

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.

Before you start contributing to the AVM, it is highly recommended that you complete the following Microsoft Learn paths, modules & courses:

Bicep

Git

Tooling

Required Tooling

To contribute to this project the following tooling is required:

The following tooling/extensions are recommended to assist you developing for the project:

Visual Studio Code Extensions

Desktop Tooling

  • GitHub Desktop
    • 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?

Each AVM module requires a module owner and MAY have additional module contributors.

Essentially, you have 3 options:

  1. You sign up to be a module owner (and optionally, you can find additional contributors to help you).
  2. You find / request someone else to be the module owner (and optionally, you can be a contributor).
  3. 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.

You can propose a new module here.


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?

We suggest that you review module specification and contribution guides:

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?

To get list of labels that MUST be created on gitHub repositories where modules are held navigate to Shared non-functional requirement 23 (SNFR23).

You SHOULD NOT use any additional labels.

There is also a PowerShell script that the AVM core team created that can help to apply those labels the GitHub module repository.


Is there any naming convention for modules name, repository name, variables, parameters…. ?

AVM specification covers all naming conventions. As example:
Module naming specification


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 router to module owner that MUST respond to logged issues within 3 business days. 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[[Pre-Release v0.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:

AVM Preview Notice

Important

As the overall AVM framework is not GA (generally available) yet - the CI framework and test automation is not fully functional and implemented across all supported languages yet - breaking changes are expected, and additional customer feedback is yet to be gathered and incorporated. Hence, modules MUST NOT be published at version 1.0.0 or higher at this time.

All module MUST be published as a pre-release version (e.g., 0.1.0, 0.1.1, 0.2.0, etc.) until the AVM framework becomes GA.

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.)
  1. Create a GitHub Issue
  2. Discuss the issue/blocker with the AVM core team
  3. Agree upon action/resolution/closure
  4. Implement agreed upon action/resolution/closure
Note

Please note for module specific issues, these should be logged in the module’s source repository, not the AVM repository.

Terraform Contribution Guide

Important

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 (Resource and Pattern modules) MUST meet the respective requirements described in these specifications!

Summary

This section lists AVM’s Terraform-specific contribution guidance.

Subsections of Terraform Modules

Terraform Composition

Important

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 Terraform 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 Terraform specification pages, to make sure your contribution complies with the AVM module’s design and principles.

Repositories

Each Terraform AVM module will have its own GitHub Repository in the Azure GitHub Organization as per SNFR19.

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 template repo here.

  • tests/ - (for unit tests and additional tests if required - e.g. tflint etc.)
    • unit/ - (optional, may use further sub-directories if required)
  • modules/ - (for sub-modules only if used)
  • examples/ - (all examples must deploy without successfully without requiring input - these are customer facing)
    • <at least one folder> - (at least one example that uses the variable defaults minimum/required parameters/variables only)
    • <other folders for examples as required>
  • /... - (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

Directory and File Structure

/ root
β”‚
β”œβ”€β”€β”€.github/
β”‚   β”œβ”€β”€β”€actions/
β”‚   β”‚   β”œβ”€β”€β”€avmfix/
β”‚   β”‚   β”‚   └───action.yml
β”‚   β”‚   β”œβ”€β”€β”€docs-check/
β”‚   β”‚   β”‚   └───action.yml
β”‚   β”‚   β”œβ”€β”€β”€e2e-getexamples/
β”‚   β”‚   β”‚   └───action.yml
β”‚   β”‚   β”œβ”€β”€β”€e2e-testexamples/
β”‚   β”‚   β”‚   └───action.yml
β”‚   β”‚   β”œβ”€β”€β”€linting/
β”‚   β”‚   β”‚   └───action.yml
β”‚   β”‚   └───version-check/
β”‚   β”‚       └───action.yml
β”‚   β”œβ”€β”€β”€policies/
β”‚   β”‚   β”œβ”€β”€β”€avmrequiredfiles.yml
β”‚   β”‚   └───branchprotection.yml
β”‚   β”œβ”€β”€β”€workflows/
β”‚   β”‚   β”œβ”€β”€β”€e2e.yml
β”‚   β”‚   β”œβ”€β”€β”€linting.yml
β”‚   β”‚   └───version-check.yml
β”‚   β”œβ”€β”€β”€CODEOWNERS
β”‚   └───dependabot.yml
β”œβ”€β”€β”€.vscode/
β”‚   β”œβ”€β”€β”€extensions.json
β”‚   └───settings.json
β”œβ”€β”€β”€examples/
β”‚   β”œβ”€β”€β”€<example_folder>/
β”‚   β”‚   β”œβ”€β”€β”€README.md
β”‚   β”‚   β”œβ”€β”€β”€_footer.md
β”‚   β”‚   β”œβ”€β”€β”€_header.md
β”‚   β”‚   β”œβ”€β”€β”€main.tf
β”‚   β”‚   └───variables.tf
β”‚   β”œβ”€β”€β”€.terraform-docs.yml
β”‚   └───README.md
β”œβ”€β”€β”€modules/
β”‚   └───README.md
β”œβ”€β”€β”€tests/
β”‚   └───README.md
β”œβ”€β”€β”€.gitignore
β”œβ”€β”€β”€.terraform-docs.yml
β”œβ”€β”€β”€CODE_OF_CONDUCT.md
β”œβ”€β”€β”€LICENSE
β”œβ”€β”€β”€Makefile
β”œβ”€β”€β”€README.md
β”œβ”€β”€β”€SECURITY.md
β”œβ”€β”€β”€SUPPORT.md
β”œβ”€β”€β”€_footer.md
β”œβ”€β”€β”€_header.md
β”œβ”€β”€β”€avm
β”œβ”€β”€β”€avm.bat
β”œβ”€β”€β”€locals.tf
β”œβ”€β”€β”€main.privateendpoint.tf
β”œβ”€β”€β”€main.telemetry.tf
β”œβ”€β”€β”€main.tf
β”œβ”€β”€β”€outputs.tf
β”œβ”€β”€β”€terraform.tf
└───variables.tf

Code Styling

This section points to conventions to be followed when developing a module.

Casing

Use snake_casing as per TFNFR3.

Input Parameters and Variables

Make sure to review all specifications of Category: Inputs/Outputs within the Terraform specification pages.

Tip

See examples in specifications SNFR14 and TFFR14.

Resources

Resources are primarily leveraged by resource modules to declare the primary resource of the main resource type deployed by the AVM module.

Make sure to review all specifications covering resource properties and usage.

Tip

See examples in specifications SFR1 and RMFR1.

Outputs

Make sure to review all specifications of Category: Inputs/Outputs within the Terraform specification pages.

Tip

See examples in specification RMFR7 and TFFR2.

Interfaces

Note

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 Terraform Interfaces page.

Telemetry

To meet SFR3 & SFR4.
We use a telemetry provider modtm.
This is a lightweight telemetry provider that sends telemetry data to Azure Application Insights via a HTTP POST front end service.

The telemetry provider is included in the module by default and is enabled by default.
You do not need to change the configuration included in the template repo.

You must make sure to have the modtm provider in your required_providers.
The linter will check this for you.
However, inside your terraform.tf file please make sure you have this entry:

terraform {
  required_providers {
    # .. other required providers as needed
    modtm = {
      source = "Azure/modtm"
      version = "~> 0.3"
    }
  }
}

Eventual Consistency

When creating modules, it is important to understand that the Azure Resource Manager (ARM) API is sometimes eventually consistent.
This means that when you create a resource, it may not be available immediately.
A good example of this is data plane role assignments.
When you create such a role assignment, it may take some time for the role assignment to be available.
We can use an optional time_sleep resource to wait for the role assignment to be available before creating resources that depend on it.

# In variables.tf...
variable "wait_for_rbac_before_foo_operations" {
  type: object({
    create  = optional(string, "30s")
    destroy = optional(string, "0s")
  })
  default     = {}
  description = <<DESCRIPTION
This variable controls the amount of time to wait before performing foo operations.
It only applies when `var.role_assignments` and `var.foo` are both set.
This is useful when you are creating role assignments on the bar resource and immediately creating foo resources in it.
The default is 30 seconds for create and 0 seconds for destroy.
DESCRIPTION
}

# In main.tf...
resource "time_sleep" "wait_for_rbac_before_foo_operations" {
  count = length(var.role_assignments) > 0 && length(var.foo) > 0 ? 1 : 0
  depends_on = [
    azurerm_role_assignment.this
  ]
  create_duration  = var.wait_for_rbac_before_foo_operations.create
  destroy_duration = var.wait_for_rbac_before_foo_operations.destroy

  # This ensures that the sleep is re-created when the role assignments change.
  triggers = {
    role_assignments = jsonencode(var.role_assignments)
  }
}

resource "azurerm_foo" "this" {
  for_each = var.foo
  depends_on = [
    time_sleep.wait_for_rbac_before_foo_operations
  ]
  # ...
}

Terraform 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/terraform/terraform-contribution-flow/#1-fork-the-module-source-repository"
  B(2 - Setup your Azure test environment)
    click B "/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#2-prepare-your-azure-test-environment"
  C(3 - Implement your contribution)
    click C "/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#3-implement-your-contribution"
  D{4 - Pre-commit<br>checks successful?}
    click D "/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#4-run-pre-commit-checks"
  E(5 - Create a pull request to the upstream repository)
    click E "/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#5-create-a-pull-request-to-the-upstream-repository"
  A --> B
  B --> C
  C --> D
  D -->|yes|E
  D -->|no|C

GitFlow for contributors

The GitFlow process outlined here depicts and suggests a way of working with Git and GitHub. It serves to synchronize the forked repository with the original upstream repository. It is not a strict requirement to follow this process, but it is highly recommended to do so.

---

config:
  logLevel: debug
  gitGraph:
    rotateCommitLabel: false
---

gitGraph LR:
  commit id:"fork"
  branch fork/main
  checkout fork/main
  commit id:"checkout feature" type: HIGHLIGHT
  branch feature
  checkout feature
  commit id:"checkout fix"
  branch fix
  checkout main
  merge feature id: "Pull Request 'Feature'" type: HIGHLIGHT
  checkout fix
  commit id:"Patch 1"
  commit id:"Patch 2"
  checkout main
  merge fix id: "Pull Request 'Fix'" type: HIGHLIGHT
Tip

When implementing the GitFlow process as described, it is advisable to configure the local clone of your forked repository with an additional remote for the upstream repository. This will allow you to easily synchronize your locally forked repository with the upstream repository. Remember, there is a difference between the forked repository on GitHub and the clone of the forked repository on your local machine.

UpstreamToForkAndSourceRepository UpstreamToForkAndSourceRepository

Note

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.

Prepare your developer environment

1. Fork the module source repository

Important

Each Terraform AVM module will have its own GitHub repository in the Azure GitHub Organization as per SNFR19.

This repository will be created by the Module owners and the AVM Core team collaboratively, including the configuration of permissions as per SNFR9

Module contributors are expected to fork the corresponding repository and work on a branch from within their fork, before then creating a Pull Request (PR) back into the source repository’s main branch.

To do so, simply navigate to your desired 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’.

Note

If the module repository you want to contribute to is not yet available, please get in touch with the respective module owner which can be tracked in the Terraform Resource Modules index see PrimaryModuleOwnerGHHandle column.

Optional: The usage of local source branches

For consistent contributors but also Azure-org members in general it is possible to get invited as collaborator of the module repository which enables you to work on branches instead of forks. To get invited get in touch with the module owner since it’s the module owner’s decision who gets invited as collaborator.

2. Prepare your Azure test environment

AVM performs end-to-end (e2e) test deployments of all modules in Azure for validation. We recommend you to perform a local e2e test deployment of your module before you create a PR to the upstream repository. Especially because the e2e test deployment will be triggered automatically once you create a PR to the upstream repository.

  1. Have/create an Azure Active Directory Service Principal with at least Contributor & User Access Administrator permissions on the Management-Group/Subscription you want to test the modules in. You might find the following links useful:

    # Linux/MacOs
    export ARM_SUBSCRIPTION_ID=$(az account show --query id --output tsv) # or set <subscription_id>
    export ARM_TENANT_ID=$(az account show --query tenantId --output tsv) # or set <tenant_id>
    export ARM_CLIENT_ID=<client_id>
    export ARM_CLIENT_SECRET=<service_principal_password>
    
    # Windows/Powershell
    $env:ARM_SUBSCRIPTION_ID = $(az account show --query id --output tsv) # or set <subscription_id>
    $env:ARM_TENANT_ID = $(az account show --query tenantId --output tsv) # or set <tenant_id>
    $env:ARM_CLIENT_ID = "<client_id>"
    $env:ARM_CLIENT_SECRET = "<service_principal_password>"
  2. Change to the root of your module repository and run ./avm docscheck (Linux/MacOs) / avm.bat docscheck (Windows) to verify the container image is working as expected or needs to be pulled first. You will need this later.

    PullLatestAzterraformContainerImage PullLatestAzterraformContainerImage

3. Implement your contribution

To implement your contribution, we kindly ask you to first review the Terraform specifications and composition guidelines in particular to make sure your contribution complies with the repository’s design and principles.

Tip

To get a head start on developing your module, consider using the tooling recommended per spec TFNFR37. For example you can use the newres tool to help with creating variables.tf and main.tf if you’re developing a module using Azurerm provider.

4. Run Pre-commit Checks

Important

Make sure you have Docker installed and running on your machine.

Note

To simplify and help with the execution of commands like pre-commit, pr-check, docscheck, fmt, test-example, etc. there is now a simplified avm script available distributed to all repositories via terraform-azurerm-avm-template which combines all scripts from the avm_scripts folder in the tfmod-scaffold repository using avmmakefile.

The avm script also makes sure to pull the latest mcr.microsoft.com/azterraform:latest container image before executing any command.

4.1. Run pre-commit and pr-check

The following commands will run all pre-commit checks and the pr-check.

# Running all pre-commit checks
# `pre-commit` runs depsensure fmt fumpt autofix docs
# `pr-check` runs fmtcheck tfvalidatecheck lint unit-test

## Linux/MacOs
./avm pre-commit
./avm pr-check

## Windows
avm.bat pre-commit
avm.bat pr-check

4.2 Run e2e tests

Currently you have two options to run e2e tests:

Note

With the help of the avm script and the commands ./avm test-example (Linux/MacOs) / avm.bat test-example (Windows) you will be able to run it in a more simplified way. Currently the test-example command is not completely ready yet and will be released soon. Therefore please use the below docker command for now.

  1. Run e2e tests with the help of the azterraform docker container image.

    # Linux/MacOs
    
    docker run --rm -v $(pwd):/src -w /src -v $HOME/.azure:/root/.azure -e TF_IN_AUTOMATION -e AVM_MOD_PATH=/src -e AVM_EXAMPLE=<example_folder> -e ARM_SUBSCRIPTION_ID -e ARM_TENANT_ID -e ARM_CLIENT_ID -e ARM_CLIENT_SECRET mcr.microsoft.com/azterraform:latest make test-example
    
    # Powershell
    
    docker run --rm -v ${pwd}:/src -w /src -v $HOME/.azure:/root/.azure -e TF_IN_AUTOMATION -e AVM_MOD_PATH=/src -e AVM_EXAMPLE=<example_folder> -e ARM_SUBSCRIPTION_ID -e ARM_TENANT_ID -e ARM_CLIENT_ID -e ARM_CLIENT_SECRET mcr.microsoft.com/azterraform:latest make test-example

    Make sure to replace <client_id> and <service_principal_password> with the values of your service principal as well as <example_folder> (e.g. default) with the name of the example folder you want to run e2e tests for.

  2. Run e2e tests with the help of terraform init/plan/apply.

    Simply run terraform init and terraform apply in the example folder you want to run e2e tests for. Make sure to set the environment variables ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID and ARM_CLIENT_SECRET before you run terraform init and terraform apply or make sure you have a valid Azure CLI session and are logged in with az login.

5. Create a pull request to the upstream repository

Once you are satisfied with your contribution and validated it, submit a pull request to the upstream repository and work with the module owner to get the module reviewed by the AVM Core team, by following the initial module review process for Terraform Modules, described here. This is a prerequisite for publishing the module. Once the review process is complete and your PR is approved, merge it into the upstream repository and the Module owner will publish the module to the HashiCorp Terraform Registry.

5.1 Create the Pull Request [Contributor]

These steps are performed by the contributor:

  1. Navigate to the upstream repository and click on the Pull requests tab.
  2. Click on the New pull request button.
  3. Ensure the base repository is set to the upstream AVM repo.
  4. Ensure the base branch is set to main.
  5. Ensure your head repository and compare branch are set to your fork and the branch you are working on.
  6. Click on the Create pull request button.

5.2 Review the Pull Request [Owner]

  1. IMPORTANT: The module owner must first check for any malicious code or changes to workflow files. If they are found, the owner should close the PR and report the contributor.
  2. Review the changes made by the contributor and determine whether end to end tests need to be run.
  3. If end to end tests do not need to be run (e.g. doc changes, small changes, etc) then so long as the static analysis passes, the PR can be merged to main.
  4. If end to end tests do need to be run, then follow the steps in 5.3.

5.3 Release Branch and Run End to End Tests [Owner]

  1. IMPORTANT: The module owner must first check for any malicious code or changes to workflow files. If they are found, the owner should close the PR and report the contributor.
  2. Create a release branch from main. Suggested naminmg convention is release/<description-of-change>.
  3. Open the PR created by the contributor and click Edit at the top right of the PR.
  4. Change the base branch to the release branch you just created.
  5. Wait for the PR checks to run, validate the code looks good and then merge the PR into the release branch.
  6. Create a new PR from the release branch to the main branch of the AVM module.
  7. The end to end tests should trigger and you can approve the run.
  8. Once the end to end tests have passed, merge the PR into the main branch.
  9. If the end to end tests fail, investigate the failure. You have two options:
    1. Work with the contributor to resolve the issue and ask them to submit a new PR from their fork branch to the release branch.
      1. Re-run the tests and merge to main. Repeat the loop as required.
    2. If the issue is a simple fix, resolve it directly in the release branch, re-run the tests and merge to main.

Common mistakes to avoid and recommendations to follow

  • If you contribute to a new module then search and update TODOs (which are coming with the terraform-azurerm-avm-template) within the code and remove the TODO comments once complete
  • terraform.lock.hcl shouldn’t be in the repository as per the .gitignore file
  • Update the support.md file
  • \_header.md needs to be updated
  • support.md needs to be updated
  • Exclude terraform.tfvars file from the repository

Subsections of Contribution Flow

Terraform Owner Contribution Flow

This section describes the contribution flow for module owners who are responsible for creating and maintaining Terraform Module repositories.

Important

This contribution flow is for Module owners only.

As a Terraform Module Owner you need to be aware of the AVM contribution process overview & Terraform specifications (including Interfaces) as as these need to be considered during pull request reviews for the modules you own.

Info

Make sure module authors/contributors tested their module in their environment before raising a PR. The PR uses e2e checks with 1ES agents in the 1ES subscriptions. At the moment their is no read access to the 1ES subscription. Also if more than two subscriptions are required for testing, that’s currently not supported.

1. Owner Activities and Responsibilities

Familiarise yourself with the responsibilities as Module Owner outlined in Team Definitions & RACI and in the TF Issue Triage.

  1. Watch Pull Request (PR) and issue (questions/feedback) activity for your module(s) in your repository and ensure that PRs are reviewed and merged in a timely manner as outlined in SNFR11.
Info

Make sure module authors/contributors tested their module in their environment before raising a PR. Also because once a PR is raised a e2e GitHib 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.

2. GitHub repository creation and configuration

Familiarise yourself with the AVM Resource Module Naming in the module index csv’s.

  • Example: terraform-<provider>-avm-res-<rp>-<ARM resource type>
Important

Make sure you have access to the Azure organisation see GitHub Account Link and Access.

  1. Create the module repostory using terraform-azuremrm-avm-template in the Azure organisation with the following details (internal only). You will then have to complete the configuration of your repository and start an internal business review.

  2. Create GitHub teams as outlined in SNFR20 and add respective parent teams:

    Segments:

    • avm-res-<RP>-<modulename>-module-owners-tf
    • avm-res-<RP>-<modulename>-module-contributors-tf

    Examples:

    • avm-res-compute-virtualmachine-module-owners-tf
    • avm-res-compute-virtualmachine-module-contributors-tf

    If a secondary owner is required, add the secondary owner to the avm-res-<RP>-<modulename>-module-owners-tf team.

  3. Add these teams with the following permissions directly to the repository:

    • Admin: avm-core-team-technical-terraform = AVM Core Team (Terraform Technical)
    • Admin: terraform-avm = Terraform PG
    • Admin: avm-res-<RP>-<modulename>-module-owners-tf = AVM Terraform Module Owners
    • Write: avm-res-<RP>-<modulename>-module-contributors-tf = AVM Terraform Module Contributors
  4. Make sure the branch protection rules for the main branch are inherited from the Azure/terraform-azurerm-avm-template repository:

    • Require a pull request before merging
    • Dismiss stale pull request approvals when new commits are pushed
    • Require review from Code Owners
    • Require linear history
    • Do not allow bypassing the above settings
  5. The respoitory environment test will be automatically created within 4 hours, it will have approvals and secrets applied to it ready to run end to end tests. You should not create this environment manually.

    • If you wish to use your own tenant and subscription for end to end tests, you can override the secrets by setting ARM_TENANT_ID_OVERRIDE, ARM_SUBSCRIPTION_ID_OVERRIDE, and ARM_CLIENT_ID_OVERRIDE secrets.
    • If you need to supply additional secrets or variables for your end to end tests, you can add them to the test environment. They must be prefixed with TF_VAR_, otherwise they will be ignored.

3. GitHub Repository Labels

As per SNFR23 the repositories created by module owners MUST have and use the pre-defined GitHub labels. To apply these labels to the repository review the PowerShell script Set-AvmGitHubLabels.ps1 that is provided in SNFR23.

Set-AvmGitHubLabels.ps1 -RepositoryName "Azure/MyGitHubRepo" -CreateCsvLabelExports $false -NoUserPrompts $true

4. Module Handover Activities

  1. Add new owner as maintainer in your avm-res-<RP>-<modulename>-module-owners-tf team and remove any other individual including yourself.
  2. In case primary owner leaves, switches roles or abandons the repo and the corresponding team then the parent team (if assigned) doesn’t have the permissions to gain back access and a ticket with GitHub support needs to be created (but the team can still be removed from the repo since the team avm-core-team has permissions on it).

5. Grept

Grept is a linting tool for repositories, ensures predefined standards, maintains codebase consistency, and quality.
It’s using the grept configuration files from the Azure-Verified-Modules-Grept repository.

You can see here which files are synced from the terraform-azurerm-avm-template repository.

  1. Set environment variables
# Linux/MacOS
export GITHUB_REPOSITORY_OWNER=Azure
export GITHUB_REPOSITORY=Azure/terraform-azurerm-avm-res-<RP>-<modulename>"

# Windows

$env:GITHUB_REPOSITORY_OWNER="Azure"
$env:GITHUB_REPOSITORY="Azure/terraform-azurerm-avm-res-<RP>-<modulename>"
  1. Run grept
# Linux/MacOS
./avm grept-apply

# Windows
avm.bat grept-apply

6. Review the module

Once the development of the module has been completed, get the module reviewed from the AVM Core team by following the AVM Review of Terraform Modules process here which is a pre-requisite for the next step.

7. Publish the module

Once a module has been reviewed and is ready to be published, follow the below steps to publish the module to the HashiCorp Registry.

Ensure your module is ready for publishing:

  1. Create a tag for the module version you want to publish.
  • Create tag: git tag -a 0.1.0 -m "0.1.0"

  • Push tag: git push

  • Create a release on Github based on the tag you just created. Make sure to generate the release notes using the Generate release notes button.

  • Optional: Instead of creating the tag via git cli, you can also create both the tag and release via Github UI. Just go to the releases tab and click on Draft a new release. Make sure to create the tag from the main branch.

    DeploymentProtectionRules DeploymentProtectionRules

  1. Elevate your respository access using the Open Source Management Portal (aka.ms/opensource/portal).
  2. Sign in to the HashiCorp Registry using GitHub.
  3. Publish a module by selecting the Publish button in the top right corner, then Module
  4. Select the repository and accept the terms.
Info

Once a module gets updated and becomes a new version/release it will be automatically published with the latest published release version to the HashiCorp Registry.

Important

When an AVM Module is published to the HashiCorp Registry, it MUST follow the below requirements:

  • Resource Module: terraform-<provider>-avm-res-<rp>-<ARM resource type> as per RMNFR1
  • Pattern Module: terraform-<provider>-avm-ptn-<patternmodulename> as per PMNFR1

Terraform Contribution Prerequisites

To contribute to this project, you need to have a GitHub account which is linked to your Microsoft corporate identity account and be a member of the Azure organization.

Tooling

Required Tooling

Tip

We recommend to use Linux or MacOS for your development environment. You can use Windows Subsystem for Linux (WSL) if you are using Windows.

To contribute to this project the following tooling is required:

The following tooling/extensions are recommended to assist you developing for the project:

Visual Studio Code Extensions

  • Go extension for Visual Studio Code
  • For visibility of Bracket Pairs:
    • Inside Visual Studio Code, add editor.bracketPairColorization.enabled: true to your settings.json, to enable bracket pair colorization.

Review of Terraform Modules

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.

  1. The module owner completes the development of the module in their branch or fork.

  2. 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.

  3. The module owner assigns the avm-core-team-technical-terraform GitHub team as reviewer on the PR.

  4. 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.
  5. The AVM team moves the module proposal from “In Development” to “In Review” in the AVM - Module Triage project.

  6. 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

    1. Complete the below task list by ticking off the tasks.
    2. 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
    - [ ] Make sure you have run [grept](https://azure.github.io/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/owner-contribution-flow/#5-grept) and [pre-commit and pr-check](https://azure.github.io/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/#41-run-pre-commit-and-pr-check).
    - [ ] 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.
    IDSpecCompliantComments
    1ID: SFR1 - Category: Composition - Preview ServicesNA if no preview services are used
    2ID: SFR2 - Category: Composition - WAF AlignedEnsure only high priority reliability & security recommendations are implemented if any
    3ID: SFR3 - Category: Telemetry - Deployment/Usage Telemetry
    4ID: SFR4 - Category: Telemetry - Telemetry Enablement FlexibilityYesYes if AVM Template Repo has been used
    5ID: SFR5 - Category: Composition - Availability Zones
    6ID: SFR6 - Category: Composition - Data Redundancy
    7ID: SNFR25 - Category: Composition - Resource Naming
    8ID: SNFR1 - Category: Testing - Prescribed TestsYesYes if all e2e test, version-check & linting checks passed
    9ID: SNFR2 - Category: Testing - E2E TestingYesYes if e2e tests passed
    10ID: SNFR3 - Category: Testing - AVM Compliance TestsYesYes if all e2e test, version-check & linting checks passed
    11ID: SNFR4 - Category: Testing - Unit TestsNA if no tests created in tests folder
    12ID: SNFR5 - Category: Testing - Upgrade TestsNAModule Owner to action this spec post-publish as appropriate
    13ID: SNFR6 - Category: Testing - Static Analysis/Linting TestsYesYes if all linting checks passed
    14ID: SNFR7 - Category: Testing - Idempotency TestsYesYes if e2e tests passed
    15ID: SNFR24 - Category: Testing - Testing Child, Extension & Interface ResourcesYesYes if e2e tests passed
    16ID: SNFR8 - Category: Contribution/Support - Module Owner(s) GitHub
    17ID: SNFR20 - Category: Contribution/Support - GitHub Teams Only
    18ID: SNFR9 - Category: Contribution/Support - AVM & PG Teams GitHub Repo Permissions
    19ID: SNFR10 - Category: Contribution/Support - MIT LicensingYesYes if AVM Template Repo has been used
    20ID: SNFR11 - Category: Contribution/Support - Issues Response TimesNAModule Owner to action this spec post-publish as appropriate
    21ID: SNFR12 - Category: Contribution/Support - Versions SupportedNAModule Owner to action this spec post-publish as appropriate
    22ID: SNFR23 - Category: Contribution/Support - GitHub Repo Labels
    23ID: SNFR14 - Category: Inputs - Data Types
    24ID: SNFR22 - Category: Inputs - Parameters/Variables for Resource IDs
    25ID: SNFR15 - Category: Documentation - Automatic Documentation GenerationYesYes if linting / docs check passed
    26ID: SNFR16 - Category: Documentation - Examples/E2EYesYes if e2e tests passed
    27ID: SNFR17 - Category: Release - Semantic VersioningYesYes if version-check check passed
    28ID: SNFR18 - Category: Release - Breaking ChangesNAModule Owner to action this spec post-publish as appropriate
    29ID: SNFR19 - Category: Publishing - Registries TargetedNAModule Owner to action this spec post-publish as appropriate
    30ID: SNFR21 - Category: Publishing - Cross Language CollaborationNAModule Owner to action this spec post-publish as appropriate
    31ID: RMFR1 - Category: Composition - Single Resource Only
    32ID: RMFR2 - Category: Composition - No Resource Wrapper Modules
    33ID: RMFR3 - Category: Composition - Resource Groups
    34ID: RMFR4 - Category: Composition - AVM Consistent Feature & Extension Resources Value AddYesYes if linting / terraform check passed
    35ID: RMFR5 - Category: Composition - AVM Consistent Feature & Extension Resources Value Add Interfaces/SchemasYesYes if linting / terraform check passed
    36ID: RMFR8 - Category: Composition - Dependency on child and other resources
    37ID: RMFR6 - Category: Inputs - Parameter/Variable Naming
    38ID: RMFR7 - Category: Outputs - Minimum Required OutputsYesYes if linting / terraform check passed
    39ID: RMNFR1 - Category: Naming - Module Naming
    40ID: RMNFR2 - Category: Inputs - Parameter/Variable Naming
    41ID: RMNFR3 - Category: Composition - RP CollaborationNAModule Owner to action this spec post-publish as appropriate
    42ID: PMFR1 - Category: Composition - Resource Group CreationNA if this is not a pattern module
    43ID: PMNFR1 - Category: Naming - Module NamingNA if this is not a pattern module
    44ID: PMNFR2 - Category: Composition - Use Resource Modules to Build a Pattern ModuleNA if this is not a pattern module
    45ID: PMNFR3 - Category: Composition - Use other Pattern Modules to Build a Pattern ModuleNA if this is not a pattern module
    46ID: PMNFR4 - Category: Hygiene - Missing Resource Module(s)NA if this is not a pattern module
    47ID: PMNFR5 - Category: Inputs - Parameter/Variable NamingNA if this is not a pattern module
    48ID: TFFR1 - Category: Composition - Cross-Referencing Modules
    49ID: TFFR2 - Category: Outputs - Additional Terraform OutputsYesYes if linting / terraform check passed
    50ID: TFNFR1 - Category: Documentation - Descriptions
    51ID: TFNFR2 - Category: Documentation - Module Documentation GenerationYesYes if linting / docs check passed
    52ID: TFNFR3 - Category: Contribution/Support - GitHub Repo Branch ProtectionYesYes if AVM Template Repo has been used
    53ID: TFNFR4 - Category: Composition - Code Styling - lower snake_casingYesYes if linting / terraform check passed
    54ID: TFNFR5 - Category: Testing - Test ToolingYesYes if linting / terraform check passed
    55ID: TFNFR6 - Category: Code Style - Resource & Data Order
    56ID: TFNFR7 - Category: Code Style - count & for_each Use
    57ID: TFNFR8 - Category: Code Style - Resource & Data Block OrdersYesYes if linting / avmfix check passed
    58ID: TFNFR9 - Category: Code Style - Module Block Order
    59ID: TFNFR10 - Category: Code Style - No Double Quotes in ignore_changes
    60ID: TFNFR11 - Category: Code Style - Null Comparison Toggle
    61ID: TFNFR12 - Category: Code Style - Dynamic for Optional Nested Objects
    62ID: TFNFR13 - Category: Code Style - Default Values with coalesce/try
    63ID: TFNFR14 - Category: Inputs - Not allowed variables
    64ID: TFNFR15 - Category: Code Style - Variable Definition OrderYesYes if linting / avmfix check passed
    65ID: TFNFR16 - Category: Code Style - Variable Naming RulesYesYes if linting / terraform check passed
    66ID: TFNFR17 - Category: Code Style - Variables with DescriptionsYesYes if linting / terraform check passed
    67ID: TFNFR18 - Category: Code Style - Variables with TypesYesYes if linting / terraform check passed
    68ID: TFNFR19 - Category: Code Style - Sensitive Data Variables
    69ID: TFNFR20 - Category: Code Style - Non-Nullable Defaults for collection values
    70ID: TFNFR21 - Category: Code Style - Discourage Nullability by DefaultYesYes if linting / avmfix check passed
    71ID: TFNFR22 - Category: Code Style - Avoid sensitive = falseYesYes if linting / avmfix check passed
    72ID: TFNFR23 - Category: Code Style - Sensitive Default Value ConditionsYesYes if linting / terraform check passed
    73ID: TFNFR24 - Category: Code Style - Handling Deprecated VariablesNAModule Owner to action this spec post-publish as appropriate
    74ID: TFNFR25 - Category: Code Style - Verified Modules RequirementsYesYes if linting / terraform check passed
    75ID: TFNFR26 - Category: Code Style - Providers in required_providersYesYes if linting / terraform check passed
    76ID: TFNFR27 - Category: Code Style - Provider Declarations in ModulesYesYes if linting / terraform check passed
    77ID: TFNFR29 - Category: Code Style - Sensitive Data OutputsYesYes if linting / avmfix check passed
    78ID: TFNFR30 - Category: Code Style - Handling Deprecated OutputsNAModule Owner to action this spec post-publish as appropriate
    79ID: TFNFR31 - Category: Code Style - locals.tf for Locals Only
    80ID: TFNFR32 - Category: Code Style - Alphabetical Local ArrangementYesYes if linting / avmfix check passed
    81ID: TFNFR33 - Category: Code Style - Precise Local Types
    82ID: TFNFR34 - Category: Code Style - Using Feature TogglesNAModule Owner to action this spec post-publish as appropriate
    83ID: TFNFR35 - Category: Code Style - Reviewing Potential Breaking ChangesNAModule Owner to action this spec post-publish as appropriate
    84ID: TFNFR36 - Category: Code Style - Setting prevent_deletion_if_contains_resources
    85ID: TFNFR37 - Category: Code Style - Tool Usage by Module Owner
  7. 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.

  8. The module reviewer reviews the code in the PR and leaves comments to request any necessary updates.

  9. 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

    βž• AVM Terraform Module Review Initiation Message
    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.
  10. The module owner updates the check list and the table in the AVM-Review issue and notifies the module reviewer in a comment.

  11. 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’.

  12. The module reviewer approves the AVM-Review-PR, and leaves the following comment on the AVM-Review issue with the following comment.

    βž• AVM Terraform Module Review Completion Message
    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. You may proceed with [publishing](/Azure-Verified-Modules/contributing/terraform/terraform-contribution-flow/owner-contribution-flow/#7-publish-the-module) this module to the HashiCorp Terraform Registry with an initial pre-release version of v0.1.0. Please keep future versions also pre-release i.e. < 1.0.0 until AVM becomes generally available (GA) of which the AVM team will notify you.
    
    **Requested Action**: Once published 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. Requesting AVM team to close this module proposal and mark the module available in the module index.
    Terraform Registry Link: <REPLACE WITH THE LINK OF THE MODULE IN TERRAFORM REGISTRY>
    GitHub Repo Link: <REPLACE WITH THE LINK OF THE MODULE IN GITHUB>"
  13. 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.

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

  • Git
  • Visual Studio Code (VS Code)
    • Extensions:
      • editorconfig.editorconfig, streetsidesoftware.code-spell-checker, ms-vsliveshare.vsliveshare, medo64.render-crlf, vscode-icons-team.vscode-icons
      • VS Code will recommend automatically to install these when you open this repo, or a fork of it, in VS Code.
  • Hugo Extended

winget Install Commands

To install winget follow the install instructions here.

winget install --id 'Git.Git'
winget install --id 'Microsoft.VisualStudioCode'
winget install --id 'Hugo.Hugo.Extended'

Other requirements

Useful Resources

Below are links to a number of useful resources to have when contributing to AVM:

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.