Bicep Specific Specification
Legacy content
The content on this website has been deprecated and will be removed in the future.
Please refer to the new documentation under the Bicep Spefications chapter for the most up-to-date information.
This page contains the Bicep specific requirements for AVM modules (Resource and Pattern modules) that ALL Bicep AVM modules MUST meet. These requirements are in addition to the Shared Specification requirements that ALL AVM modules MUST meet.
The following table summarizes the category identification codes used in this specification:
Scope | Functional requirements | Non-functional requirements |
---|---|---|
Shared requirements (resource & pattern modules) | BCPFR | BCPNFR |
Resource module level requirements | N/A | BCPRMNFR |
Pattern module level requirements | N/A | N/A |
Listed below are both functional and non-functional requirements for Bicep AVM modules (Resource and Pattern).
Module owners MAY cross-references other modules to build either Resource or Pattern modules.
However, they MUST be referenced only by a public registry reference to a pinned version e.g. br/public:avm/[res|ptn|utl]/<publishedModuleName>:>version<
. They MUST NOT use local parent path references to a module e.g. ../../xxx/yyy.bicep
.
Although, child modules, that are children of the primary resources being deployed by the AVM Resource Module, MAY be specified via local child path e.g. child/resource.bicep
.
Modules MUST NOT contain references to non-AVM modules.
Module owners MAY define common RBAC Role Definition names and IDs within a variable to allow consumers to define a RBAC Role Definition by their name rather than their ID, this should be self contained within the module themselves.
However, they MUST use only the official RBAC Role Definition name within the variable and nothing else.
To meet the requirements of BCPFR2 , BCPNFR5 and BCPNFR6 you MUST use the below code sample in your AVM Modules to achieve this.
@description('''Required. You can provide either the display name (note not all roles are supported, check module documentation) of the role definition, or its fully qualified ID in the following format: `/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11`.''')
param roleDefinitionIdOrName string
var builtInRbacRoleNames = {
Owner: '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
Contributor: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c'
Reader: '/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7'
'Role Based Access Control Administrator (Preview)': '/providers/Microsoft.Authorization/roleDefinitions/f58310d9-a9f6-439a-9e8d-f62e7b41a168'
'User Access Administrator': '/providers/Microsoft.Authorization/roleDefinitions/18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'
//Other RBAC Role Definitions Names & IDs can be added here as needed for your module
}
var roleDefinitionIdMappedResult = (contains(builtInRbacRoleNames, roleDefinitionIdOrName) ? builtInRbacRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName)
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
//Other properties removed for ease of reading
properties: {
roleDefinitionId: roleDefinitionIdMappedResult
//Other properties removed for ease of reading
}
}
To comply with specifications outlined in
SFR3
&
SFR4
you MUST incorporate the following code snippet into your modules. Place this code sample in the “top level” main.bicep
file; it is not necessary to include it in any nested Bicep files (child modules).
@description('Optional. Location for all resources.')
param location string = resourceGroup().location
@description('Optional. Enable/Disable usage telemetry for module.')
param enableTelemetry bool = true
#disable-next-line no-deployments-resources
resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) {
name: take('46d3xbcp.res.compute-virtualmachine.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}', 64)
properties: {
mode: 'Incremental'
template: {
'$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#'
contentVersion: '1.0.0.0'
resources: []
outputs: {
telemetry: {
type: 'String'
value: 'For more information, see https://aka.ms/avm/TelemetryInfo'
}
}
}
}
}
To implement requirement SFR5 , the following convention SHOULD apply:
In this case, the parameter should be implemented like
@description('Optional. The Availability Zones to place the resources in.')
@allowed([
1
2
3
])
param zones int[] = [
1
2
3
]
resource myResource (...) {
(...)
properties: {
(...)
zones: map(zones, zone => string(zone))
}
}
In this case, the parameter should be implemented using a singular-named zone
parameter of type int
like
@description('Required. The Availability Zone to place the resource in. If set to 0, then Availability Zone is not set.')
@allowed([
0
1
2
3
])
param zone int
resource myResource (...) {
(...)
properties: {
(...)
zones: zone != 0 ? [ string(zone) ] : null
}
}
To simplify the consumption experience for module consumers when interacting with complex data types input parameters, mainly objects and arrays, the Bicep feature of User-Defined Types MUST be used and declared.
User-Defined Types are GA in Bicep as of version v0.21.1, please ensure you have this version installed as a minimum.
User-Defined Types allow intellisense support in supported IDEs (e.g. Visual Studio Code) for complex input parameters using arrays and objects.
While the transition of CARML modules into AVM is complete, retrofitting User-Defined Types for all modules will take a considerable amount of time.
Therefore, the addition of User-Defined Types is currently NOT mandated/enforced. However, past their initial release, all modules MUST implement User-Defined Types prior to the release of their next version.
Modules will have lots of parameters that will differ in their requirement type (required, optional, etc.). To help consumers understand what each parameter’s requirement type is, module owners MUST add the requirement type to the beginning of each parameter’s description. Below are the requirement types with a definition and example for the description decorator:
Parameter Requirement Type | Definition | Example Description Decorator |
---|---|---|
Required | The parameter value must be provided. The parameter does not have a default value and hence the module expects and requires an input. | @description('Required. <PARAMETER DESCRIPTION HERE...>') |
Conditional | The parameter value can be optional or required based on a condition, mostly based on the value provided to other parameters. Should contain a sentence starting with ‘Required if (…).’ to explain the condition. | @description('Conditional. <PARAMETER DESCRIPTION HERE...>') |
Optional | The parameter value is not mandatory. The module provides a default value for the parameter. | @description('Optional. <PARAMETER DESCRIPTION HERE...>') |
Generated | The parameter value is generated within the module and should not be specified as input in most cases. A common example of this is the utcNow() function that is only supported as the input for a parameter value, and not inside a variable. | @description('Generated. <PARAMETER DESCRIPTION HERE...>') |
This script/tool is currently being developed by the AVM team and will be made available very soon.
Bicep modules documentation MUST be automatically generated via the provided script/tooling from the AVM team, providing the following headings:
- Title
- Description
- Navigation
- Resource Types
- Usage Examples
- Parameters
- Outputs
- Cross-referenced modules
Usage examples for Bicep modules MUST be provided in the following formats:
Bicep file (orchestration module style) -
.bicep
module <resourceName> 'br/public:avm/[res|ptn|utl]/<publishedModuleName>:>version<' = { name: '${uniqueString(deployment().name, location)}-test-<uniqueIdentifier>' params: { (...) } }
JSON / ARM Template Parameter Files -
.json
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { (...) } }
The above formats are currently automatically taken & generated from thetests/e2e
tests. It is enough to run theSet-ModuleReadMe
orSet-AVMModule
functions (from theutilities
folder) to update the usage examples in the readme(s).
Bicep Parameter Files (.bicepparam
) are being reviewed and considered by the AVM team for the usability and features at this time and will likely be added in the future.
Bicep modules MAY provide parameter input examples for parameters using the metadata.example
property via the @metadata()
decorator.
Example:
@metadata({
example: 'uksouth'
})
@description('Optional. Location for all resources.')
param location string = resourceGroup().location
@metadata({
example: '''
{
keyName: 'myKey'
keyVaultResourceId: '/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/myvault'
keyVersion: '6d143c1a0a6a453daffec4001e357de0'
userAssignedIdentityResourceId '/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/my-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity'
}
'''
})
@description('Optional. The customer managed key definition.')
param customerManagedKey customerManagedKeyType
It is planned that these examples are automatically added to the module readme’s parameter descriptions when running either the Set-ModuleReadMe
or Set-AVMModule
scripts (available in the utilities folder).
As per BCPFR2 , module owners MAY define common RBAC Role Definition names and IDs within a variable to allow consumers to define a RBAC Role Definition by their name rather than their ID.
Module owners SHOULD NOT map every RBAC Role Definition within this variable as it can cause the module to bloat in size and cause consumption issues later when stitched together with other modules due to the 4MB ARM Template size limit.
Therefore module owners SHOULD only map the most applicable and common RBAC Role Definition names for their module and SHOULD NOT exceed 15 RBAC Role Definitions in the variable.
Remember if the RBAC Role Definition name is not included in the variable this does not mean it cannot be declared, used and assigned to an identity via an RBAC Role Assignment as part of a module, as any RBAC Role Definition can be specified via its ID without being in the variable.
Review the Bicep Contribution Guide’s ‘RBAC Role Definition Name Mapping’ section for a code sample to achieve this requirement.
Module owners MUST include the following roles in the variable for RBAC Role Definition names:
- Owner - ID:
8e3af657-a8ff-443c-a75c-2fe8c4bcb635
- Contributor - ID:
b24988ac-6180-42a0-ab88-20f7382dd24c
- Reader - ID:
acdd72a7-3385-48ef-bd42-f606fba81ae7
- User Access Administrator - ID:
18d7d88d-d35e-4fb5-a5c3-7773c20a72d9
- Role Based Access Control Administrator (Preview) - ID:
f58310d9-a9f6-439a-9e8d-f62e7b41a168
Review the Bicep Contribution Guide’s ‘RBAC Role Definition Name Mapping’ section for a code sample to achieve this requirement.
Module owners SHOULD use lower camelCasing for naming the following:
- Parameters
- Variables
- Outputs
- User Defined Types
- Resources (symbolic names)
- Modules (symbolic names)
For example: camelCasingExample
(lowercase first word (entirely), with capital of first letter of all other words and rest of word in lowercase)
To meet
SNFR17
and depending on the changes you make, you may need to bump the version in the version.json
file.
{
"$schema": "https://aka.ms/bicep-registry-module-version-file-schema#",
"version": "0.1",
"pathFilters": [
"./main.json"
]
}
The version
value is in the form of MAJOR.MINOR
. The PATCH version will be incremented by the CI automatically when publishing the module to the Public Bicep Registry once the corresponding pull request is merged. Therefore, contributions that would only require an update of the patch version, can keep the version.json
file intact.
For example, the version
value should be:
0.1
for new modules, so that they can be released asv0.1.0
.1.0
once the module owner signs off the module is stable enough for it’s first Major release ofv1.0.0
.0.x
for all feature updates between the first releasev0.1.0
and the first Major release ofv1.0.0
.
Module owners MUST name their test .bicep
files in the /tests/e2e/<defaults/waf-aligned/max/etc.>
directories: main.test.bicep
as the test framework (CI) relies upon this name.
Module owners MUST use the below tooling for unit/linting/static/security analysis tests. These are also used in the AVM Compliance Tests.
- PSRule for Azure
- Pester
- Some tests are provided as part of the AVM Compliance Tests, but you are free to also use Pester for your own tests.
Module owners MUST invoke the module in their test using the syntax:
module testDeployment '../../../main.bicep' =
Example 1: Working example with a single deployment
module testDeployment '../../../main.bicep' = {
scope: resourceGroup
name: '${uniqueString(deployment().name, location)}-test-${serviceShort}'
params: {
(...)
}
}
Example 2: Working example using a deployment loop
@batchSize(1)
module testDeployment '../../main.bicep' = [for iteration in [ 'init', 'idem' ]: {
scope: resourceGroup
name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}'
params: {
(...)
}
}]
The syntax is used by the ReadMe-generating utility to identify, pull & format usage examples.
By default, the ReadMe-generating utility will create usage examples headers based on each e2e
folder’s name.
Module owners MAY provide a custom name & description by specifying the metadata blocks name
& description
in their main.test.bicep
test files.
For example:
metadata name = 'Using Customer-Managed-Keys with System-Assigned identity'
metadata description = 'This instance deploys the module using Customer-Managed-Keys using a System-Assigned Identity. This required the service to be deployed twice, once as a pre-requisite to create the System-Assigned Identity, and once to use it for accessing the Customer-Managed-Key secret.'
would lead to a header in the module’s readme.md
file along the lines of
### Example 1: _Using Customer-Managed-Keys with System-Assigned identity_
This instance deploys the module using Customer-Managed-Keys using a System-Assigned Identity. This required the service to be deployed twice, once as a pre-requisite to create the System-Assigned Identity, and once to use it for accessing the Customer-Managed-Key secret.
As part of the “initial Pull Request” (that publishes the first version of the module), module owners MUST add an entry to the AVM Module Issue template
file in the BRM repository (
here
).
Through this approach, the AVM core team will allow raising a bug or feature request for a module, only after the module gets merged to the BRM repository.
The module name entry MUST be added to the dropdown list with id module-name-dropdown
as an option, in alphabetical order.
Module owners MUST ensure that the module name is added in alphabetical order, to simplify selecting the right module name when raising an AVM module issue.
Example - AVM Module Issue template
module name entry for the Bicep resource module of Azure Virtual Network (avm/res/network/virtual-network
):
- type: dropdown
id: module-name-dropdown
attributes:
label: Module Name
description: Which existing AVM module is this issue related to?
options:
...
- "avm/res/network/virtual-network"
...
For each test case in the e2e
folder, you can optionally add post-deployment Pester tests that are executed once the corresponding deployment completed and before the removal logic kicks in.
To leverage the feature you MUST:
Use Pester as a test framework in each test file
Name the file with the suffix
"*.tests.ps1"
Place each test file the
e2e
test’s folder or any subfolder (e.g.,e2e/max/myTest.tests.ps1
ore2e/max/tests/myTest.tests.ps1
)Implement an input parameter
TestInputData
in the following way:param ( [Parameter(Mandatory = $false)] [hashtable] $TestInputData = @{} )
Through this parameter you can make use of every output the
main.test.bicep
file returns, as well as the path to the test template file in case you want to extract data from it directly.For example, with an output such as
output resourceId string = testDeployment[1].outputs.resourceId
defined in themain.test.bicep
file, the$TestInputData
would look like:$TestInputData = @{ DeploymentOutputs = @{ resourceId = @{ Type = "String" Value = "/subscriptions/***/resourceGroups/dep-***-keyvault.vaults-kvvpe-rg/providers/Microsoft.KeyVault/vaults/***kvvpe001" } } ModuleTestFolderPath = "/home/runner/work/bicep-registry-modules/bicep-registry-modules/avm/res/key-vault/vault/tests/e2e/private-endpoint" }
A full test file may look like:
To improve the usability of primitive module properties declared as strings, you SHOULD declare them using a type which better represents them, and apply any required casting in the module on behalf of the user.
For reference, please refer to the following examples:
@allowed([
'false'
'true'
])
param myParameterValue string = 'false'
resource myResource '(...)' = {
(...)
properties: {
myParameter: myParameterValue
}
}
param myParameterValue string = false
resource myResource '(...)' = {
(...)
properties: {
myParameter: string(myParameterValue)
}
}
@allowed([
'1'
'2'
'3'
])
param zones array
resource myResource '(...)' = {
(...)
properties: {
zones: zones
}
}
@allowed([
1
2
3
])
param zones int[]
resource myResource '(...)' = {
(...)
properties: {
zones: map(zones, zone => string(zone))
}
}
Listed below are both functional and non-functional requirements for Bicep AVM Resource Modules .
Module owners MUST create the defaults
, waf-aligned
folders within their /tests/e2e/
directory in their resource module source code and SHOULD create a max
folder also. Module owners CAN create additional folders as required. Each folder will be used as described for various test cases.
The defaults
folder contains a test instance that deploys the module with the minimum set of required parameters.
This includes input parameters of type Required
plus input parameters of type Conditional
marked as required for WAF compliance.
This instance has heavy reliance on the default values for other input parameters. Parameters of type Optional
SHOULD NOT be used.
The waf-aligned
folder contains a test instance that deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.
This includes input parameters of type Required
, parameters of type Conditional
marked as required for WAF compliance, and parameters of type Optional
useful for WAF compliance.
Parameters and dependencies which are not needed for WAF compliance, SHOULD NOT be included.
The max
folder contains a test instance that deploys the module using a large parameter set, enabling most of the modules’ features.
The purpose of this instance is primarily parameter validation and not necessarily to serve as a real example scenario. Ideally, all features, extension resources and child resources should be enabled in this test, unless not possible due to conflicts, e.g., in case parameters are mutually exclusive.
Please note that this test is not mandatory to have, but recommended for bulk parameter validation. It can be skipped in case the module parameter validation is covered already by additional, more scenario-specific tests.
Additional folders CAN
be created by module owners as required.
For example, to validate parameters not covered by the max
test due to conflicts, or to provide a real example scenario for a specific use case.
If a module can deploy varying styles of the same resource, e.g., VMs can be Linux or Windows, each style should be tested as both defaults
and waf-aligned
. These names should be used as suffixes in the directory name to denote the style, e.g., for a VM we would expect to see:
/tests/e2e/defaults.linux/main.test.bicep
/tests/e2e/waf-aligned.linux/main.test.bicep
/tests/e2e/defaults.windows/main.test.bicep
/tests/e2e/waf-aligned.windows/main.test.bicep