# Bicep Interfaces Bicep Module Interface Specifications for the Azure Verified Modules (AVM) program This chapter details the interfaces/schemas for the AVM Resource Modules features/extension resources as referenced in RMFR4 and RMFR5. Diagnostic Settings Important Allowed values for logs and metric categories or category groups MUST NOT be specified to keep the module implementation evergreen for any new categories or category groups added by RPs, without module owners having to update a list of allowed values and cut a new release of their module. Diagnostic Settings Variant 1: Diagnostic settings with both logs & metrics Variant 2: Diagnostic settings with only metrics Variant 3: Diagnostic settings with only logs ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The diagnostic settings of the service.') param diagnosticSettings diagnosticSettingFullType[]? // ============= // // Resources // // ============= // resource >singularMainResourceType<_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' properties: { storageAccountId: diagnosticSetting.?storageAccountResourceId workspaceId: diagnosticSetting.?workspaceResourceId eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId eventHubName: diagnosticSetting.?eventHubName metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): { category: group.category enabled: group.?enabled ?? true timeGrain: null }] logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): { categoryGroup: group.?categoryGroup category: group.?category enabled: group.?enabled ?? true }] marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType } scope: >singularMainResourceType< }] ​ Input Example with Values diagnosticSettings: [ { name: 'diagSetting1' logCategoriesAndGroups: [ { category: 'AzurePolicyEvaluationDetails' } { category: 'AuditEvent' } ] metricCategories: [ { category: 'AllMetrics' } ] logAnalyticsDestinationType: 'Dedicated' workspaceResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}' storageAccountResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}' eventHubAuthorizationRuleResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventHub/namespaces/{namespaceName}/eventhubs/{eventHubName}/authorizationrules/{authorizationRuleName}' eventHubName: '{eventHubName}' marketplacePartnerResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{partnerResourceProvider}/{partnerResourceType}/{partnerResourceName}' } ] ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { diagnosticSettingMetricsOnlyType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The diagnostic settings of the service.') param diagnosticSettings diagnosticSettingMetricsOnlyType[]? // ============= // // Resources // // ============= // resource >singularMainResourceType<_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' properties: { storageAccountId: diagnosticSetting.?storageAccountResourceId workspaceId: diagnosticSetting.?workspaceResourceId eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId eventHubName: diagnosticSetting.?eventHubName metrics: [for group in (diagnosticSetting.?metricCategories ?? [ { category: 'AllMetrics' } ]): { category: group.category enabled: group.?enabled ?? true timeGrain: null }] marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType } scope: >singularMainResourceType< }] ​ Input Example with Values diagnosticSettings: [ { name: 'diagSetting1' metricCategories: [ { category: 'AllMetrics' } ] logAnalyticsDestinationType: 'Dedicated' workspaceResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}' storageAccountResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}' eventHubAuthorizationRuleResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventHub/namespaces/{namespaceName}/eventhubs/{eventHubName}/authorizationrules/{authorizationRuleName}' eventHubName: '{eventHubName}' marketplacePartnerResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{partnerResourceProvider}/{partnerResourceType}/{partnerResourceName}' } ] ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { diagnosticSettingLogsOnlyType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The diagnostic settings of the service.') param diagnosticSettings diagnosticSettingLogsOnlyType[]? // ============= // // Resources // // ============= // resource >singularMainResourceType<_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [for (diagnosticSetting, index) in (diagnosticSettings ?? []): { name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' properties: { storageAccountId: diagnosticSetting.?storageAccountResourceId workspaceId: diagnosticSetting.?workspaceResourceId eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId eventHubName: diagnosticSetting.?eventHubName logs: [for group in (diagnosticSetting.?logCategoriesAndGroups ?? [ { categoryGroup: 'allLogs' } ]): { categoryGroup: group.?categoryGroup category: group.?category enabled: group.?enabled ?? true }] marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType } scope: >singularMainResourceType< }] ​ Input Example with Values diagnosticSettings: [ { name: 'diagSetting1' logCategoriesAndGroups: [ { category: 'AzurePolicyEvaluationDetails' } { category: 'AuditEvent' } ] logAnalyticsDestinationType: 'Dedicated' workspaceResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName}' storageAccountResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}' eventHubAuthorizationRuleResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.EventHub/namespaces/{namespaceName}/eventhubs/{eventHubName}/authorizationrules/{authorizationRuleName}' eventHubName: '{eventHubName}' marketplacePartnerResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{partnerResourceProvider}/{partnerResourceType}/{partnerResourceName}' } ] Note In the provided example for Diagnostic Settings, both logs and metrics are enabled for the associated resource. However, it is IMPORTANT to note that certain resources may not support both diagnostic setting types/categories. In such cases, the resource configuration MUST be modified accordingly to ensure proper functionality and compliance with system requirements. Role Assignments ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. Array of role assignments to create.') param roleAssignments roleAssignmentType[]? // ============= // // Variables // // ============= // var builtInRoleNames = { // Add other relevant built-in roles here for your resource as per BCPNFR5 Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') 'Role Based Access Control Administrator (Preview)': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168') 'User Access Administrator': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9') } var formattedRoleAssignments = [ for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, { roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') ? roleAssignment.roleDefinitionIdOrName : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName)) }) ] // ============= // // Resources // // ============= // resource >singularMainResourceType<_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ for (roleAssignment, index) in (formattedRoleAssignments ?? []): { name: roleAssignment.?name ?? guid(>singularMainResourceType<.id, roleAssignment.principalId, roleAssignment.roleDefinitionId) properties: { roleDefinitionId: roleAssignment.roleDefinitionId principalId: roleAssignment.principalId description: roleAssignment.?description principalType: roleAssignment.?principalType condition: roleAssignment.?condition conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId } scope: >singularMainResourceType< } ] ​ Input Example with Values roleAssignments: [ { roleDefinitionIdOrName: 'Owner' principalId: nestedDependencies.outputs.managedIdentityPrincipalId principalType: 'ServicePrincipal' } { roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' principalId: nestedDependencies.outputs.managedIdentityPrincipalId principalType: 'ServicePrincipal' } { roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') principalId: nestedDependencies.outputs.managedIdentityPrincipalId principalType: 'ServicePrincipal' } { name: guid('Custom role assignment name seed') roleDefinitionIdOrName: 'Storage Blob Data Reader' principalId: '00000000-0000-0000-0000-000000000000' principalType: 'Group' description: 'Group with read-only access' condition: '@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase 'foo_storage_container'' conditionVersion: '2.0' } ] Details on child, extension and cross-referenced resources: Modules MUST support Role Assignments on child, extension and cross-referenced resources as well as the primary resource via parameters/variables Resource Locks ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { lockType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The lock settings of the service.') param lock lockType? // ============= // // Resources // // ============= // resource >singularMainResourceType<_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { name: lock.?name ?? 'lock-${name}' properties: { level: lock.?kind ?? '' notes: lock.?notes ?? (lock.?kind == 'CanNotDelete' ? 'Cannot delete resource or child resources.' : 'Cannot delete or modify the resource or child resources.') } scope: >singularMainResourceType< } ​ Input Example with Values lock: { kind: 'CanNotDelete' name: 'myCustomLockName' notes: 'This is a custom lock note.' } Details on child and extension resources: Locks SHOULD be able to be set for child resources of the primary resource in resource modules Details on cross-referenced resources: Locks MUST be automatically applied to cross-referenced resources if the primary resource has a lock applied. This MUST also be able to be turned off for each of the cross-referenced resources by the module consumer via a parameter/variable if they desire An example of this is a Key Vault module that has a Private Endpoints enabled. If a lock is applied to the Key Vault via the lock parameter/variable then the lock should also be applied to the Private Endpoint automatically, unless the privateEndpointLock/private_endpoint_lock (example name) parameter/variable is set to None Tags ​ User Defined Type, Parameter & Resource Example @description('Optional. Tags of the resource.') param tags object? ​ Input Example with Values tags: { key: 'value' 'another-key': 'another-value' integers: 123 } Details on child, extension and cross-referenced resources: Tags MUST be automatically applied to child, extension and cross-referenced resources, if tags are applied to the primary resource. By default, all tags set for the primary resource will automatically be passed down to child, extension and cross-referenced resources. This MUST be able to be overridden by the module consumer so they can specify alternate tags for child, extension and cross-referenced resources, if they desire via a parameter/variable If overridden by the module consumer, no merge/union of tags will take place from the primary resource and only the tags specified for the child, extension and cross-referenced resources will be applied Managed Identities ​ Variant 1: Both user- & system-assigned identities supported Variant 2: Only system-assigned identities supported Variant 3: Only user-assigned identities supported ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The managed identity definition for this resource.') param managedIdentities managedIdentityAllType? // ============= // // Variables // // ============= // var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } var identity = !empty(managedIdentities) ? { type: (managedIdentities.?systemAssigned ?? false) ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null } : null // ============= // // Resources // // ============= // resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: name identity: identity properties: { ... // other properties } } // =========== // // Outputs // // =========== // @description('The principal ID of the system assigned identity.') output systemAssignedMIPrincipalId string? = >singularMainResourceType<.?identity.?principalId ​ Input Example with Values managedIdentities: { systemAssigned: true userAssignedResourceIds: [ '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}' '/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName2}' ] } ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { managedIdentityOnlySysAssignedType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The managed identity definition for this resource.') param managedIdentities managedIdentityOnlySysAssignedType? // ============= // // Variables // // ============= // var identity = !empty(managedIdentities) ? { type: (managedIdentities.?systemAssigned ?? false) ? 'SystemAssigned' : null } : null // ============= // // Resources // // ============= // resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: name identity: identity properties: { ... // other properties } } // =========== // // Outputs // // =========== // @description('The principal ID of the system assigned identity.') output systemAssignedMIPrincipalId string? = >singularMainResourceType<.?identity.?principalId ​ Input Example with Values managedIdentities: { systemAssigned: true } ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { managedIdentityOnlyUserAssignedType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The managed identity definition for this resource.') param managedIdentities managedIdentityOnlyUserAssignedType? // ============= // // Variables // // ============= // var formattedUserAssignedIdentities = reduce(map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, (cur, next) => union(cur, next)) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } var identity = !empty(managedIdentities) ? { type: !empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None' userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null } : null // ============= // // Resources // // ============= // resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: name identity: identity properties: { ... // other properties } } ​ Input Example with Values managedIdentities: { userAssignedResourceIds: [ '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}' '/subscriptions/{subscriptionId2}/resourceGroups/{resourceGroupName2}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName2}' ] } Reason for differences in User Assigned data type in languages: We do not foresee the Managed Identity Resource Provider team to ever add additional properties within the empty object ({}) value required on the input of a User Assigned Managed Identity. In Bicep we therefore have removed the need for this to be declared and just converted it to a simple array of Resource IDs Private Endpoints Private Endpoints Variant 1: The default service (groupId) can be assumed Variant 2: The default service (groupId) cannot be assumed E.g., for services that only have one private endpoint type. ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') param privateEndpoints privateEndpointSingleServiceType[]? var enableReferencedModulesTelemetry = false // resource module // ============= // // Resources // // ============= // module >singularMainResourceType<_privateEndpoints 'br/public:avm/res/network/private-endpoint:>version<' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { name: '${uniqueString(deployment().name, location)}->singularMainResourceType<-PrivateEndpoint-${index}' scope: resourceGroup( split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2], split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4] ) params: { // Variant 1: A default service can be assumed (i.e., for services that only have one private endpoint type) name: privateEndpoint.?name ?? 'pep-${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.?service ?? '>defaultServiceName<'}-${index}' privateLinkServiceConnections: privateEndpoint.?isManualConnection != true ? [ { name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.?service ?? '>defaultServiceName<'}-${index}' properties: { privateLinkServiceId: >singularMainResourceType<.id groupIds: [ privateEndpoint.?service ?? '>defaultServiceName<' ] } } ] : null manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true ? [ { name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.?service ?? '>defaultServiceName<'}-${index}' properties: { privateLinkServiceId: >singularMainResourceType<.id groupIds: [ privateEndpoint.?service ?? '>defaultServiceName<' ] requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.' } } ] : null subnetResourceId: privateEndpoint.subnetResourceId enableTelemetry: enableReferencedModulesTelemetry // resource module enableTelemetry: privateEndpoint.?enableTelemetry ?? enableTelemetry // pattern / utility module location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup roleAssignments: privateEndpoint.?roleAssignments tags: privateEndpoint.?tags ?? tags customDnsConfigs: privateEndpoint.?customDnsConfigs ipConfigurations: privateEndpoint.?ipConfigurations applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @description('The private endpoints of the resource.') output privateEndpoints privateEndpointOutputType[] = [ for (pe, index) in (privateEndpoints ?? []): { name: >singularMainResourceType<_privateEndpoints[index].outputs.name resourceId: >singularMainResourceType<_privateEndpoints[index].outputs.resourceId groupId: >singularMainResourceType<_privateEndpoints[index].outputs.?groupId! customDnsConfigs: >singularMainResourceType<_privateEndpoints[index].outputs.customDnsConfigs networkInterfaceResourceIds: >singularMainResourceType<_privateEndpoints[index].outputs.networkInterfaceResourceIds } ] // =============== // // Definitions // // =============== // @export() type privateEndpointOutputType = { @description('The name of the private endpoint.') name: string @description('The resource ID of the private endpoint.') resourceId: string @description('The group Id for the private endpoint Group.') groupId: string? @description('The custom DNS configurations of the private endpoint.') customDnsConfigs: { @description('FQDN that resolves to private endpoint IP address.') fqdn: string? @description('A list of private IP addresses of the private endpoint.') ipAddresses: string[] }[] @description('The IDs of the network interfaces associated with the private endpoint.') networkInterfaceResourceIds: string[] } ​ Input Example with Values privateEndpoints: { { name: 'myPeName' privateLinkServiceConnectionName: 'myPrivateLinkConnectionName' lock: 'CanNotDelete' tags: { 'hidden-title': 'This is visible in the resource name' } subnetResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mysubnet' resourceGroupResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg' applicationSecurityGroupResourceIds: [ '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/applicationSecurityGroups/myAsg' ] privateDnsZoneGroup: { privateDnsZoneGroupConfigs: [ { name: 'config' privateDnsZoneResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/privateDnsZones/myZone' } ] } customDnsConfigs: [ { fqdn: 'fqdn1.example.com' ipAddresses: [ '10.0.0.1', '10.0.0.2' ] } ] networkInterfaceName: 'nic1' ipConfigurations: [ { name: 'ipconfig1' groupId: 'vault' memberName: 'default' privateIpAddress: '10.0.0.7' } ] roleAssignments: [ { roleDefinitionIdOrName: 'Owner' principalId: '11111111-1111-1111-1111-111111111111' principalType: 'ServicePrincipal' } { roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions','acdd72a7-3385-48ef-bd42-f606fba81ae7') principalId: '11111111-1111-1111-1111-111111111111' principalType: 'ServicePrincipal' } ] } } E.g., for services that have more than one private endpoint type, like a Storage Account (blob, file, etc.) ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { privateEndpointMultiServiceType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') param privateEndpoints privateEndpointMultiServiceType[]? var enableReferencedModulesTelemetry = false // resource module // ============= // // Resources // // ============= // module >singularMainResourceType<_privateEndpoints 'br/public:avm/res/network/private-endpoint:>version<' = [for (privateEndpoint, index) in (privateEndpoints ?? []): { name: '${uniqueString(deployment().name, location)}->singularMainResourceType<-PrivateEndpoint-${index}' scope: resourceGroup( split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2], split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4] ) params: { // Variant 2: A default service cannot be assumed (i.e., for services that have more than one private endpoint type, like Storage Account) name: privateEndpoint.?name ?? 'pep-${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.service}-${index}' privateLinkServiceConnections: privateEndpoint.?isManualConnection != true ? [ { name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.service}-${index}' properties: { privateLinkServiceId: >singularMainResourceType<.id groupIds: [ privateEndpoint.service ] } } ] : null manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true ? [ { name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(>singularMainResourceType<.id, '/'))}-${privateEndpoint.service}-${index}' properties: { privateLinkServiceId: >singularMainResourceType<.id groupIds: [ privateEndpoint.service ] requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.' } } ] : null subnetResourceId: privateEndpoint.subnetResourceId enableTelemetry: enableReferencedModulesTelemetry // resource module enableTelemetry: privateEndpoint.?enableTelemetry ?? enableTelemetry // pattern / utility module location: privateEndpoint.?location ?? reference(split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location lock: privateEndpoint.?lock ?? lock privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup roleAssignments: privateEndpoint.?roleAssignments tags: privateEndpoint.?tags ?? tags customDnsConfigs: privateEndpoint.?customDnsConfigs ipConfigurations: privateEndpoint.?ipConfigurations applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } }] @description('The private endpoints of the resource.') output privateEndpoints privateEndpointOutputType[] = [ for (pe, index) in (privateEndpoints ?? []): { name: >singularMainResourceType<_privateEndpoints[index].outputs.name resourceId: >singularMainResourceType<_privateEndpoints[index].outputs.resourceId groupId: >singularMainResourceType<_privateEndpoints[index].outputs.?groupId! customDnsConfigs: >singularMainResourceType<_privateEndpoints[index].outputs.customDnsConfigs networkInterfaceResourceIds: >singularMainResourceType<_privateEndpoints[index].outputs.networkInterfaceResourceIds } ] // =============== // // Definitions // // =============== // @export() type privateEndpointOutputType = { @description('The name of the private endpoint.') name: string @description('The resource ID of the private endpoint.') resourceId: string @description('The group Id for the private endpoint Group.') groupId: string? @description('The custom DNS configurations of the private endpoint.') customDnsConfigs: { @description('FQDN that resolves to private endpoint IP address.') fqdn: string? @description('A list of private IP addresses of the private endpoint.') ipAddresses: string[] }[] @description('The IDs of the network interfaces associated with the private endpoint.') networkInterfaceResourceIds: string[] } ​ Input Example with Values privateEndpoints: { { name: 'myPeName' privateLinkServiceConnectionName: 'myPrivateLinkConnectionName' lock: 'CanNotDelete' tags: { 'hidden-title': 'This is visible in the resource name' } service: 'blob' subnetResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mysubnet' resourceGroupResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg' applicationSecurityGroupResourceIds: [ '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/applicationSecurityGroups/myAsg' ] privateDnsZoneGroup: { privateDnsZoneGroupConfigs: [ { name: 'config' privateDnsZoneResourceId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.Network/privateDnsZones/myZone' } ] } customDnsConfigs: [ { fqdn: 'fqdn1.example.com' ipAddresses: [ '10.0.0.1', '10.0.0.2' ] } ] networkInterfaceName: 'nic1' ipConfigurations: [ { name: 'ipconfig1' groupId: 'blob' memberName: 'default' privateIpAddress: '10.0.0.7' } ] roleAssignments: [ { roleDefinitionIdOrName: 'Owner' principalId: '11111111-1111-1111-1111-111111111111' principalType: 'ServicePrincipal' } { roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions','acdd72a7-3385-48ef-bd42-f606fba81ae7') principalId: '11111111-1111-1111-1111-111111111111' principalType: 'ServicePrincipal' } ] } } Notes: The properties defined in the schema above are the minimum amount of properties expected to be exposed for Private Endpoints in AVM Resource Modules. A module owner MAY chose to expose additional properties of the Private Endpoint resource However, module owners considering this SHOULD contact the AVM core team first to consult on how the property should be exposed to avoid future breaking changes to the schema that may be enforced upon them Module owners MAY chose to define a list of allowed value for the ‘service’ (a.k.a. groupIds) property However, they should do so with caution as should a new service appear for their resource module, a new release will need to be cut to add this new service to the allowed values Whereas not specifying allowed values will allow flexibility from day 0 without the need for any changes and releases to be made Customer Managed Keys Customer Managed Keys Variant 1: For CMK configurations not supporting auto-key-rotation Variant 2: For CMK configurations supporting auto-key-rotation ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { customerManagedKeyType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The customer managed key definition.') param customerManagedKey customerManagedKeyType? // ============= // // Resources // // ============= // resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) { name: last(split((customerManagedKey.?keyVaultResourceId!), '/')) scope: resourceGroup( split(customerManagedKey.?keyVaultResourceId!, '/')[2], split(customerManagedKey.?keyVaultResourceId!, '/')[4] ) resource cMKKey 'keys@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { name: customerManagedKey.?keyName! } } resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { name: last(split(customerManagedKey.?userAssignedIdentityResourceId!, '/')) scope: resourceGroup( split(customerManagedKey.?userAssignedIdentityResourceId!, '/')[2], split(customerManagedKey.?userAssignedIdentityResourceId!, '/')[4] ) } resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: '>exampleResource<' properties: { ... // other properties encryption: !empty(customerManagedKey) ? { keySource: 'Microsoft.KeyVault' keyVaultProperties: { keyVaultUri: cMKKeyVault.properties.vaultUri keyName: customerManagedKey!.keyName keyVersion: !empty(customerManagedKey.?keyVersion) ? customerManagedKey!.keyVersion : last(split(cMKKeyVault::cMKKey.properties.keyUriWithVersion, '/')) keyIdentifier: !empty(customerManagedKey.?keyVersion) ? '${cMKKeyVault::cMKKey.properties.keyUri}/${customerManagedKey!.keyVersion}' : cMKKeyVault::cMKKey.properties.keyUriWithVersion identityClientId: !empty(customerManagedKey.?userAssignedIdentityResourceId) ? cMKUserAssignedIdentity.properties.clientId : null identity: !empty(customerManagedKey.?userAssignedIdentityResourceId) ? { userAssignedIdentity: cMKUserAssignedIdentity.id } : null } } : null } } ​ Input Example with Values customerManagedKey: { keyVaultResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{keyVaultName}' keyName: '{keyName}' keyVersion: '{keyVersion}' userAssignedIdentityResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{uamiName}' } ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // import { customerManagedKeyWithAutoRotateType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Optional. The customer managed key definition.') param customerManagedKey customerManagedKeyWithAutoRotateType? // ============= // // Resources // // ============= // resource cMKKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) { name: last(split((customerManagedKey.?keyVaultResourceId!), '/')) scope: resourceGroup( split(customerManagedKey.?keyVaultResourceId!, '/')[2], split(customerManagedKey.?keyVaultResourceId!, '/')[4] ) resource cMKKey 'keys@2023-02-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { name: customerManagedKey.?keyName! } } resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { name: last(split(customerManagedKey.?userAssignedIdentityResourceId!, '/')) scope: resourceGroup( split(customerManagedKey.?userAssignedIdentityResourceId!, '/')[2], split(customerManagedKey.?userAssignedIdentityResourceId!, '/')[4] ) } resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: '>exampleResource<' properties: { ... // other properties encryption: !empty(customerManagedKey) ? { keySource: 'Microsoft.KeyVault' keyVaultProperties: { keyVaultUri: cMKKeyVault.properties.vaultUri keyName: customerManagedKey!.keyName keyVersion: !empty(customerManagedKey.?keyVersion) ? customerManagedKey!.keyVersion : (customerManagedKey.?autoRotationEnabled ?? true) ? null : last(split(cMKKeyVault::cMKKey.properties.keyUriWithVersion, '/')) keyIdentifier: !empty(customerManagedKey.?keyVersion) ? '${cMKKeyVault::cMKKey.properties.keyUri}/${customerManagedKey!.keyVersion}' : (customerManagedKey.?autoRotationEnabled ?? true) ? cMKKeyVault::cMKKey.properties.keyUri : cMKKeyVault::cMKKey.properties.keyUriWithVersion identityClientId: !empty(customerManagedKey.?userAssignedIdentityResourceId) ? cMKUserAssignedIdentity.properties.clientId : null identity: !empty(customerManagedKey.?userAssignedIdentityResourceId) ? { userAssignedIdentity: cMKUserAssignedIdentity.id } : null } } : null } } ​ Input Example with Values customerManagedKey: { keyVaultResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.KeyVault/vaults/{keyVaultName}' keyName: '{keyName}' autoRotationEnabled: {autoRotationEnabled} userAssignedIdentityResourceId: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{uamiName}' } Secrets export (DEPRECATED) Important Since version Bicep 0.35.1, it is possible to export secrets securely using the secure() annotation. As this approach is fairly simple compared with the below workaround it is highly recommended to use it instead. Example @secure() @description('The primary connection string of the service bus namespace.') output primaryConnectionString string = listkeys( '${serviceBusNamespace.id}/AuthorizationRules/RootManageSharedAccessKey', '2024-01-01' ).primaryConnectionString @secure() @description('The primary key of the service bus namespace.') output primaryKey string = listkeys( '${serviceBusNamespace.id}/AuthorizationRules/RootManageSharedAccessKey', '2024-01-01' ).primaryKey Secrets used inside a module can be exported to a Key Vault reference provided as per the below schema. This implementation provides a secure way around the current limitation of Bicep on providing a secure template output (that can be used for secrets). The user MUST provide the resource Id to a Key Vault. The principal used for the deployment MUST be allowed to set secrets in this Key Vault. provide a name for each secret they want to store (opt-in). The module will suggest which secrets are available via the implemented user-defined type. The module returns an output table where the key is the name of the secret the user provided, and the value contains both the secret’s resource Id and URI. Important The feature MUST be implemented as per the below schema. Diversions are only allowed in places marked as >text< to ensure a consistent user experience across modules. User Defined Type, Parameter & Resource Example ​ User Defined Type, Parameter & Resource Example // ============== // // Parameters // // ============== // @description('Optional. Key vault reference and secret settings for the module\'s secrets export.') param secretsExportConfiguration secretsExportConfigurationType? // ============= // // Resources // // ============= // module secretsExport 'modules/keyVaultExport.bicep' = if (secretsExportConfiguration != null) { name: '${uniqueString(deployment().name, location)}-secrets-kv' scope: resourceGroup( split(secretsExportConfiguration.?keyVaultResourceId, '/')[2], split(secretsExportConfiguration.?keyVaultResourceId, '/')[4] ) params: { keyVaultName: last(split(secretsExportConfiguration.?keyVaultResourceId, '/')) secretsToSet: union( [], contains(secretsExportConfiguration!, '>secretToExport1<Name') ? [ { name: secretsExportConfiguration!.?>secretToExport1<Name value: >secretReference1< // e.g., >singularMainResourceType<.listKeys().primaryMasterKey } ] : [], contains(secretsExportConfiguration!, '>secretToExport2<Name') ? [ { name: secretsExportConfiguration!.?>secretToExport2<Name value:>secretReference2< // e.g., >singularMainResourceType<.listKeys().secondaryMasterKey } ] : [] // (...) ) } } // =========== // // Outputs // // =========== // import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret\'s name.') output exportedSecrets secretsOutputType = (secretsExportConfiguration != null) ? toObject(secretsExport.outputs.secretsSet, secret => last(split(secret.secretResourceId, '/')), secret => secret) : {} // =============== // // Definitions // // =============== // @export() type secretsExportConfigurationType = { @description('Required. The resource ID of the key vault where to store the secrets of this module.') keyVaultResourceId: string @description('Optional. The >secretToExport1< secret name to create.') >secretToExport1<Name: string? @description('Optional. The >secretToExport2< secret name to create.') >secretToExport2<Name: string? // (...) } Input Example with Values ​ Input Example with Values // ============== // // Parameters // // ============== // @description('Optional. Key vault reference and secret settings for the module\'s secrets export.') param secretsExportConfiguration secretsExportConfigurationType? // ============= // // Resources // // ============= // module secretsExport 'modules/keyVaultExport.bicep' = if (secretsExportConfiguration != null) { name: '${uniqueString(deployment().name, location)}-secrets-kv' scope: resourceGroup( split(secretsExportConfiguration.?keyVaultResourceId, '/')[2], split(secretsExportConfiguration.?keyVaultResourceId, '/')[4] ) params: { keyVaultName: last(split(secretsExportConfiguration.?keyVaultResourceId, '/')) secretsToSet: union( [], contains(secretsExportConfiguration!, '>secretToExport1<Name') ? [ { name: secretsExportConfiguration!.?>secretToExport1<Name value: >secretReference1< // e.g., >singularMainResourceType<.listKeys().primaryMasterKey } ] : [], contains(secretsExportConfiguration!, '>secretToExport2<Name') ? [ { name: secretsExportConfiguration!.?>secretToExport2<Name value:>secretReference2< // e.g., >singularMainResourceType<.listKeys().secondaryMasterKey } ] : [] // (...) ) } } // =========== // // Outputs // // =========== // import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret\'s name.') output exportedSecrets secretsOutputType = (secretsExportConfiguration != null) ? toObject(secretsExport.outputs.secretsSet, secret => last(split(secret.secretResourceId, '/')), secret => secret) : {} // =============== // // Definitions // // =============== // @export() type secretsExportConfigurationType = { @description('Required. The resource ID of the key vault where to store the secrets of this module.') keyVaultResourceId: string @description('Optional. The >secretToExport1< secret name to create.') >secretToExport1<Name: string? @description('Optional. The >secretToExport2< secret name to create.') >secretToExport2<Name: string? // (...) } [modules/keyVaultExport.bicep] file ​ [modules/keyVaultExport.bicep] file // ============== // // Parameters // // ============== // @description('Required. The name of the Key Vault to set the secrets in.') param keyVaultName string import { secretToSetType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('Required. The secrets to set in the Key Vault.') param secretsToSet secretToSetType[] // ============= // // Resources // // ============= // resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { name: keyVaultName } resource secrets 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = [ for secret in secretsToSet: { name: secret.name parent: keyVault properties: { value: secret.value } } ] // =========== // // Outputs // // =========== // import { secretSetOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('The references to the secrets exported to the provided Key Vault.') output secretsSet secretSetOutputType[] = [ #disable-next-line outputs-should-not-contain-secrets // Only returning the references, not a secret value for index in range(0, length(secretsToSet ?? [])): { secretResourceId: secrets[index].id secretUri: secrets[index].properties.secretUri secretUriWithVersion: secrets[index].properties.secretUriWithVersion } ] Output Usage Example When using a module that implements the above interface, you can access its outputs for example in the following ways: ​ Output Usage Example // ============== // // Parameters // // ============== // @description('Optional. Key vault reference and secret settings for the module\'s secrets export.') param secretsExportConfiguration secretsExportConfigurationType? // ============= // // Resources // // ============= // module secretsExport 'modules/keyVaultExport.bicep' = if (secretsExportConfiguration != null) { name: '${uniqueString(deployment().name, location)}-secrets-kv' scope: resourceGroup( split(secretsExportConfiguration.?keyVaultResourceId, '/')[2], split(secretsExportConfiguration.?keyVaultResourceId, '/')[4] ) params: { keyVaultName: last(split(secretsExportConfiguration.?keyVaultResourceId, '/')) secretsToSet: union( [], contains(secretsExportConfiguration!, '>secretToExport1<Name') ? [ { name: secretsExportConfiguration!.?>secretToExport1<Name value: >secretReference1< // e.g., >singularMainResourceType<.listKeys().primaryMasterKey } ] : [], contains(secretsExportConfiguration!, '>secretToExport2<Name') ? [ { name: secretsExportConfiguration!.?>secretToExport2<Name value:>secretReference2< // e.g., >singularMainResourceType<.listKeys().secondaryMasterKey } ] : [] // (...) ) } } // =========== // // Outputs // // =========== // import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:>version<' @description('A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret\'s name.') output exportedSecrets secretsOutputType = (secretsExportConfiguration != null) ? toObject(secretsExport.outputs.secretsSet, secret => last(split(secret.secretResourceId, '/')), secret => secret) : {} // =============== // // Definitions // // =============== // @export() type secretsExportConfigurationType = { @description('Required. The resource ID of the key vault where to store the secrets of this module.') keyVaultResourceId: string @description('Optional. The >secretToExport1< secret name to create.') >secretToExport1<Name: string? @description('Optional. The >secretToExport2< secret name to create.') >secretToExport2<Name: string? // (...) } Which returns a JSON-formatted output like: ​ Output Usage Example { "exportedSecrets": { "Type": "Object", "Value": { ">secretToExportName1<": { "secretResourceId": "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName1<", "secretUri": "https://<vaultName>.vault.azure.net/secrets/>secretToExportName1<" }, ">secretToExportName2<": { "secretResourceId": "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName2<", "secretUri": "https://<vaultName>.vault.azure.net/secrets/>secretToExportName2<" } } }, "specificSecret": { "Type": "String", "Value": "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName1<" }, "exportedSecretResourceIds": { "Type": "Array", "Value": [ "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName1<", "/subscriptions/<subId>/resourceGroups/<rgName>providers/Microsoft.KeyVault/vaults/<vaultName>/secrets/>secretToExportName2<" ] } } Azure Monitor Alerts Note This interface is a SHOULD instead of a MUST and therefore the AVM core team have not mandated a interface schema to use. Zonal & zone-redundant resources Many Azure resources can be deployed into specific availability zones. Depending on whether a resource is ‘zonal’ (i.e., deploys a single instance into a single zone) or ‘zone-redundant’ (i.e., spreads multiple of its instances across the configured zones), implementing a different interface is required. Simply put, the zone of a zonal resource must be a required parameter (but give the user the option to ‘opt-out’), while zone-redundant resources must span all available zones by default, but still give the user the option to ‘opt-out’. Please note that the support for Availability Zones may differ from region to region. ​ Variant 1: Zone-redundant resource (e.g., VirtualMachineScaleSet) Variant 2: Zonal resource (e.g., Compute Disk) ​ Parameter & Resource Example // ============== // // Parameters // // ============== // @description('Required. If set to 1, 2 or 3, the availability zone is hardcoded to that value. If set to -1, no zone is defined. Note that the availability zone numbers here are the logical availability zone in your Azure subscription. Different subscriptions might have a different mapping of the physical zone and logical zone. To understand more, please refer to [Physical and logical availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview?tabs=azure-cli#physical-and-logical-availability-zones).') @allowed([ -1 1 2 3 ]) param availabilityZone int // ============= // // Resources // // ============= // resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: '>exampleResource<' properties: { ... // other properties zones: availabilityZone != -1 ? array(string(availabilityZone)) : null // If expecting an array // Or availabilityZone: availabilityZone != -1 ? string(availabilityZone) : null // If expecting a single value } } ​ Input Example with Values availabilityZone: -1 // Deploy into no zone availabilityZone: 1 // Deploy into zone 1 ​ Parameter & Resource Example // ============== // // Parameters // // ============== // @description('Optional. The list of Availability zones to use for the zone-redundant resources.') @allowed([ 1 2 3 ]) param availabilityZones int[] = [1, 2, 3] // ============= // // Resources // // ============= // resource >singularMainResourceType< '>providerNamespace</>resourceType<@>apiVersion<' = { name: '>exampleResource<' properties: { ... // other properties zones: map(availabilityZones, zone => '${zone}') } } ​ Input Example with Values availabilityZones: [] // Deploy into no zone availabilityZones: [1, 2] // Deploy into zone 1 & 2 --- Source: https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/content/specs-defs/specs/bicep/interfaces.md Last Modified: 0001-01-01