From 66a71a40e0ac47efb1fe89d363412911ffcc886a Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Mon, 9 Sep 2024 00:26:34 +0200 Subject: [PATCH 01/46] fix: Kusto Cluster - Fixed identity output (#3220) ## Description The identity property does not always exist which is why the deployment fails for e.g. the defaults test. I assume the primary reason to add it was to return the System-Assigned-Identity information which has its own separate output and the identity ouptut can hence be removed. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.kusto.cluster](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.kusto.cluster.yml/badge.svg?branch=users%2Falsehr%2FkustoMsiFix&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.kusto.cluster.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --- avm/res/kusto/cluster/README.md | 1 - avm/res/kusto/cluster/main.bicep | 3 --- avm/res/kusto/cluster/main.json | 9 +-------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/avm/res/kusto/cluster/README.md b/avm/res/kusto/cluster/README.md index dc7611acf7a..8d94f1f46fb 100644 --- a/avm/res/kusto/cluster/README.md +++ b/avm/res/kusto/cluster/README.md @@ -1699,7 +1699,6 @@ The resource ID of the subnet to which to deploy the Kusto Cluster. | Output | Type | Description | | :-- | :-- | :-- | -| `identity` | object | The identity of the cluster. | | `location` | string | The location the resource was deployed into. | | `name` | string | The name of the kusto cluster. | | `privateEndpoints` | array | The private endpoints of the kusto cluster. | diff --git a/avm/res/kusto/cluster/main.bicep b/avm/res/kusto/cluster/main.bicep index b7693cf1044..e1372dca577 100644 --- a/avm/res/kusto/cluster/main.bicep +++ b/avm/res/kusto/cluster/main.bicep @@ -375,9 +375,6 @@ output name string = kustoCluster.name @description('The location the resource was deployed into.') output location string = kustoCluster.location -@description('The identity of the cluster.') -output identity object = kustoCluster.identity - @description('The private endpoints of the kusto cluster.') output privateEndpoints array = [ for (pe, i) in (!empty(privateEndpoints) ? array(privateEndpoints) : []): { diff --git a/avm/res/kusto/cluster/main.json b/avm/res/kusto/cluster/main.json index 99f4dd208cd..2c06ed215d1 100644 --- a/avm/res/kusto/cluster/main.json +++ b/avm/res/kusto/cluster/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "9987903523872780142" + "templateHash": "17272299626990757362" }, "name": "Kusto Cluster", "description": "This module deploys a Kusto Cluster.", @@ -1977,13 +1977,6 @@ }, "value": "[reference('kustoCluster', '2023-08-15', 'full').location]" }, - "identity": { - "type": "object", - "metadata": { - "description": "The identity of the cluster." - }, - "value": "[reference('kustoCluster', '2023-08-15', 'full').identity]" - }, "privateEndpoints": { "type": "array", "metadata": { From ff08d9847602d8f704018913ef5274ec5fa9cd65 Mon Sep 17 00:00:00 2001 From: Kris Baranek <20225789+krbar@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:01:37 +0200 Subject: [PATCH 02/46] feat: Add identity support for `avm/res/insigths/data-collection-rule` module (#3230) ## Description - add identity support for the `avm/res/insigths/data-collection-rule` module - update api version in test cases ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.insights.data-collection-rule](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml/badge.svg?branch=user%2Fkrbar%2FdcrIdentity)](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../insights/data-collection-rule/README.md | 52 +++++++++++++++++++ .../insights/data-collection-rule/main.bicep | 32 ++++++++++++ .../insights/data-collection-rule/main.json | 42 ++++++++++++++- .../tests/e2e/agent-settings/main.test.bicep | 2 +- .../tests/e2e/customadv/dependencies.bicep | 4 +- .../tests/e2e/customadv/main.test.bicep | 5 +- .../tests/e2e/custombasic/dependencies.bicep | 4 +- .../tests/e2e/custombasic/main.test.bicep | 2 +- .../tests/e2e/customiis/dependencies.bicep | 4 +- .../tests/e2e/customiis/main.test.bicep | 2 +- .../tests/e2e/defaults/main.test.bicep | 2 +- .../tests/e2e/linux/dependencies.bicep | 2 +- .../tests/e2e/linux/main.test.bicep | 2 +- .../tests/e2e/max/dependencies.bicep | 9 ++-- .../tests/e2e/max/main.test.bicep | 8 ++- .../tests/e2e/waf-aligned/dependencies.bicep | 2 +- .../tests/e2e/waf-aligned/main.test.bicep | 2 +- .../tests/e2e/windows/dependencies.bicep | 2 +- .../tests/e2e/windows/main.test.bicep | 2 +- .../data-collection-rule/version.json | 2 +- 20 files changed, 159 insertions(+), 23 deletions(-) diff --git a/avm/res/insights/data-collection-rule/README.md b/avm/res/insights/data-collection-rule/README.md index 07e3b9bc40d..6134656d6d7 100644 --- a/avm/res/insights/data-collection-rule/README.md +++ b/avm/res/insights/data-collection-rule/README.md @@ -202,6 +202,9 @@ module dataCollectionRule 'br/public:avm/res/insights/data-collection-rule:' + managedIdentities: { + systemAssigned: true + } tags: { 'hidden-title': 'This is visible in the resource name' kind: 'Windows' @@ -308,6 +311,11 @@ module dataCollectionRule 'br/public:avm/res/insights/data-collection-rule:" }, + "managedIdentities": { + "value": { + "systemAssigned": true + } + }, "tags": { "value": { "hidden-title": "This is visible in the resource name", @@ -1216,6 +1224,12 @@ module dataCollectionRule 'br/public:avm/res/insights/data-collection-rule:' + ] + } roleAssignments: [ { name: '89a4d6fa-defb-4099-9196-173d94b91d67' @@ -1331,6 +1345,14 @@ module dataCollectionRule 'br/public:avm/res/insights/data-collection-rule:" + ] + } + }, "roleAssignments": { "value": [ { @@ -1926,6 +1948,7 @@ module dataCollectionRule 'br/public:avm/res/insights/data-collection-rule: { '${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' : 'None') + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null + } + : null + var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') @@ -89,6 +106,7 @@ resource dataCollectionRule 'Microsoft.Insights/dataCollectionRules@2023-03-11' location: location name: name tags: tags + identity: identity properties: dataCollectionRulePropertiesUnion } @@ -97,6 +115,7 @@ resource dataCollectionRuleAll 'Microsoft.Insights/dataCollectionRules@2023-03-1 location: location name: name tags: tags + identity: identity properties: dataCollectionRulePropertiesUnion } @@ -131,12 +150,25 @@ output location string = dataCollectionRuleProperties.kind == 'All' ? dataCollectionRuleAll.location : dataCollectionRule.location +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string = dataCollectionRuleProperties.kind == 'All' + ? dataCollectionRuleAll.?identity.?principalId ?? '' + : dataCollectionRule.?identity.?principalId ?? '' + // =============== // // Definitions // // =============== // import { roleAssignmentType, lockType } from 'modules/nested_conditionalScope.bicep' +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption.') + userAssignedResourceIds: string[]? +}? + @discriminator('kind') type dataCollectionRulePropertiesType = | linuxDcrPropertiesType diff --git a/avm/res/insights/data-collection-rule/main.json b/avm/res/insights/data-collection-rule/main.json index ffb2fd24de5..c9b904f82fe 100644 --- a/avm/res/insights/data-collection-rule/main.json +++ b/avm/res/insights/data-collection-rule/main.json @@ -6,13 +6,36 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "9024628264172390673" + "templateHash": "6159067500010827927" }, "name": "Data Collection Rules", "description": "This module deploys a Data Collection Rule.", "owner": "Azure/module-maintainers" }, "definitions": { + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + }, + "nullable": true + }, "dataCollectionRulePropertiesType": { "type": "object", "discriminator": { @@ -396,6 +419,12 @@ "description": "Optional. The lock settings of the service." } }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource. Only one type of, and up to one managed identity is supported." + } + }, "roleAssignments": { "$ref": "#/definitions/roleAssignmentType", "metadata": { @@ -411,6 +440,8 @@ } }, "variables": { + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", @@ -449,6 +480,7 @@ "kind": "[parameters('dataCollectionRuleProperties').kind]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", "properties": "[variables('dataCollectionRulePropertiesUnion')]" }, "dataCollectionRuleAll": { @@ -458,6 +490,7 @@ "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", "properties": "[variables('dataCollectionRulePropertiesUnion')]" }, "dataCollectionRule_conditionalScopeResources": { @@ -715,6 +748,13 @@ "description": "The location the resource was deployed into." }, "value": "[if(equals(parameters('dataCollectionRuleProperties').kind, 'All'), reference('dataCollectionRuleAll', '2023-03-11', 'full').location, reference('dataCollectionRule', '2023-03-11', 'full').location)]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[if(equals(parameters('dataCollectionRuleProperties').kind, 'All'), coalesce(tryGet(tryGet(reference('dataCollectionRuleAll', '2023-03-11', 'full'), 'identity'), 'principalId'), ''), coalesce(tryGet(tryGet(reference('dataCollectionRule', '2023-03-11', 'full'), 'identity'), 'principalId'), ''))]" } } } \ No newline at end of file diff --git a/avm/res/insights/data-collection-rule/tests/e2e/agent-settings/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/agent-settings/main.test.bicep index b618848eb8c..28364a28f08 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/agent-settings/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/agent-settings/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/customadv/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/customadv/dependencies.bicep index 35ea4ba1206..83a92b025ff 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/customadv/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/customadv/dependencies.bicep @@ -7,7 +7,7 @@ param dataCollectionEndpointName string @description('Required. The name of the log analytics workspace to create.') param logAnalyticsWorkspaceName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location @@ -47,7 +47,7 @@ resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10 } } -resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021-04-01' = { +resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2023-03-11' = { kind: 'Windows' location: location name: dataCollectionEndpointName diff --git a/avm/res/insights/data-collection-rule/tests/e2e/customadv/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/customadv/main.test.bicep index 97004509b59..23b27402bfd 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/customadv/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/customadv/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } @@ -127,6 +127,9 @@ module testDeployment '../../../main.bicep' = [ } } } + managedIdentities: { + systemAssigned: true + } tags: { 'hidden-title': 'This is visible in the resource name' resourceType: 'Data Collection Rules' diff --git a/avm/res/insights/data-collection-rule/tests/e2e/custombasic/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/custombasic/dependencies.bicep index 0412e611644..b474947ee3a 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/custombasic/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/custombasic/dependencies.bicep @@ -7,7 +7,7 @@ param dataCollectionEndpointName string @description('Required. The name of the log analytics workspace to create.') param logAnalyticsWorkspaceName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location @@ -31,7 +31,7 @@ resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10 } } -resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021-04-01' = { +resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2023-03-11' = { kind: 'Windows' location: location name: dataCollectionEndpointName diff --git a/avm/res/insights/data-collection-rule/tests/e2e/custombasic/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/custombasic/main.test.bicep index cdd52f593ec..7c660c4be1f 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/custombasic/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/custombasic/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/customiis/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/customiis/dependencies.bicep index b12b662cc76..a4a121e5677 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/customiis/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/customiis/dependencies.bicep @@ -7,12 +7,12 @@ param dataCollectionEndpointName string @description('Required. The name of the log analytics workspace to create.') param logAnalyticsWorkspaceName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location } -resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021-04-01' = { +resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2023-03-11' = { kind: 'Windows' location: location name: dataCollectionEndpointName diff --git a/avm/res/insights/data-collection-rule/tests/e2e/customiis/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/customiis/main.test.bicep index 017061171a8..21221f6e68d 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/customiis/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/customiis/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/defaults/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/defaults/main.test.bicep index 6c6fd466dad..50102a69277 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/defaults/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/defaults/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/linux/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/linux/dependencies.bicep index c20b584c10e..62da14da45a 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/linux/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/linux/dependencies.bicep @@ -4,7 +4,7 @@ param location string = resourceGroup().location @description('Required. The name of the log analytics workspace to create.') param logAnalyticsWorkspaceName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/linux/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/linux/main.test.bicep index 0371f755418..4a7759ac31e 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/linux/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/linux/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/max/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/max/dependencies.bicep index e7a3cd006be..e2e3291fddc 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/max/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/max/dependencies.bicep @@ -10,7 +10,7 @@ param logAnalyticsWorkspaceName string @description('Required. The name of the managed identity to create.') param managedIdentityName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location @@ -34,12 +34,12 @@ resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10 } } -resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: managedIdentityName location: location } -resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021-04-01' = { +resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2023-03-11' = { kind: 'Windows' location: location name: dataCollectionEndpointName @@ -59,5 +59,8 @@ output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name @description('The principal ID of the created managed identity.') output managedIdentityPrincipalId string = managedIdentity.properties.principalId +@description('The resource ID of the created Managed Identity.') +output managedIdentityResourceId string = managedIdentity.id + @description('The resource ID of the created Data Collection Endpoint.') output dataCollectionEndpointResourceId string = dataCollectionEndpoint.id diff --git a/avm/res/insights/data-collection-rule/tests/e2e/max/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/max/main.test.bicep index 27ac14b5c46..41c799217ab 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/max/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/max/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } @@ -116,6 +116,12 @@ module testDeployment '../../../main.bicep' = [ kind: 'CanNotDelete' name: 'myCustomLockName' } + managedIdentities: { + systemAssigned: false + userAssignedResourceIds: [ + nestedDependencies.outputs.managedIdentityResourceId + ] + } roleAssignments: [ { name: '89a4d6fa-defb-4099-9196-173d94b91d67' diff --git a/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/dependencies.bicep index c20b584c10e..62da14da45a 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/dependencies.bicep @@ -4,7 +4,7 @@ param location string = resourceGroup().location @description('Required. The name of the log analytics workspace to create.') param logAnalyticsWorkspaceName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/main.test.bicep index 797344b7e12..d4cec148c1e 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/waf-aligned/main.test.bicep @@ -26,7 +26,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/windows/dependencies.bicep b/avm/res/insights/data-collection-rule/tests/e2e/windows/dependencies.bicep index c20b584c10e..62da14da45a 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/windows/dependencies.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/windows/dependencies.bicep @@ -4,7 +4,7 @@ param location string = resourceGroup().location @description('Required. The name of the log analytics workspace to create.') param logAnalyticsWorkspaceName string -resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { name: logAnalyticsWorkspaceName location: location } diff --git a/avm/res/insights/data-collection-rule/tests/e2e/windows/main.test.bicep b/avm/res/insights/data-collection-rule/tests/e2e/windows/main.test.bicep index 7c58e5ede82..0655a2c2fdf 100644 --- a/avm/res/insights/data-collection-rule/tests/e2e/windows/main.test.bicep +++ b/avm/res/insights/data-collection-rule/tests/e2e/windows/main.test.bicep @@ -25,7 +25,7 @@ param namePrefix string = '#_namePrefix_#' // General resources // ================= -resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource resourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: resourceGroupName location: resourceLocation } diff --git a/avm/res/insights/data-collection-rule/version.json b/avm/res/insights/data-collection-rule/version.json index c177b1bb58b..3f863a2bec9 100644 --- a/avm/res/insights/data-collection-rule/version.json +++ b/avm/res/insights/data-collection-rule/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] From 7990280450653fa60278cb6e9a130c8053d199fa Mon Sep 17 00:00:00 2001 From: Ahmad Abdalla <28486158+ahmadabdalla@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:04:13 +1000 Subject: [PATCH 03/46] fix: Update `avm/res/dev-test-lab/lab` module virtual network parameters and increased test coverage. (#3180) ## Description Closes #3126 - Update optional parameters for the virtual network child module. - Increase test coverage for the max test cases to cater for optional scenarios. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.dev-test-lab.lab](https://github.com/ahmadabdalla/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml/badge.svg?branch=users%2Fahmad%2F3126_DTL)](https://github.com/ahmadabdalla/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/dev-test-lab/lab/README.md | 30 +- avm/res/dev-test-lab/lab/main.bicep | 4 +- avm/res/dev-test-lab/lab/main.json | 426 +++++++++--------- .../lab/tests/e2e/max/dependencies.bicep | 24 +- .../lab/tests/e2e/max/main.test.bicep | 25 +- .../dev-test-lab/lab/virtualnetwork/README.md | 160 ++++++- .../lab/virtualnetwork/main.bicep | 12 +- .../dev-test-lab/lab/virtualnetwork/main.json | 213 ++++----- 8 files changed, 558 insertions(+), 336 deletions(-) diff --git a/avm/res/dev-test-lab/lab/README.md b/avm/res/dev-test-lab/lab/README.md index da4135a4ffb..22a277da2ac 100644 --- a/avm/res/dev-test-lab/lab/README.md +++ b/avm/res/dev-test-lab/lab/README.md @@ -149,6 +149,8 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { target: 450 thresholdValue100DisplayOnChart: 'Enabled' thresholdValue100SendNotificationWhenExceeded: 'Enabled' + thresholdValue125DisplayOnChart: 'Disabled' + thresholdValue75DisplayOnChart: 'Enabled' } disableAutoUpgradeCseMinorVersion: true encryptionDiskEncryptionSetId: '' @@ -320,6 +322,11 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { labSubnetName: '' resourceId: '' } + { + allowPublicIp: 'Deny' + labSubnetName: '' + resourceId: '' + } ] description: 'lab virtual network description' externalProviderResourceId: '' @@ -343,6 +350,12 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { useInVmCreationPermission: 'Allow' usePublicIpAddressPermission: 'Allow' } + { + labSubnetName: '' + resourceId: '' + useInVmCreationPermission: 'Deny' + usePublicIpAddressPermission: 'Deny' + } ] } ] @@ -425,7 +438,9 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { "status": "Enabled", "target": 450, "thresholdValue100DisplayOnChart": "Enabled", - "thresholdValue100SendNotificationWhenExceeded": "Enabled" + "thresholdValue100SendNotificationWhenExceeded": "Enabled", + "thresholdValue125DisplayOnChart": "Disabled", + "thresholdValue75DisplayOnChart": "Enabled" } }, "disableAutoUpgradeCseMinorVersion": { @@ -634,6 +649,11 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { "allowPublicIp": "Allow", "labSubnetName": "", "resourceId": "" + }, + { + "allowPublicIp": "Deny", + "labSubnetName": "", + "resourceId": "" } ], "description": "lab virtual network description", @@ -657,6 +677,12 @@ module lab 'br/public:avm/res/dev-test-lab/lab:' = { }, "useInVmCreationPermission": "Allow", "usePublicIpAddressPermission": "Allow" + }, + { + "labSubnetName": "", + "resourceId": "", + "useInVmCreationPermission": "Deny", + "usePublicIpAddressPermission": "Deny" } ] } @@ -2026,7 +2052,7 @@ The resource ID of the subnet. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). -- Required: Yes +- Required: No - Type: object **Required parameters** diff --git a/avm/res/dev-test-lab/lab/main.bicep b/avm/res/dev-test-lab/lab/main.bicep index 3d0fe3a7ce6..186bb5773ca 100644 --- a/avm/res/dev-test-lab/lab/main.bicep +++ b/avm/res/dev-test-lab/lab/main.bicep @@ -461,10 +461,10 @@ type virtualNetworkType = { description: string? @description('Optional. The allowed subnets of the virtual network.') - allowedSubnets: allowedSubnetType[]? + allowedSubnets: allowedSubnetType? @description('Optional. The subnet overrides of the virtual network.') - subnetOverrides: subnetOverrideType[]? + subnetOverrides: subnetOverrideType? }[]? type costsType = { diff --git a/avm/res/dev-test-lab/lab/main.json b/avm/res/dev-test-lab/lab/main.json index 711f287f085..f1245a9d6e8 100644 --- a/avm/res/dev-test-lab/lab/main.json +++ b/avm/res/dev-test-lab/lab/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "9164793528274272981" + "templateHash": "15435393299266858028" }, "name": "DevTest Labs", "description": "This module deploys a DevTest Lab.", @@ -244,20 +244,14 @@ } }, "allowedSubnets": { - "type": "array", - "items": { - "$ref": "#/definitions/allowedSubnetType" - }, + "$ref": "#/definitions/allowedSubnetType", "nullable": true, "metadata": { "description": "Optional. The allowed subnets of the virtual network." } }, "subnetOverrides": { - "type": "array", - "items": { - "$ref": "#/definitions/subnetOverrideType" - }, + "$ref": "#/definitions/subnetOverrideType", "nullable": true, "metadata": { "description": "Optional. The subnet overrides of the virtual network." @@ -666,33 +660,37 @@ "nullable": true }, "allowedSubnetType": { - "type": "object", - "properties": { - "allowPublicIp": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the allowed subnet." - } - }, - "labSubnetName": { - "type": "string", - "metadata": { - "description": "Required. The name of the subnet as seen in the lab." + "type": "array", + "items": { + "type": "object", + "properties": { + "allowPublicIp": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the allowed subnet." + } + }, + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name of the subnet as seen in the lab." + } } } }, + "nullable": true, "metadata": { "__bicep_imported_from!": { "sourceTemplate": "virtualnetwork/main.bicep" @@ -784,87 +782,92 @@ } }, "subnetOverrideType": { - "type": "object", - "properties": { - "labSubnetName": { - "type": "string", - "metadata": { - "description": "Required. The name given to the subnet within the lab." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the subnet." - } - }, - "sharedPublicIpAddressConfiguration": { - "type": "object", - "properties": { - "allowedPorts": { - "type": "array", - "items": { - "type": "object", - "properties": { - "backendPort": { - "type": "int", - "metadata": { - "description": "Required. Backend port of the target virtual machine." - } - }, - "transportProtocol": { - "type": "string", - "allowedValues": [ - "Tcp", - "Udp" - ], - "metadata": { - "description": "Required. Protocol type of the port." + "type": "array", + "items": { + "type": "object", + "properties": { + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name given to the subnet within the lab." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "sharedPublicIpAddressConfiguration": { + "type": "object", + "properties": { + "allowedPorts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "backendPort": { + "type": "int", + "metadata": { + "description": "Required. Backend port of the target virtual machine." + } + }, + "transportProtocol": { + "type": "string", + "allowedValues": [ + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Protocol type of the port." + } } } + }, + "metadata": { + "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." } - }, - "metadata": { - "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." } + }, + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." } }, - "metadata": { - "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." - } - }, - "useInVmCreationPermission": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." - } - }, - "usePublicIpAddressPermission": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." - } - }, - "virtualNetworkPoolName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The virtual network pool associated with this subnet." + "useInVmCreationPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." + } + }, + "usePublicIpAddressPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." + } + }, + "virtualNetworkPoolName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The virtual network pool associated with this subnet." + } } } }, + "nullable": true, "metadata": { "__bicep_imported_from!": { "sourceTemplate": "virtualnetwork/main.bicep" @@ -1272,127 +1275,136 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "18309816581107210302" + "templateHash": "12122718661184299591" }, "name": "DevTest Lab Virtual Networks", - "description": "This module deploys a DevTest Lab Virtual Network.\r\n\r\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", + "description": "This module deploys a DevTest Lab Virtual Network.\n\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", "owner": "Azure/module-maintainers" }, "definitions": { "allowedSubnetType": { - "type": "object", - "properties": { - "allowPublicIp": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the allowed subnet." - } - }, - "labSubnetName": { - "type": "string", - "metadata": { - "description": "Required. The name of the subnet as seen in the lab." + "type": "array", + "items": { + "type": "object", + "properties": { + "allowPublicIp": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the allowed subnet." + } + }, + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name of the subnet as seen in the lab." + } } } }, + "nullable": true, "metadata": { "__bicep_export!": true } }, "subnetOverrideType": { - "type": "object", - "properties": { - "labSubnetName": { - "type": "string", - "metadata": { - "description": "Required. The name given to the subnet within the lab." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the subnet." - } - }, - "sharedPublicIpAddressConfiguration": { - "type": "object", - "properties": { - "allowedPorts": { - "type": "array", - "items": { - "type": "object", - "properties": { - "backendPort": { - "type": "int", - "metadata": { - "description": "Required. Backend port of the target virtual machine." - } - }, - "transportProtocol": { - "type": "string", - "allowedValues": [ - "Tcp", - "Udp" - ], - "metadata": { - "description": "Required. Protocol type of the port." + "type": "array", + "items": { + "type": "object", + "properties": { + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name given to the subnet within the lab." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "sharedPublicIpAddressConfiguration": { + "type": "object", + "properties": { + "allowedPorts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "backendPort": { + "type": "int", + "metadata": { + "description": "Required. Backend port of the target virtual machine." + } + }, + "transportProtocol": { + "type": "string", + "allowedValues": [ + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Protocol type of the port." + } } } + }, + "metadata": { + "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." } - }, - "metadata": { - "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." } + }, + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." } }, - "metadata": { - "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." - } - }, - "useInVmCreationPermission": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." - } - }, - "usePublicIpAddressPermission": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." - } - }, - "virtualNetworkPoolName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The virtual network pool associated with this subnet." + "useInVmCreationPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." + } + }, + "usePublicIpAddressPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." + } + }, + "virtualNetworkPoolName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The virtual network pool associated with this subnet." + } } } }, + "nullable": true, "metadata": { "__bicep_export!": true } @@ -1426,21 +1438,19 @@ }, "description": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The description of the virtual network." } }, "allowedSubnets": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/allowedSubnetType", "metadata": { "description": "Optional. The allowed subnets of the virtual network." } }, "subnetOverrides": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/subnetOverrideType", "metadata": { "description": "Optional. The subnet overrides of the virtual network." } diff --git a/avm/res/dev-test-lab/lab/tests/e2e/max/dependencies.bicep b/avm/res/dev-test-lab/lab/tests/e2e/max/dependencies.bicep index 5e1c0b1d255..4c97fabff53 100644 --- a/avm/res/dev-test-lab/lab/tests/e2e/max/dependencies.bicep +++ b/avm/res/dev-test-lab/lab/tests/e2e/max/dependencies.bicep @@ -103,9 +103,15 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { } subnets: [ { - name: 'defaultSubnet' + name: 'subnet01' properties: { - addressPrefix: cidrSubnet(addressPrefix, 16, 0) + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + } + } + { + name: 'subnet02' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 1) } } ] @@ -118,11 +124,17 @@ output virtualNetworkName string = virtualNetwork.name @description('The resource ID of the created Virtual Network.') output virtualNetworkResourceId string = virtualNetwork.id -@description('The name of the created Virtual Network Subnet.') -output subnetName string = virtualNetwork.properties.subnets[0].name +@description('The name of the created Virtual Network Subnet 1.') +output subnet1Name string = virtualNetwork.properties.subnets[0].name + +@description('The resource ID of the created Virtual Network Subnet 1.') +output subnet1ResourceId string = virtualNetwork.properties.subnets[0].id + +@description('The name of the created Virtual Network Subnet 2.') +output subnet2Name string = virtualNetwork.properties.subnets[1].name -@description('The resource ID of the created Virtual Network Subnet.') -output subnetResourceId string = virtualNetwork.properties.subnets[0].id +@description('The resource ID of the created Virtual Network Subnet 2.') +output subnet2ResourceId string = virtualNetwork.properties.subnets[1].id @description('The principal ID of the created Managed Identity.') output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep b/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep index 65d06f3e057..fc069f92483 100644 --- a/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep +++ b/avm/res/dev-test-lab/lab/tests/e2e/max/main.test.bicep @@ -129,15 +129,20 @@ module testDeployment '../../../main.bicep' = [ description: 'lab virtual network description' allowedSubnets: [ { - labSubnetName: nestedDependencies.outputs.subnetName - resourceId: nestedDependencies.outputs.subnetResourceId + labSubnetName: nestedDependencies.outputs.subnet1Name + resourceId: nestedDependencies.outputs.subnet1ResourceId allowPublicIp: 'Allow' } + { + labSubnetName: nestedDependencies.outputs.subnet2Name + resourceId: nestedDependencies.outputs.subnet2ResourceId + allowPublicIp: 'Deny' + } ] subnetOverrides: [ { - labSubnetName: nestedDependencies.outputs.subnetName - resourceId: nestedDependencies.outputs.subnetResourceId + labSubnetName: nestedDependencies.outputs.subnet1Name + resourceId: nestedDependencies.outputs.subnet1ResourceId useInVmCreationPermission: 'Allow' usePublicIpAddressPermission: 'Allow' sharedPublicIpAddressConfiguration: { @@ -153,14 +158,20 @@ module testDeployment '../../../main.bicep' = [ ] } } + { + labSubnetName: nestedDependencies.outputs.subnet2Name + resourceId: nestedDependencies.outputs.subnet2ResourceId + useInVmCreationPermission: 'Deny' + usePublicIpAddressPermission: 'Deny' + } ] } ] policies: [ { - name: nestedDependencies.outputs.subnetName + name: nestedDependencies.outputs.subnet1Name evaluatorType: 'MaxValuePolicy' - factData: nestedDependencies.outputs.subnetResourceId + factData: nestedDependencies.outputs.subnet1ResourceId factName: 'UserOwnedLabVmCountInSubnet' threshold: '1' } @@ -308,6 +319,8 @@ module testDeployment '../../../main.bicep' = [ currencyCode: 'AUD' thresholdValue100DisplayOnChart: 'Enabled' thresholdValue100SendNotificationWhenExceeded: 'Enabled' + thresholdValue125DisplayOnChart: 'Disabled' + thresholdValue75DisplayOnChart: 'Enabled' } } } diff --git a/avm/res/dev-test-lab/lab/virtualnetwork/README.md b/avm/res/dev-test-lab/lab/virtualnetwork/README.md index 6fa8c79a9a2..203f59111b7 100644 --- a/avm/res/dev-test-lab/lab/virtualnetwork/README.md +++ b/avm/res/dev-test-lab/lab/virtualnetwork/README.md @@ -67,7 +67,48 @@ The allowed subnets of the virtual network. - Required: No - Type: array -- Default: `[]` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`labSubnetName`](#parameter-allowedsubnetslabsubnetname) | string | The name of the subnet as seen in the lab. | +| [`resourceId`](#parameter-allowedsubnetsresourceid) | string | The resource ID of the allowed subnet. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowPublicIp`](#parameter-allowedsubnetsallowpublicip) | string | The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). | + +### Parameter: `allowedSubnets.labSubnetName` + +The name of the subnet as seen in the lab. + +- Required: Yes +- Type: string + +### Parameter: `allowedSubnets.resourceId` + +The resource ID of the allowed subnet. + +- Required: Yes +- Type: string + +### Parameter: `allowedSubnets.allowPublicIp` + +The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Default' + 'Deny' + ] + ``` ### Parameter: `description` @@ -75,7 +116,6 @@ The description of the virtual network. - Required: No - Type: string -- Default: `''` ### Parameter: `subnetOverrides` @@ -83,7 +123,121 @@ The subnet overrides of the virtual network. - Required: No - Type: array -- Default: `[]` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`labSubnetName`](#parameter-subnetoverrideslabsubnetname) | string | The name given to the subnet within the lab. | +| [`resourceId`](#parameter-subnetoverridesresourceid) | string | The resource ID of the subnet. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`sharedPublicIpAddressConfiguration`](#parameter-subnetoverridessharedpublicipaddressconfiguration) | object | The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). | +| [`useInVmCreationPermission`](#parameter-subnetoverridesuseinvmcreationpermission) | string | Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny). | +| [`usePublicIpAddressPermission`](#parameter-subnetoverridesusepublicipaddresspermission) | string | Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny). | +| [`virtualNetworkPoolName`](#parameter-subnetoverridesvirtualnetworkpoolname) | string | The virtual network pool associated with this subnet. | + +### Parameter: `subnetOverrides.labSubnetName` + +The name given to the subnet within the lab. + +- Required: Yes +- Type: string + +### Parameter: `subnetOverrides.resourceId` + +The resource ID of the subnet. + +- Required: Yes +- Type: string + +### Parameter: `subnetOverrides.sharedPublicIpAddressConfiguration` + +The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny)). + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowedPorts`](#parameter-subnetoverridessharedpublicipaddressconfigurationallowedports) | array | Backend ports that virtual machines on this subnet are allowed to expose. | + +### Parameter: `subnetOverrides.sharedPublicIpAddressConfiguration.allowedPorts` + +Backend ports that virtual machines on this subnet are allowed to expose. + +- Required: Yes +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`backendPort`](#parameter-subnetoverridessharedpublicipaddressconfigurationallowedportsbackendport) | int | Backend port of the target virtual machine. | +| [`transportProtocol`](#parameter-subnetoverridessharedpublicipaddressconfigurationallowedportstransportprotocol) | string | Protocol type of the port. | + +### Parameter: `subnetOverrides.sharedPublicIpAddressConfiguration.allowedPorts.backendPort` + +Backend port of the target virtual machine. + +- Required: Yes +- Type: int + +### Parameter: `subnetOverrides.sharedPublicIpAddressConfiguration.allowedPorts.transportProtocol` + +Protocol type of the port. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Tcp' + 'Udp' + ] + ``` + +### Parameter: `subnetOverrides.useInVmCreationPermission` + +Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny). + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Default' + 'Deny' + ] + ``` + +### Parameter: `subnetOverrides.usePublicIpAddressPermission` + +Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny). + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Allow' + 'Default' + 'Deny' + ] + ``` + +### Parameter: `subnetOverrides.virtualNetworkPoolName` + +The virtual network pool associated with this subnet. + +- Required: No +- Type: string ### Parameter: `tags` diff --git a/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep b/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep index 07e8f88432e..ac6911125dc 100644 --- a/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep +++ b/avm/res/dev-test-lab/lab/virtualnetwork/main.bicep @@ -17,13 +17,13 @@ param externalProviderResourceId string param tags object? @sys.description('Optional. The description of the virtual network.') -param description string = '' +param description string? @sys.description('Optional. The allowed subnets of the virtual network.') -param allowedSubnets array = [] +param allowedSubnets allowedSubnetType @sys.description('Optional. The subnet overrides of the virtual network.') -param subnetOverrides array = [] +param subnetOverrides subnetOverrideType resource lab 'Microsoft.DevTestLab/labs@2018-09-15' existing = { name: labName @@ -64,7 +64,7 @@ type allowedSubnetType = { @sys.description('Required. The name of the subnet as seen in the lab.') labSubnetName: string -} +}[]? @export() type subnetOverrideType = { @@ -84,7 +84,7 @@ type subnetOverrideType = { @sys.description('Required. Protocol type of the port.') transportProtocol: 'Tcp' | 'Udp' }[] - } + }? @sys.description('Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny).') useInVmCreationPermission: 'Allow' | 'Deny' | 'Default'? @@ -94,4 +94,4 @@ type subnetOverrideType = { @sys.description('Optional. The virtual network pool associated with this subnet.') virtualNetworkPoolName: string? -} +}[]? diff --git a/avm/res/dev-test-lab/lab/virtualnetwork/main.json b/avm/res/dev-test-lab/lab/virtualnetwork/main.json index 1eab7d68ebf..d68480b5a3d 100644 --- a/avm/res/dev-test-lab/lab/virtualnetwork/main.json +++ b/avm/res/dev-test-lab/lab/virtualnetwork/main.json @@ -6,127 +6,136 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "18309816581107210302" + "templateHash": "12122718661184299591" }, "name": "DevTest Lab Virtual Networks", - "description": "This module deploys a DevTest Lab Virtual Network.\r\n\r\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", + "description": "This module deploys a DevTest Lab Virtual Network.\n\nLab virtual machines must be deployed into a virtual network. This resource type allows configuring the virtual network and subnet settings used for the lab virtual machines.", "owner": "Azure/module-maintainers" }, "definitions": { "allowedSubnetType": { - "type": "object", - "properties": { - "allowPublicIp": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the allowed subnet." - } - }, - "labSubnetName": { - "type": "string", - "metadata": { - "description": "Required. The name of the subnet as seen in the lab." + "type": "array", + "items": { + "type": "object", + "properties": { + "allowPublicIp": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the allowed subnet." + } + }, + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name of the subnet as seen in the lab." + } } } }, + "nullable": true, "metadata": { "__bicep_export!": true } }, "subnetOverrideType": { - "type": "object", - "properties": { - "labSubnetName": { - "type": "string", - "metadata": { - "description": "Required. The name given to the subnet within the lab." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the subnet." - } - }, - "sharedPublicIpAddressConfiguration": { - "type": "object", - "properties": { - "allowedPorts": { - "type": "array", - "items": { - "type": "object", - "properties": { - "backendPort": { - "type": "int", - "metadata": { - "description": "Required. Backend port of the target virtual machine." - } - }, - "transportProtocol": { - "type": "string", - "allowedValues": [ - "Tcp", - "Udp" - ], - "metadata": { - "description": "Required. Protocol type of the port." + "type": "array", + "items": { + "type": "object", + "properties": { + "labSubnetName": { + "type": "string", + "metadata": { + "description": "Required. The name given to the subnet within the lab." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "sharedPublicIpAddressConfiguration": { + "type": "object", + "properties": { + "allowedPorts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "backendPort": { + "type": "int", + "metadata": { + "description": "Required. Backend port of the target virtual machine." + } + }, + "transportProtocol": { + "type": "string", + "allowedValues": [ + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Protocol type of the port." + } } } + }, + "metadata": { + "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." } - }, - "metadata": { - "description": "Required. Backend ports that virtual machines on this subnet are allowed to expose." } + }, + "nullable": true, + "metadata": { + "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." } }, - "metadata": { - "description": "Optional. The permission policy of the subnet for allowing public IP addresses (i.e. Allow, Deny))." - } - }, - "useInVmCreationPermission": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." - } - }, - "usePublicIpAddressPermission": { - "type": "string", - "allowedValues": [ - "Allow", - "Default", - "Deny" - ], - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." - } - }, - "virtualNetworkPoolName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The virtual network pool associated with this subnet." + "useInVmCreationPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether this subnet can be used during virtual machine creation (i.e. Allow, Deny)." + } + }, + "usePublicIpAddressPermission": { + "type": "string", + "allowedValues": [ + "Allow", + "Default", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether public IP addresses can be assigned to virtual machines on this subnet (i.e. Allow, Deny)." + } + }, + "virtualNetworkPoolName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The virtual network pool associated with this subnet." + } } } }, + "nullable": true, "metadata": { "__bicep_export!": true } @@ -160,21 +169,19 @@ }, "description": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The description of the virtual network." } }, "allowedSubnets": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/allowedSubnetType", "metadata": { "description": "Optional. The allowed subnets of the virtual network." } }, "subnetOverrides": { - "type": "array", - "defaultValue": [], + "$ref": "#/definitions/subnetOverrideType", "metadata": { "description": "Optional. The subnet overrides of the virtual network." } From d13be75d4ee0012933cc638f7a170154121c3108 Mon Sep 17 00:00:00 2001 From: Kris Baranek <20225789+krbar@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:09:40 +0200 Subject: [PATCH 04/46] fix: Remove '(Preview)' label from role name - batch 4 (#3229) ## Description Remove '(Preview)' label from all 'Role Based Access Control Administrator' role as described in #3112 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.authorization.role-assignment](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml) | | [![avm.res.operational-insights.workspace](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml) | | [![avm.res.portal.dashboard](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.portal.dashboard.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.portal.dashboard.yml) | | [![avm.res.purview.account](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml) | | [![avm.res.recovery-services.vault](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml) | | [![avm.res.relay.namespace](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml) | | [![avm.res.resource-graph.query](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml) | | [![avm.res.resources.deployment-script](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml) | | [![avm.res.resources.resource-group](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml) | | [![avm.res.search.search-service](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml) | | [![avm.res.service-bus.namespace](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml) | | [![avm.res.service-fabric.cluster](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml) | | [![avm.res.signal-r-service.signal-r](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml) | | [![avm.res.signal-r-service.web-pub-sub](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml) | | [![avm.res.sql.server](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml) | | [![avm.res.storage.storage-account](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) | | [![avm.res.synapse.private-link-hub](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml) | | [![avm.res.synapse.workspace](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml) | | [![avm.res.virtual-machine-images.image-template](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml) | | [![avm.res.web.connection](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml) | | [![avm.res.web.site](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.web.site.yml) | | [![avm.res.web.static-site](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml/badge.svg?branch=users%2Fkrbar%2FrbacAdminRename4&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [x] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../authorization/role-assignment/main.json | 22 +++++++------- .../modules/management-group.bicep | 24 +++++++++++---- .../modules/resource-group.bicep | 18 ++++++++--- .../modules/subscription.bicep | 18 ++++++++--- .../role-assignment/version.json | 4 +-- .../operational-insights/workspace/main.bicep | 2 +- .../operational-insights/workspace/main.json | 8 ++--- .../workspace/table/main.bicep | 2 +- .../workspace/table/main.json | 4 +-- .../workspace/version.json | 2 +- avm/res/portal/dashboard/main.bicep | 2 +- avm/res/portal/dashboard/main.json | 4 +-- avm/res/portal/dashboard/version.json | 2 +- avm/res/purview/account/main.bicep | 2 +- avm/res/purview/account/main.json | 4 +-- avm/res/purview/account/version.json | 2 +- avm/res/recovery-services/vault/main.bicep | 2 +- avm/res/recovery-services/vault/main.json | 4 +-- avm/res/recovery-services/vault/version.json | 2 +- .../namespace/hybrid-connection/main.bicep | 2 +- .../namespace/hybrid-connection/main.json | 4 +-- avm/res/relay/namespace/main.bicep | 2 +- avm/res/relay/namespace/main.json | 12 ++++---- avm/res/relay/namespace/version.json | 2 +- avm/res/relay/namespace/wcf-relay/main.bicep | 2 +- avm/res/relay/namespace/wcf-relay/main.json | 4 +-- avm/res/resource-graph/query/main.bicep | 2 +- avm/res/resource-graph/query/main.json | 4 +-- avm/res/resource-graph/query/version.json | 2 +- .../resources/deployment-script/main.bicep | 2 +- avm/res/resources/deployment-script/main.json | 4 +-- .../resources/deployment-script/version.json | 2 +- avm/res/resources/resource-group/main.json | 6 ++-- .../modules/nested_roleAssignments.bicep | 2 +- avm/res/resources/resource-group/version.json | 2 +- avm/res/search/search-service/main.bicep | 2 +- avm/res/search/search-service/main.json | 4 +-- avm/res/search/search-service/version.json | 2 +- avm/res/service-bus/namespace/main.bicep | 2 +- avm/res/service-bus/namespace/main.json | 12 ++++---- .../service-bus/namespace/queue/main.bicep | 2 +- avm/res/service-bus/namespace/queue/main.json | 4 +-- .../service-bus/namespace/topic/main.bicep | 2 +- avm/res/service-bus/namespace/topic/main.json | 4 +-- avm/res/service-bus/namespace/version.json | 2 +- avm/res/service-fabric/cluster/main.bicep | 2 +- avm/res/service-fabric/cluster/main.json | 4 +-- avm/res/service-fabric/cluster/version.json | 2 +- avm/res/signal-r-service/signal-r/main.bicep | 2 +- avm/res/signal-r-service/signal-r/main.json | 4 +-- .../signal-r-service/signal-r/version.json | 2 +- .../signal-r-service/web-pub-sub/main.bicep | 2 +- .../signal-r-service/web-pub-sub/main.json | 4 +-- .../signal-r-service/web-pub-sub/version.json | 2 +- avm/res/sql/server/main.bicep | 2 +- avm/res/sql/server/main.json | 4 +-- avm/res/sql/server/version.json | 2 +- .../blob-service/container/main.bicep | 2 +- .../blob-service/container/main.json | 4 +-- .../storage-account/blob-service/main.json | 6 ++-- .../storage-account/file-service/main.json | 8 ++--- .../file-service/share/main.json | 6 ++-- .../share/modules/nested_roleAssignment.bicep | 2 +- avm/res/storage/storage-account/main.bicep | 2 +- avm/res/storage/storage-account/main.json | 30 +++++++++---------- .../storage-account/queue-service/main.json | 6 ++-- .../queue-service/queue/main.bicep | 2 +- .../queue-service/queue/main.json | 4 +-- .../storage-account/table-service/main.json | 6 ++-- .../table-service/table/main.bicep | 2 +- .../table-service/table/main.json | 4 +-- avm/res/storage/storage-account/version.json | 2 +- avm/res/synapse/private-link-hub/main.bicep | 2 +- avm/res/synapse/private-link-hub/main.json | 4 +-- avm/res/synapse/private-link-hub/version.json | 2 +- avm/res/synapse/workspace/main.bicep | 2 +- avm/res/synapse/workspace/main.json | 4 +-- avm/res/synapse/workspace/version.json | 2 +- .../image-template/main.bicep | 2 +- .../image-template/main.json | 4 +-- .../image-template/version.json | 2 +- avm/res/web/connection/main.bicep | 2 +- avm/res/web/connection/main.json | 4 +-- avm/res/web/connection/version.json | 2 +- avm/res/web/site/main.bicep | 2 +- avm/res/web/site/main.json | 8 ++--- avm/res/web/site/slot/main.bicep | 2 +- avm/res/web/site/slot/main.json | 4 +-- avm/res/web/site/version.json | 2 +- avm/res/web/static-site/main.bicep | 2 +- avm/res/web/static-site/main.json | 4 +-- avm/res/web/static-site/version.json | 2 +- 92 files changed, 213 insertions(+), 181 deletions(-) diff --git a/avm/ptn/authorization/role-assignment/main.json b/avm/ptn/authorization/role-assignment/main.json index e7d405fa2be..e908d925ceb 100644 --- a/avm/ptn/authorization/role-assignment/main.json +++ b/avm/ptn/authorization/role-assignment/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "7625867886550880091" + "version": "0.29.47.4906", + "templateHash": "11572179981104603818" }, "name": "Role Assignments (All scopes)", "description": "This module deploys a Role Assignment at a Management Group, Subscription or Resource Group scope.", @@ -164,8 +164,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "8906093264527258150" + "version": "0.29.47.4906", + "templateHash": "16307162966269052514" }, "name": "Role Assignments (Management Group scope)", "description": "This module deploys a Role Assignment at a Management Group scope.", @@ -244,7 +244,7 @@ "Owner": "/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Resource Policy Contributor": "/providers/Microsoft.Authorization/roleDefinitions/36243c78-bf99-498c-9df9-86d9f8d28608", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", "Management Group Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ac63b705-f282-497d-ac71-919bf39d939d')]" }, @@ -328,8 +328,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "707244099707019442" + "version": "0.29.47.4906", + "templateHash": "7865292958983636483" }, "name": "Role Assignments (Subscription scope)", "description": "This module deploys a Role Assignment at a Subscription scope.", @@ -407,7 +407,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" }, "roleDefinitionIdVar": "[if(contains(variables('builtInRoleNames'), parameters('roleDefinitionIdOrName')), variables('builtInRoleNames')[parameters('roleDefinitionIdOrName')], parameters('roleDefinitionIdOrName'))]" @@ -500,8 +500,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "14591941439222880522" + "version": "0.29.47.4906", + "templateHash": "4013817963006824674" }, "name": "Role Assignments (Resource Group scope)", "description": "This module deploys a Role Assignment at a Resource Group scope.", @@ -585,7 +585,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" }, "roleDefinitionIdVar": "[if(contains(variables('builtInRoleNames'), parameters('roleDefinitionIdOrName')), variables('builtInRoleNames')[parameters('roleDefinitionIdOrName')], parameters('roleDefinitionIdOrName'))]" diff --git a/avm/ptn/authorization/role-assignment/modules/management-group.bicep b/avm/ptn/authorization/role-assignment/modules/management-group.bicep index b1a51d40f96..18381b16e19 100644 --- a/avm/ptn/authorization/role-assignment/modules/management-group.bicep +++ b/avm/ptn/authorization/role-assignment/modules/management-group.bicep @@ -44,13 +44,23 @@ var builtInRoleNames = { Owner: '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635' Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') 'Resource Policy Contributor': '/providers/Microsoft.Authorization/roleDefinitions/36243c78-bf99-498c-9df9-86d9f8d28608' - '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') - 'Management Group Reader': subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ac63b705-f282-497d-ac71-919bf39d939d') + 'Role Based Access Control Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) + 'Management Group Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'ac63b705-f282-497d-ac71-919bf39d939d' + ) } -var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName) - +var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) + ? builtInRoleNames[roleDefinitionIdOrName] + : roleDefinitionIdOrName) resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(managementGroupId, roleDefinitionIdVar, principalId) @@ -59,7 +69,9 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { principalId: principalId description: !empty(description) ? description : null principalType: !empty(principalType) ? any(principalType) : null - delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) + ? delegatedManagedIdentityResourceId + : null conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null condition: !empty(condition) ? condition : null } diff --git a/avm/ptn/authorization/role-assignment/modules/resource-group.bicep b/avm/ptn/authorization/role-assignment/modules/resource-group.bicep index 459bde3f99d..edf04b698ad 100644 --- a/avm/ptn/authorization/role-assignment/modules/resource-group.bicep +++ b/avm/ptn/authorization/role-assignment/modules/resource-group.bicep @@ -46,11 +46,19 @@ var builtInRoleNames = { 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') + 'Role Based Access Control Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } -var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName) +var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) + ? builtInRoleNames[roleDefinitionIdOrName] + : roleDefinitionIdOrName) resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscriptionId, resourceGroupName, roleDefinitionIdVar, principalId) @@ -59,7 +67,9 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { principalId: principalId description: !empty(description) ? description : null principalType: !empty(principalType) ? any(principalType) : null - delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) + ? delegatedManagedIdentityResourceId + : null conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null condition: !empty(condition) ? condition : null } diff --git a/avm/ptn/authorization/role-assignment/modules/subscription.bicep b/avm/ptn/authorization/role-assignment/modules/subscription.bicep index 2a41bc6b07d..c74f7eb5135 100644 --- a/avm/ptn/authorization/role-assignment/modules/subscription.bicep +++ b/avm/ptn/authorization/role-assignment/modules/subscription.bicep @@ -43,11 +43,19 @@ var builtInRoleNames = { 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') + 'Role Based Access Control Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) } -var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) ? builtInRoleNames[roleDefinitionIdOrName] : roleDefinitionIdOrName) +var roleDefinitionIdVar = (contains(builtInRoleNames, roleDefinitionIdOrName) + ? builtInRoleNames[roleDefinitionIdOrName] + : roleDefinitionIdOrName) resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(subscriptionId, roleDefinitionIdVar, principalId) @@ -56,7 +64,9 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { principalId: principalId description: !empty(description) ? description : null principalType: !empty(principalType) ? any(principalType) : null - delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) ? delegatedManagedIdentityResourceId : null + delegatedManagedIdentityResourceId: !empty(delegatedManagedIdentityResourceId) + ? delegatedManagedIdentityResourceId + : null conditionVersion: !empty(conditionVersion) && !empty(condition) ? conditionVersion : null condition: !empty(condition) ? condition : null } diff --git a/avm/ptn/authorization/role-assignment/version.json b/avm/ptn/authorization/role-assignment/version.json index 7fa401bdf78..1c035df49f2 100644 --- a/avm/ptn/authorization/role-assignment/version.json +++ b/avm/ptn/authorization/role-assignment/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.1", + "version": "0.2", "pathFilters": [ "./main.json" ] -} +} \ No newline at end of file diff --git a/avm/res/operational-insights/workspace/main.bicep b/avm/res/operational-insights/workspace/main.bicep index 4c6bd5e5a1b..b5be2532024 100644 --- a/avm/res/operational-insights/workspace/main.bicep +++ b/avm/res/operational-insights/workspace/main.bicep @@ -132,7 +132,7 @@ var builtInRoleNames = { ) 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/operational-insights/workspace/main.json b/avm/res/operational-insights/workspace/main.json index ccbd83e3c15..61d489e40d9 100644 --- a/avm/res/operational-insights/workspace/main.json +++ b/avm/res/operational-insights/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11994891550110156380" + "templateHash": "15059379956726052447" }, "name": "Log Analytics Workspaces", "description": "This module deploys a Log Analytics Workspace.", @@ -468,7 +468,7 @@ "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Security Admin": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb1c8493-542b-48eb-b624-b4c8fea62acd')]", "Security Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '39bc4728-0917-49c7-9d2c-d95423bc2eb4')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" @@ -1567,7 +1567,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6905244456918791391" + "templateHash": "10380077652898392916" }, "name": "Log Analytics Workspace Tables", "description": "This module deploys a Log Analytics Workspace Table.", @@ -1734,7 +1734,7 @@ "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/operational-insights/workspace/table/main.bicep b/avm/res/operational-insights/workspace/table/main.bicep index cee095b8d28..b38ff9210f5 100644 --- a/avm/res/operational-insights/workspace/table/main.bicep +++ b/avm/res/operational-insights/workspace/table/main.bicep @@ -61,7 +61,7 @@ var builtInRoleNames = { ) 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/operational-insights/workspace/table/main.json b/avm/res/operational-insights/workspace/table/main.json index 18cdd3e09bd..6a1e1e11a68 100644 --- a/avm/res/operational-insights/workspace/table/main.json +++ b/avm/res/operational-insights/workspace/table/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6905244456918791391" + "templateHash": "10380077652898392916" }, "name": "Log Analytics Workspace Tables", "description": "This module deploys a Log Analytics Workspace Table.", @@ -173,7 +173,7 @@ "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/operational-insights/workspace/version.json b/avm/res/operational-insights/workspace/version.json index e42c3d9e5fb..7e1d3f4157e 100644 --- a/avm/res/operational-insights/workspace/version.json +++ b/avm/res/operational-insights/workspace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.6", + "version": "0.7", "pathFilters": [ "./main.json" ] diff --git a/avm/res/portal/dashboard/main.bicep b/avm/res/portal/dashboard/main.bicep index b732ee714ce..aa4b03c4de4 100644 --- a/avm/res/portal/dashboard/main.bicep +++ b/avm/res/portal/dashboard/main.bicep @@ -31,7 +31,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/portal/dashboard/main.json b/avm/res/portal/dashboard/main.json index 32fcb39e933..b8eca11f502 100644 --- a/avm/res/portal/dashboard/main.json +++ b/avm/res/portal/dashboard/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4329413484700617362" + "templateHash": "417422598154649479" }, "name": "Portal Dashboards", "description": "This module deploys a Portal Dashboard.", @@ -182,7 +182,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/portal/dashboard/version.json b/avm/res/portal/dashboard/version.json index 729ac87673b..76049e1c4ad 100644 --- a/avm/res/portal/dashboard/version.json +++ b/avm/res/portal/dashboard/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.2", + "version": "0.3", "pathFilters": [ "./main.json" ] diff --git a/avm/res/purview/account/main.bicep b/avm/res/purview/account/main.bicep index 392942cbf61..e0d408fdb13 100644 --- a/avm/res/purview/account/main.bicep +++ b/avm/res/purview/account/main.bicep @@ -73,7 +73,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/purview/account/main.json b/avm/res/purview/account/main.json index f4cf2712e16..34bd78df71c 100644 --- a/avm/res/purview/account/main.json +++ b/avm/res/purview/account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15039051778035234501" + "templateHash": "14495497067965615328" }, "name": "Purview Accounts", "description": "This module deploys a Purview Account.", @@ -588,7 +588,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/purview/account/version.json b/avm/res/purview/account/version.json index 3f863a2bec9..a8eda31021f 100644 --- a/avm/res/purview/account/version.json +++ b/avm/res/purview/account/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] diff --git a/avm/res/recovery-services/vault/main.bicep b/avm/res/recovery-services/vault/main.bicep index 5ee99722fa1..6f984370b81 100644 --- a/avm/res/recovery-services/vault/main.bicep +++ b/avm/res/recovery-services/vault/main.bicep @@ -94,7 +94,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/recovery-services/vault/main.json b/avm/res/recovery-services/vault/main.json index 91ffe8c5009..f05335967c2 100644 --- a/avm/res/recovery-services/vault/main.json +++ b/avm/res/recovery-services/vault/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1317340221517130136" + "templateHash": "11169688111946598929" }, "name": "Recovery Services Vaults", "description": "This module deploys a Recovery Services Vault.", @@ -625,7 +625,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Site Recovery Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6670b86e-a3f7-4917-ac9b-5d6ab1be4567')]", "Site Recovery Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '494ae006-db33-4328-bf46-533a6560a3ca')]", "Site Recovery Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'dbaa88c4-0c30-4179-9fb3-46319faa6149')]", diff --git a/avm/res/recovery-services/vault/version.json b/avm/res/recovery-services/vault/version.json index 3f863a2bec9..a8eda31021f 100644 --- a/avm/res/recovery-services/vault/version.json +++ b/avm/res/recovery-services/vault/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] diff --git a/avm/res/relay/namespace/hybrid-connection/main.bicep b/avm/res/relay/namespace/hybrid-connection/main.bicep index 6c31cfede6d..e12ee555a90 100644 --- a/avm/res/relay/namespace/hybrid-connection/main.bicep +++ b/avm/res/relay/namespace/hybrid-connection/main.bicep @@ -64,7 +64,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/relay/namespace/hybrid-connection/main.json b/avm/res/relay/namespace/hybrid-connection/main.json index 219af4e2d5f..b5807ecc0dc 100644 --- a/avm/res/relay/namespace/hybrid-connection/main.json +++ b/avm/res/relay/namespace/hybrid-connection/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6090434662270085474" + "templateHash": "3066452421480890151" }, "name": "Relay Namespace Hybrid Connections", "description": "This module deploys a Relay Namespace Hybrid Connection.", @@ -198,7 +198,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/relay/namespace/main.bicep b/avm/res/relay/namespace/main.bicep index e28a037e516..9758646370c 100644 --- a/avm/res/relay/namespace/main.bicep +++ b/avm/res/relay/namespace/main.bicep @@ -71,7 +71,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/relay/namespace/main.json b/avm/res/relay/namespace/main.json index 66c0fbf16e5..dab21e582d3 100644 --- a/avm/res/relay/namespace/main.json +++ b/avm/res/relay/namespace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "10535689791446770792" + "templateHash": "10748276001217770184" }, "name": "Relay Namespaces", "description": "This module deploys a Relay Namespace", @@ -569,7 +569,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -939,7 +939,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6090434662270085474" + "templateHash": "3066452421480890151" }, "name": "Relay Namespace Hybrid Connections", "description": "This module deploys a Relay Namespace Hybrid Connection.", @@ -1131,7 +1131,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -1367,7 +1367,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4256310187824299728" + "templateHash": "25032660008872161" }, "name": "Relay Namespace WCF Relays", "description": "This module deploys a Relay Namespace WCF Relay.", @@ -1577,7 +1577,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/relay/namespace/version.json b/avm/res/relay/namespace/version.json index 3f863a2bec9..a8eda31021f 100644 --- a/avm/res/relay/namespace/version.json +++ b/avm/res/relay/namespace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] diff --git a/avm/res/relay/namespace/wcf-relay/main.bicep b/avm/res/relay/namespace/wcf-relay/main.bicep index d38ae8a614e..c2d9814469d 100644 --- a/avm/res/relay/namespace/wcf-relay/main.bicep +++ b/avm/res/relay/namespace/wcf-relay/main.bicep @@ -74,7 +74,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/relay/namespace/wcf-relay/main.json b/avm/res/relay/namespace/wcf-relay/main.json index f5db1226834..681b9dac738 100644 --- a/avm/res/relay/namespace/wcf-relay/main.json +++ b/avm/res/relay/namespace/wcf-relay/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4256310187824299728" + "templateHash": "25032660008872161" }, "name": "Relay Namespace WCF Relays", "description": "This module deploys a Relay Namespace WCF Relay.", @@ -216,7 +216,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/resource-graph/query/main.bicep b/avm/res/resource-graph/query/main.bicep index 3f02f3f7eea..80149b6fc49 100644 --- a/avm/res/resource-graph/query/main.bicep +++ b/avm/res/resource-graph/query/main.bicep @@ -38,7 +38,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/resource-graph/query/main.json b/avm/res/resource-graph/query/main.json index 9ba837cb409..f11a8d7c87e 100644 --- a/avm/res/resource-graph/query/main.json +++ b/avm/res/resource-graph/query/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "2991554121224170850" + "templateHash": "15439682235813635403" }, "name": "Resource Graph Queries", "description": "This module deploys a Resource Graph Query.", @@ -178,7 +178,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/resource-graph/query/version.json b/avm/res/resource-graph/query/version.json index 1c035df49f2..c177b1bb58b 100644 --- a/avm/res/resource-graph/query/version.json +++ b/avm/res/resource-graph/query/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.2", + "version": "0.3", "pathFilters": [ "./main.json" ] diff --git a/avm/res/resources/deployment-script/main.bicep b/avm/res/resources/deployment-script/main.bicep index 55dc0612e2b..49b96d7a481 100644 --- a/avm/res/resources/deployment-script/main.bicep +++ b/avm/res/resources/deployment-script/main.bicep @@ -92,7 +92,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/resources/deployment-script/main.json b/avm/res/resources/deployment-script/main.json index 96f10eba581..37eaaa6a2a6 100644 --- a/avm/res/resources/deployment-script/main.json +++ b/avm/res/resources/deployment-script/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11095024633645020544" + "templateHash": "8443498654175834102" }, "name": "Deployment Scripts", "description": "This module deploys Deployment Scripts.", @@ -345,7 +345,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" }, "containerSettings": { diff --git a/avm/res/resources/deployment-script/version.json b/avm/res/resources/deployment-script/version.json index 76049e1c4ad..13669e66018 100644 --- a/avm/res/resources/deployment-script/version.json +++ b/avm/res/resources/deployment-script/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] diff --git a/avm/res/resources/resource-group/main.json b/avm/res/resources/resource-group/main.json index d0a5b4e2a30..3e371b30599 100644 --- a/avm/res/resources/resource-group/main.json +++ b/avm/res/resources/resource-group/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1112152378548482375" + "templateHash": "12280129054844550830" }, "name": "Resource Groups", "description": "This module deploys a Resource Group.", @@ -296,7 +296,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4236428492292165426" + "templateHash": "10992477577200576081" } }, "definitions": { @@ -396,7 +396,7 @@ "Quota Request Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0e5f05e5-9ab9-446b-b98d-1e2157c94125')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Resource Policy Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Tag Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4a9ae827-6dc8-4573-8ac7-8239d42aa03f')]", "Template Spec Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1c9b6475-caf0-4164-b5a1-2142a7116f4b')]", "Template Spec Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '392ae280-861d-42bd-9ea5-08ee6d83b80e')]", diff --git a/avm/res/resources/resource-group/modules/nested_roleAssignments.bicep b/avm/res/resources/resource-group/modules/nested_roleAssignments.bicep index bf8bc903d4e..9916ca8032a 100644 --- a/avm/res/resources/resource-group/modules/nested_roleAssignments.bicep +++ b/avm/res/resources/resource-group/modules/nested_roleAssignments.bicep @@ -13,7 +13,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/resources/resource-group/version.json b/avm/res/resources/resource-group/version.json index c177b1bb58b..3f863a2bec9 100644 --- a/avm/res/resources/resource-group/version.json +++ b/avm/res/resources/resource-group/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] diff --git a/avm/res/search/search-service/main.bicep b/avm/res/search/search-service/main.bicep index bc455a8f68c..951adfe7254 100644 --- a/avm/res/search/search-service/main.bicep +++ b/avm/res/search/search-service/main.bicep @@ -123,7 +123,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/search/search-service/main.json b/avm/res/search/search-service/main.json index 04beacbf9ea..c4b625ff47b 100644 --- a/avm/res/search/search-service/main.json +++ b/avm/res/search/search-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "10051405020131908010" + "templateHash": "8483667347070963331" }, "name": "Search Services", "description": "This module deploys a Search Service.", @@ -656,7 +656,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Search Index Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')]", "Search Index Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", "Search Service Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", diff --git a/avm/res/search/search-service/version.json b/avm/res/search/search-service/version.json index e42c3d9e5fb..7e1d3f4157e 100644 --- a/avm/res/search/search-service/version.json +++ b/avm/res/search/search-service/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.6", + "version": "0.7", "pathFilters": [ "./main.json" ] diff --git a/avm/res/service-bus/namespace/main.bicep b/avm/res/service-bus/namespace/main.bicep index 507297992b5..08ecf97e725 100644 --- a/avm/res/service-bus/namespace/main.bicep +++ b/avm/res/service-bus/namespace/main.bicep @@ -126,7 +126,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/service-bus/namespace/main.json b/avm/res/service-bus/namespace/main.json index e3bdecdc0f6..b3917916f07 100644 --- a/avm/res/service-bus/namespace/main.json +++ b/avm/res/service-bus/namespace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "10964684048264975988" + "templateHash": "16628890374295506516" }, "name": "Service Bus Namespaces", "description": "This module deploys a Service Bus Namespace.", @@ -1318,7 +1318,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -2020,7 +2020,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7247345844358420118" + "templateHash": "12442268068778335924" }, "name": "Service Bus Namespace Queue", "description": "This module deploys a Service Bus Namespace Queue.", @@ -2301,7 +2301,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -2589,7 +2589,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "3425340822225922607" + "templateHash": "782028791267114581" }, "name": "Service Bus Namespace Topic", "description": "This module deploys a Service Bus Namespace Topic.", @@ -2998,7 +2998,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/service-bus/namespace/queue/main.bicep b/avm/res/service-bus/namespace/queue/main.bicep index c8375f00a16..b6e1097705f 100644 --- a/avm/res/service-bus/namespace/queue/main.bicep +++ b/avm/res/service-bus/namespace/queue/main.bicep @@ -96,7 +96,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/service-bus/namespace/queue/main.json b/avm/res/service-bus/namespace/queue/main.json index 7eb70080098..49ba95b0686 100644 --- a/avm/res/service-bus/namespace/queue/main.json +++ b/avm/res/service-bus/namespace/queue/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7247345844358420118" + "templateHash": "12442268068778335924" }, "name": "Service Bus Namespace Queue", "description": "This module deploys a Service Bus Namespace Queue.", @@ -287,7 +287,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/service-bus/namespace/topic/main.bicep b/avm/res/service-bus/namespace/topic/main.bicep index 9b4683fb7c9..20096333931 100644 --- a/avm/res/service-bus/namespace/topic/main.bicep +++ b/avm/res/service-bus/namespace/topic/main.bicep @@ -95,7 +95,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/service-bus/namespace/topic/main.json b/avm/res/service-bus/namespace/topic/main.json index 55bcfc9651a..f037f5762dd 100644 --- a/avm/res/service-bus/namespace/topic/main.json +++ b/avm/res/service-bus/namespace/topic/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "3425340822225922607" + "templateHash": "782028791267114581" }, "name": "Service Bus Namespace Topic", "description": "This module deploys a Service Bus Namespace Topic.", @@ -415,7 +415,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/service-bus/namespace/version.json b/avm/res/service-bus/namespace/version.json index 09c3664cecd..9a9a06e8978 100644 --- a/avm/res/service-bus/namespace/version.json +++ b/avm/res/service-bus/namespace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.7", + "version": "0.8", "pathFilters": [ "./main.json" ] diff --git a/avm/res/service-fabric/cluster/main.bicep b/avm/res/service-fabric/cluster/main.bicep index cef122c80f3..41089bc7ac0 100644 --- a/avm/res/service-fabric/cluster/main.bicep +++ b/avm/res/service-fabric/cluster/main.bicep @@ -246,7 +246,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/service-fabric/cluster/main.json b/avm/res/service-fabric/cluster/main.json index ae343dc1173..3f0955fb78c 100644 --- a/avm/res/service-fabric/cluster/main.json +++ b/avm/res/service-fabric/cluster/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "10632901635628178846" + "templateHash": "11160466068108868601" }, "name": "Service Fabric Clusters", "description": "This module deploys a Service Fabric Cluster.", @@ -572,7 +572,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/service-fabric/cluster/version.json b/avm/res/service-fabric/cluster/version.json index 76049e1c4ad..13669e66018 100644 --- a/avm/res/service-fabric/cluster/version.json +++ b/avm/res/service-fabric/cluster/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] diff --git a/avm/res/signal-r-service/signal-r/main.bicep b/avm/res/signal-r-service/signal-r/main.bicep index 37b74493509..ece400d2ed3 100644 --- a/avm/res/signal-r-service/signal-r/main.bicep +++ b/avm/res/signal-r-service/signal-r/main.bicep @@ -128,7 +128,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/signal-r-service/signal-r/main.json b/avm/res/signal-r-service/signal-r/main.json index 6ed88a6fada..b6f8656b92a 100644 --- a/avm/res/signal-r-service/signal-r/main.json +++ b/avm/res/signal-r-service/signal-r/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6232258125767604001" + "templateHash": "16576967735793916107" }, "name": "SignalR Service SignalR", "description": "This module deploys a SignalR Service SignalR.", @@ -548,7 +548,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "SignalR AccessKey Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '04165923-9d83-45d5-8227-78b77b0a687e')]", "SignalR App Server": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '420fcaa2-552c-430f-98ca-3264be4806c7')]", "SignalR REST API Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fd53cd77-2268-407a-8f46-7e7863d0f521')]", diff --git a/avm/res/signal-r-service/signal-r/version.json b/avm/res/signal-r-service/signal-r/version.json index 3f863a2bec9..a8eda31021f 100644 --- a/avm/res/signal-r-service/signal-r/version.json +++ b/avm/res/signal-r-service/signal-r/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] diff --git a/avm/res/signal-r-service/web-pub-sub/main.bicep b/avm/res/signal-r-service/web-pub-sub/main.bicep index a3821042f8d..760511f84b0 100644 --- a/avm/res/signal-r-service/web-pub-sub/main.bicep +++ b/avm/res/signal-r-service/web-pub-sub/main.bicep @@ -91,7 +91,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/signal-r-service/web-pub-sub/main.json b/avm/res/signal-r-service/web-pub-sub/main.json index 5807cd4da1a..51dcc979c6a 100644 --- a/avm/res/signal-r-service/web-pub-sub/main.json +++ b/avm/res/signal-r-service/web-pub-sub/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "3619836062547426326" + "templateHash": "17348552013839664242" }, "name": "SignalR Web PubSub Services", "description": "This module deploys a SignalR Web PubSub Service.", @@ -501,7 +501,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "SignalR AccessKey Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '04165923-9d83-45d5-8227-78b77b0a687e')]", "SignalR App Server": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '420fcaa2-552c-430f-98ca-3264be4806c7')]", "SignalR REST API Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fd53cd77-2268-407a-8f46-7e7863d0f521')]", diff --git a/avm/res/signal-r-service/web-pub-sub/version.json b/avm/res/signal-r-service/web-pub-sub/version.json index 3f863a2bec9..a8eda31021f 100644 --- a/avm/res/signal-r-service/web-pub-sub/version.json +++ b/avm/res/signal-r-service/web-pub-sub/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] diff --git a/avm/res/sql/server/main.bicep b/avm/res/sql/server/main.bicep index 253c26b2440..e6286681461 100644 --- a/avm/res/sql/server/main.bicep +++ b/avm/res/sql/server/main.bicep @@ -113,7 +113,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/sql/server/main.json b/avm/res/sql/server/main.json index 6f055afb2c6..b13a78c08f8 100644 --- a/avm/res/sql/server/main.json +++ b/avm/res/sql/server/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "5025800136020814452" + "templateHash": "13031224188572751832" }, "name": "Azure SQL Servers", "description": "This module deploys an Azure SQL Server.", @@ -631,7 +631,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reservation Purchaser": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "SQL DB Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec')]", "SQL Managed Instance Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4939a1f6-9ae0-4e48-a1e0-f2cbe897382d')]", "SQL Security Manager": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3')]", diff --git a/avm/res/sql/server/version.json b/avm/res/sql/server/version.json index 7e1d3f4157e..0f81d22abc4 100644 --- a/avm/res/sql/server/version.json +++ b/avm/res/sql/server/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.7", + "version": "0.8", "pathFilters": [ "./main.json" ] diff --git a/avm/res/storage/storage-account/blob-service/container/main.bicep b/avm/res/storage/storage-account/blob-service/container/main.bicep index fd1c851da32..fa0193da72f 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.bicep +++ b/avm/res/storage/storage-account/blob-service/container/main.bicep @@ -52,7 +52,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/storage/storage-account/blob-service/container/main.json b/avm/res/storage/storage-account/blob-service/container/main.json index c2b8aebe7df..98d00e679fb 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.json +++ b/avm/res/storage/storage-account/blob-service/container/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "2376441074312126168" + "templateHash": "1020003258393866601" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -189,7 +189,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/blob-service/main.json b/avm/res/storage/storage-account/blob-service/main.json index 2a053636e7f..75312674681 100644 --- a/avm/res/storage/storage-account/blob-service/main.json +++ b/avm/res/storage/storage-account/blob-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6152214765227449988" + "templateHash": "17077763197163073998" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -404,7 +404,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "2376441074312126168" + "templateHash": "1020003258393866601" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -587,7 +587,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/file-service/main.json b/avm/res/storage/storage-account/file-service/main.json index 4e9d267c821..9375230d2f6 100644 --- a/avm/res/storage/storage-account/file-service/main.json +++ b/avm/res/storage/storage-account/file-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11112322068652007329" + "templateHash": "1933197013743223154" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -287,7 +287,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7774942141784896900" + "templateHash": "13477688809575027800" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -494,7 +494,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17818484349715313082" + "templateHash": "10820882302387746924" } }, "parameters": { @@ -617,7 +617,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/file-service/share/main.json b/avm/res/storage/storage-account/file-service/share/main.json index 0d4365a4a43..90dc2205607 100644 --- a/avm/res/storage/storage-account/file-service/share/main.json +++ b/avm/res/storage/storage-account/file-service/share/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7774942141784896900" + "templateHash": "13477688809575027800" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -213,7 +213,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17818484349715313082" + "templateHash": "10820882302387746924" } }, "parameters": { @@ -336,7 +336,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/file-service/share/modules/nested_roleAssignment.bicep b/avm/res/storage/storage-account/file-service/share/modules/nested_roleAssignment.bicep index bb16f5bd94e..177a6c7c83c 100644 --- a/avm/res/storage/storage-account/file-service/share/modules/nested_roleAssignment.bicep +++ b/avm/res/storage/storage-account/file-service/share/modules/nested_roleAssignment.bicep @@ -12,7 +12,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/storage/storage-account/main.bicep b/avm/res/storage/storage-account/main.bicep index 9bcf736a9ae..014c244abcb 100644 --- a/avm/res/storage/storage-account/main.bicep +++ b/avm/res/storage/storage-account/main.bicep @@ -209,7 +209,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/storage/storage-account/main.json b/avm/res/storage/storage-account/main.json index d73a89ee8f7..38e3f3d998d 100644 --- a/avm/res/storage/storage-account/main.json +++ b/avm/res/storage/storage-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "13539810648097972278" + "templateHash": "6735651687082765200" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account.", @@ -965,7 +965,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", @@ -2266,7 +2266,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6152214765227449988" + "templateHash": "17077763197163073998" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -2664,7 +2664,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "2376441074312126168" + "templateHash": "1020003258393866601" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -2847,7 +2847,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", @@ -3126,7 +3126,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11112322068652007329" + "templateHash": "1933197013743223154" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -3407,7 +3407,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7774942141784896900" + "templateHash": "13477688809575027800" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -3614,7 +3614,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17818484349715313082" + "templateHash": "10820882302387746924" } }, "parameters": { @@ -3737,7 +3737,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", @@ -3890,7 +3890,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11962188257977966881" + "templateHash": "9552908737955027812" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -4135,7 +4135,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "18160215842105253661" + "templateHash": "1992900679572007532" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -4257,7 +4257,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", @@ -4406,7 +4406,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11825608650296439803" + "templateHash": "15143318143658591417" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -4648,7 +4648,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "16189309416113928106" + "templateHash": "16017327978473583176" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -4763,7 +4763,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/queue-service/main.json b/avm/res/storage/storage-account/queue-service/main.json index 535642de396..00065e2abef 100644 --- a/avm/res/storage/storage-account/queue-service/main.json +++ b/avm/res/storage/storage-account/queue-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11962188257977966881" + "templateHash": "9552908737955027812" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -251,7 +251,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "18160215842105253661" + "templateHash": "1992900679572007532" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -373,7 +373,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/queue-service/queue/main.bicep b/avm/res/storage/storage-account/queue-service/queue/main.bicep index 3dbc2a173d7..4b0f656ef0d 100644 --- a/avm/res/storage/storage-account/queue-service/queue/main.bicep +++ b/avm/res/storage/storage-account/queue-service/queue/main.bicep @@ -23,7 +23,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/storage/storage-account/queue-service/queue/main.json b/avm/res/storage/storage-account/queue-service/queue/main.json index 9472e55002e..8bea12f90ea 100644 --- a/avm/res/storage/storage-account/queue-service/queue/main.json +++ b/avm/res/storage/storage-account/queue-service/queue/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "18160215842105253661" + "templateHash": "1992900679572007532" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -128,7 +128,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/table-service/main.json b/avm/res/storage/storage-account/table-service/main.json index c1ec65c05a3..5582b11c4a1 100644 --- a/avm/res/storage/storage-account/table-service/main.json +++ b/avm/res/storage/storage-account/table-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11825608650296439803" + "templateHash": "15143318143658591417" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -248,7 +248,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "16189309416113928106" + "templateHash": "16017327978473583176" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -363,7 +363,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/table-service/table/main.bicep b/avm/res/storage/storage-account/table-service/table/main.bicep index 78ba2ed9e05..9b76e2f338a 100644 --- a/avm/res/storage/storage-account/table-service/table/main.bicep +++ b/avm/res/storage/storage-account/table-service/table/main.bicep @@ -20,7 +20,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/storage/storage-account/table-service/table/main.json b/avm/res/storage/storage-account/table-service/table/main.json index 5a86eccbddb..0476ee247e8 100644 --- a/avm/res/storage/storage-account/table-service/table/main.json +++ b/avm/res/storage/storage-account/table-service/table/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "16189309416113928106" + "templateHash": "16017327978473583176" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -121,7 +121,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", diff --git a/avm/res/storage/storage-account/version.json b/avm/res/storage/storage-account/version.json index 23f38158856..291fb73e827 100644 --- a/avm/res/storage/storage-account/version.json +++ b/avm/res/storage/storage-account/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.12", + "version": "0.13", "pathFilters": [ "./main.json" ] diff --git a/avm/res/synapse/private-link-hub/main.bicep b/avm/res/synapse/private-link-hub/main.bicep index 12ed80fc973..60406e8ea02 100644 --- a/avm/res/synapse/private-link-hub/main.bicep +++ b/avm/res/synapse/private-link-hub/main.bicep @@ -27,7 +27,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/synapse/private-link-hub/main.json b/avm/res/synapse/private-link-hub/main.json index b19850cd411..bdf5c2772a6 100644 --- a/avm/res/synapse/private-link-hub/main.json +++ b/avm/res/synapse/private-link-hub/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "543021970961540498" + "templateHash": "6865603283108816559" }, "name": "Azure Synapse Analytics", "description": "This module deploys an Azure Synapse Analytics (Private Link Hub).", @@ -391,7 +391,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/synapse/private-link-hub/version.json b/avm/res/synapse/private-link-hub/version.json index a8eda31021f..e42c3d9e5fb 100644 --- a/avm/res/synapse/private-link-hub/version.json +++ b/avm/res/synapse/private-link-hub/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.5", + "version": "0.6", "pathFilters": [ "./main.json" ] diff --git a/avm/res/synapse/workspace/main.bicep b/avm/res/synapse/workspace/main.bicep index e396c1db28c..e67c879082a 100644 --- a/avm/res/synapse/workspace/main.bicep +++ b/avm/res/synapse/workspace/main.bicep @@ -129,7 +129,7 @@ var builtInRoleNames = { 'Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608' ) - 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/synapse/workspace/main.json b/avm/res/synapse/workspace/main.json index 6b3b300ebff..a423ffb5a86 100644 --- a/avm/res/synapse/workspace/main.json +++ b/avm/res/synapse/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "2618570898047146464" + "templateHash": "3075067451671021037" }, "name": "Synapse Workspaces", "description": "This module deploys a Synapse Workspace.", @@ -750,7 +750,7 @@ "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", "Resource Policy Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608')]", - "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/synapse/workspace/version.json b/avm/res/synapse/workspace/version.json index 7e1d3f4157e..0f81d22abc4 100644 --- a/avm/res/synapse/workspace/version.json +++ b/avm/res/synapse/workspace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.7", + "version": "0.8", "pathFilters": [ "./main.json" ] diff --git a/avm/res/virtual-machine-images/image-template/main.bicep b/avm/res/virtual-machine-images/image-template/main.bicep index c32e5e36f00..6d68abacc9a 100644 --- a/avm/res/virtual-machine-images/image-template/main.bicep +++ b/avm/res/virtual-machine-images/image-template/main.bicep @@ -78,7 +78,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/virtual-machine-images/image-template/main.json b/avm/res/virtual-machine-images/image-template/main.json index df6d5ed0eae..8a2d773d7cd 100644 --- a/avm/res/virtual-machine-images/image-template/main.json +++ b/avm/res/virtual-machine-images/image-template/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "495678619291242908" + "templateHash": "7173338220381253397" }, "name": "Virtual Machine Image Templates", "description": "This module deploys a Virtual Machine Image Template that can be consumed by Azure Image Builder (AIB).", @@ -559,7 +559,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/virtual-machine-images/image-template/version.json b/avm/res/virtual-machine-images/image-template/version.json index c177b1bb58b..3f863a2bec9 100644 --- a/avm/res/virtual-machine-images/image-template/version.json +++ b/avm/res/virtual-machine-images/image-template/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] diff --git a/avm/res/web/connection/main.bicep b/avm/res/web/connection/main.bicep index caa5327bcf7..db51edfc256 100644 --- a/avm/res/web/connection/main.bicep +++ b/avm/res/web/connection/main.bicep @@ -103,7 +103,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/web/connection/main.json b/avm/res/web/connection/main.json index e4082883772..0d5894685ab 100644 --- a/avm/res/web/connection/main.json +++ b/avm/res/web/connection/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11729288021799571934" + "templateHash": "1429787560788637007" }, "name": "API Connections", "description": "This module deploys an Azure API Connection.", @@ -230,7 +230,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, diff --git a/avm/res/web/connection/version.json b/avm/res/web/connection/version.json index c177b1bb58b..3f863a2bec9 100644 --- a/avm/res/web/connection/version.json +++ b/avm/res/web/connection/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] diff --git a/avm/res/web/site/main.bicep b/avm/res/web/site/main.bicep index 1df6f94be1d..b7418015b6f 100644 --- a/avm/res/web/site/main.bicep +++ b/avm/res/web/site/main.bicep @@ -197,7 +197,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/web/site/main.json b/avm/res/web/site/main.json index 5d92e94f371..e6eccb36882 100644 --- a/avm/res/web/site/main.json +++ b/avm/res/web/site/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4298333170970358862" + "templateHash": "7320044434284742277" }, "name": "Web/Function Apps", "description": "This module deploys a Web or Function App.", @@ -831,7 +831,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]", "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]" @@ -1677,7 +1677,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11723916533961427311" + "templateHash": "15729572124587777376" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -2481,7 +2481,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]", "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]" diff --git a/avm/res/web/site/slot/main.bicep b/avm/res/web/site/slot/main.bicep index 1269d846dc3..b871bf0960d 100644 --- a/avm/res/web/site/slot/main.bicep +++ b/avm/res/web/site/slot/main.bicep @@ -183,7 +183,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/web/site/slot/main.json b/avm/res/web/site/slot/main.json index a81ad9d072b..8f8f81e34aa 100644 --- a/avm/res/web/site/slot/main.json +++ b/avm/res/web/site/slot/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11723916533961427311" + "templateHash": "15729572124587777376" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -810,7 +810,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]", "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]" diff --git a/avm/res/web/site/version.json b/avm/res/web/site/version.json index 7e1d3f4157e..0f81d22abc4 100644 --- a/avm/res/web/site/version.json +++ b/avm/res/web/site/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.7", + "version": "0.8", "pathFilters": [ "./main.json" ] diff --git a/avm/res/web/static-site/main.bicep b/avm/res/web/static-site/main.bicep index 8676687f1ac..c4c51912ded 100644 --- a/avm/res/web/static-site/main.bicep +++ b/avm/res/web/static-site/main.bicep @@ -104,7 +104,7 @@ var builtInRoleNames = { 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( + 'Role Based Access Control Administrator': subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' ) diff --git a/avm/res/web/static-site/main.json b/avm/res/web/static-site/main.json index 81746c626a6..490b5bd6dff 100644 --- a/avm/res/web/static-site/main.json +++ b/avm/res/web/static-site/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15796777918776565235" + "templateHash": "9780314991768462863" }, "name": "Static Web Apps", "description": "This module deploys a Static Web App.", @@ -536,7 +536,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]", "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]" diff --git a/avm/res/web/static-site/version.json b/avm/res/web/static-site/version.json index ea4f3b6e679..21226dd43f3 100644 --- a/avm/res/web/static-site/version.json +++ b/avm/res/web/static-site/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.5", + "version": "0.6", "pathFilters": [ "./main.json" ] From e3b70587242c85aa14472f74aaec07775dffe5b9 Mon Sep 17 00:00:00 2001 From: "Menghua Chen (MSFT)" <111940661+Menghua1@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:15:46 +0800 Subject: [PATCH 05/46] feat: Add new ptn modules `azd/insights-dashboard`. (#3175) ## Description Fixes https://github.com/Azure/Azure-Verified-Modules/issues/1206. ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.azd.insights-dashboard](https://github.com/Menghua1/bicep-registry-modules/actions/workflows/avm.ptn.azd.insights-dashboard.yml/badge.svg?branch=ptn%2Fazd%2Finsights-dashboard)](https://github.com/Menghua1/bicep-registry-modules/actions/workflows/avm.ptn.azd.insights-dashboard.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings @jongio for notification. --------- Co-authored-by: Alexander Sehr --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .../avm.ptn.azd.insights-dashboard.yml | 88 + avm/ptn/azd/insights-dashboard/README.md | 272 ++ avm/ptn/azd/insights-dashboard/main.bicep | 111 + avm/ptn/azd/insights-dashboard/main.json | 2384 +++++++++++++++++ .../applicationinsights-dashboard.bicep | 1267 +++++++++ .../tests/e2e/defaults/dependencies.bicep | 30 + .../tests/e2e/defaults/main.test.bicep | 57 + .../tests/e2e/max/dependencies.bicep | 30 + .../tests/e2e/max/main.test.bicep | 60 + avm/ptn/azd/insights-dashboard/version.json | 7 + 12 files changed, 4308 insertions(+) create mode 100644 .github/workflows/avm.ptn.azd.insights-dashboard.yml create mode 100644 avm/ptn/azd/insights-dashboard/README.md create mode 100644 avm/ptn/azd/insights-dashboard/main.bicep create mode 100644 avm/ptn/azd/insights-dashboard/main.json create mode 100644 avm/ptn/azd/insights-dashboard/modules/applicationinsights-dashboard.bicep create mode 100644 avm/ptn/azd/insights-dashboard/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/ptn/azd/insights-dashboard/tests/e2e/defaults/main.test.bicep create mode 100644 avm/ptn/azd/insights-dashboard/tests/e2e/max/dependencies.bicep create mode 100644 avm/ptn/azd/insights-dashboard/tests/e2e/max/main.test.bicep create mode 100644 avm/ptn/azd/insights-dashboard/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6614ebf2086..c294eb5bc4a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -13,6 +13,7 @@ #/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep @Azure/avm-module-reviewers-bicep #/avm/ptn/avd-lza/session-hosts/ @Azure/avm-ptn-avd-lza-sessionhosts-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/azd/container-apps/ @Azure/avm-ptn-azd-containerapps-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/ptn/azd/insights-dashboard/ @Azure/avm-ptn-azd-insightsdashboard-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/deployment-script/import-image-to-acr/ @Azure/avm-ptn-deploymentscript-importimagetoacr-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/dev-ops/cicd-agents-and-runners/ @Azure/avm-ptn-devops-cicdagentsandrunners-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/finops-toolkit/finops-hub/ @Azure/avm-ptn-finopstoolkit-finopshub-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 51cfae077b4..bc5bdfbed51 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -48,6 +48,7 @@ body: # - "avm/ptn/avd-lza/networking" # - "avm/ptn/avd-lza/session-hosts" - "avm/ptn/azd/container-apps" + - "avm/ptn/azd/insights-dashboard" - "avm/ptn/deployment-script/import-image-to-acr" - "avm/ptn/dev-ops/cicd-agents-and-runners" - "avm/ptn/finops-toolkit/finops-hub" diff --git a/.github/workflows/avm.ptn.azd.insights-dashboard.yml b/.github/workflows/avm.ptn.azd.insights-dashboard.yml new file mode 100644 index 00000000000..a1df3a89d75 --- /dev/null +++ b/.github/workflows/avm.ptn.azd.insights-dashboard.yml @@ -0,0 +1,88 @@ +name: "avm.ptn.azd.insights-dashboard" + +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" + - ".github/workflows/avm.ptn.azd.insights-dashboard" + - "avm/ptn/azd/insights-dashboard/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/ptn/azd/insights-dashboard" + workflowPath: ".github/workflows/avm.ptn.azd.insights-dashboard.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 \ No newline at end of file diff --git a/avm/ptn/azd/insights-dashboard/README.md b/avm/ptn/azd/insights-dashboard/README.md new file mode 100644 index 00000000000..92e51c5d577 --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/README.md @@ -0,0 +1,272 @@ +# Application Insights Components `[Azd/InsightsDashboard]` + +Creates an Application Insights instance based on an existing Log Analytics workspace. + +**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Insights/components` | [2020-02-02](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2020-02-02/components) | +| `microsoft.insights/components/linkedStorageAccounts` | [2020-03-01-preview](https://learn.microsoft.com/en-us/azure/templates/microsoft.insights/2020-03-01-preview/components/linkedStorageAccounts) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Portal/dashboards` | [2020-09-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Portal/2020-09-01-preview/dashboards) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/ptn/azd/insights-dashboard:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +
+ +via Bicep module + +```bicep +module insightsDashboard 'br/public:avm/ptn/azd/insights-dashboard:' = { + name: 'insightsDashboardDeployment' + params: { + // Required parameters + logAnalyticsWorkspaceResourceId: '' + name: 'aidmin001' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "logAnalyticsWorkspaceResourceId": { + "value": "" + }, + "name": { + "value": "aidmin001" + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using large parameter set_ + +This instance deploys the module using large parameters. + + +

+ +via Bicep module + +```bicep +module insightsDashboard 'br/public:avm/ptn/azd/insights-dashboard:' = { + name: 'insightsDashboardDeployment' + params: { + // Required parameters + logAnalyticsWorkspaceResourceId: '' + name: 'icmax001' + // Non-required parameters + applicationType: 'web' + dashboardName: 'icmaxdb001' + kind: 'web' + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "logAnalyticsWorkspaceResourceId": { + "value": "" + }, + "name": { + "value": "icmax001" + }, + // Non-required parameters + "applicationType": { + "value": "web" + }, + "dashboardName": { + "value": "icmaxdb001" + }, + "kind": { + "value": "web" + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`logAnalyticsWorkspaceResourceId`](#parameter-loganalyticsworkspaceresourceid) | string | The resource ID of the loganalytics workspace. | +| [`name`](#parameter-name) | string | The resource insights components name. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`applicationType`](#parameter-applicationtype) | string | Application type. | +| [`dashboardName`](#parameter-dashboardname) | string | The resource portal dashboards name. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`kind`](#parameter-kind) | string | The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone. | +| [`location`](#parameter-location) | string | Location for all Resources. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | + +### Parameter: `logAnalyticsWorkspaceResourceId` + +The resource ID of the loganalytics workspace. + +- Required: Yes +- Type: string + +### Parameter: `name` + +The resource insights components name. + +- Required: Yes +- Type: string + +### Parameter: `applicationType` + +Application type. + +- Required: No +- Type: string +- Default: `'web'` +- Allowed: + ```Bicep + [ + 'other' + 'web' + ] + ``` + +### Parameter: `dashboardName` + +The resource portal dashboards name. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `kind` + +The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone. + +- Required: No +- Type: string +- Default: `'web'` + +### Parameter: `location` + +Location for all Resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object +- Example: + ```Bicep + { + "key1": "value1" + "key2": "value2" + } + ``` + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `applicationInsightsConnectionString` | string | The connection string of the application insights. | +| `applicationInsightsInstrumentationKey` | string | The instrumentation key of the application insights. | +| `applicationInsightsName` | string | The name of the application insights. | +| `applicationInsightsResourceId` | string | The resource ID of the application insights. | +| `dashboardName` | string | The resource name of the dashboard. | +| `dashboardResourceId` | string | The resource ID of the dashboard. | +| `resourceGroupName` | string | The resource group the application insights components were deployed into. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/res/insights/component:0.4.1` | Remote reference | +| `br/public:avm/res/portal/dashboard:0.1.0` | Remote reference | + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/azd/insights-dashboard/main.bicep b/avm/ptn/azd/insights-dashboard/main.bicep new file mode 100644 index 00000000000..911a2471a7c --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/main.bicep @@ -0,0 +1,111 @@ +metadata name = 'Application Insights Components' +metadata description = '''Creates an Application Insights instance based on an existing Log Analytics workspace. + +**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case.''' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The resource insights components name.') +param name string + +@description('Optional. The resource portal dashboards name.') +param dashboardName string = '' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Required. The resource ID of the loganalytics workspace.') +param logAnalyticsWorkspaceResourceId string + +@description('Optional. The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone.') +param kind string = 'web' + +@description('Optional. Application type.') +@allowed([ + 'web' + 'other' +]) +param applicationType string = 'web' + +@description('Optional. Tags of the resource.') +@metadata({ + example: ''' + { + "key1": "value1" + "key2": "value2" + } + ''' +}) +param tags object? + +// ============== // +// Resources // +// ============== // + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.ptn.azd-insightsdashboard.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + 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' + } + } + } + } +} + +module applicationInsights 'br/public:avm/res/insights/component:0.4.1' = { + name: '${uniqueString(deployment().name, location)}-appinsights' + params: { + name: name + location: location + tags: tags + kind: kind + applicationType: applicationType + workspaceResourceId: logAnalyticsWorkspaceResourceId + } +} + +module applicationInsightsDashboard 'modules/applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { + name: 'application-insights-dashboard' + params: { + name: dashboardName + location: location + applicationInsightsName: applicationInsights.outputs.name + applicationInsightsResourceId: applicationInsights.outputs.resourceId + } +} + +// ============ // +// Outputs // +// ============ // + +@description('The resource group the application insights components were deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the application insights.') +output applicationInsightsName string = applicationInsights.outputs.name + +@description('The resource name of the dashboard.') +output dashboardName string = !empty(dashboardName) ? applicationInsightsDashboard.outputs.dashboardName : '' + +@description('The resource ID of the application insights.') +output applicationInsightsResourceId string = applicationInsights.outputs.resourceId + +@description('The resource ID of the dashboard.') +output dashboardResourceId string = !empty(dashboardName) ? applicationInsightsDashboard.outputs.dashboardResourceId : '' + +@description('The connection string of the application insights.') +output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString + +@description('The instrumentation key of the application insights.') +output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey diff --git a/avm/ptn/azd/insights-dashboard/main.json b/avm/ptn/azd/insights-dashboard/main.json new file mode 100644 index 00000000000..ed2893f7d0f --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/main.json @@ -0,0 +1,2384 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "16185324363882257699" + }, + "name": "Application Insights Components", + "description": "Creates an Application Insights instance based on an existing Log Analytics workspace.\n\n**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The resource insights components name." + } + }, + "dashboardName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource portal dashboards name." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the loganalytics workspace." + } + }, + "kind": { + "type": "string", + "defaultValue": "web", + "metadata": { + "description": "Optional. The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone." + } + }, + "applicationType": { + "type": "string", + "defaultValue": "web", + "allowedValues": [ + "web", + "other" + ], + "metadata": { + "description": "Optional. Application type." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "example": " {\n \"key1\": \"value1\"\n \"key2\": \"value2\"\n }\n ", + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.ptn.azd-insightsdashboard.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "applicationInsights": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-appinsights', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "kind": { + "value": "[parameters('kind')]" + }, + "applicationType": { + "value": "[parameters('applicationType')]" + }, + "workspaceResourceId": { + "value": "[parameters('logAnalyticsWorkspaceResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "10653241142071426932" + }, + "name": "Application Insights", + "description": "This component deploys an Application Insights instance.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Application Insights." + } + }, + "applicationType": { + "type": "string", + "defaultValue": "web", + "allowedValues": [ + "web", + "other" + ], + "metadata": { + "description": "Optional. Application type." + } + }, + "workspaceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the log analytics workspace which the data will be ingested to. This property is required to create an application with this API version. Applications from older versions will not have this property." + } + }, + "disableIpMasking": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Disable IP masking. Default value is set to true." + } + }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Disable Non-AAD based Auth. Default value is set to false." + } + }, + "forceCustomerStorageForProfiler": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Force users to create their own storage account for profiler and debugger." + } + }, + "linkedStorageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Linked storage account resource ID." + } + }, + "publicNetworkAccessForIngestion": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The network access type for accessing Application Insights ingestion. - Enabled or Disabled." + } + }, + "publicNetworkAccessForQuery": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The network access type for accessing Application Insights query. - Enabled or Disabled." + } + }, + "retentionInDays": { + "type": "int", + "defaultValue": 365, + "allowedValues": [ + 30, + 60, + 90, + 120, + 180, + 270, + 365, + 550, + 730 + ], + "metadata": { + "description": "Optional. Retention period in days." + } + }, + "samplingPercentage": { + "type": "int", + "defaultValue": 100, + "minValue": 0, + "maxValue": 100, + "metadata": { + "description": "Optional. Percentage of the data produced by the application being monitored that is being sampled for Application Insights telemetry." + } + }, + "kind": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The kind of application that this component refers to, used to customize UI. This value is a freeform string, values should typically be one of the following: web, ios, other, store, java, phone." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", + "Monitoring Metrics Publisher": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')]", + "Application Insights Component Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ae349356-3a1b-4a5e-921d-050484c6347e')]", + "Application Insights Snapshot Debugger": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '08954f03-6346-4c2e-81c0-ec3a5cfae23b')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.insights-component.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "appInsights": { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", + "properties": { + "Application_Type": "[parameters('applicationType')]", + "DisableIpMasking": "[parameters('disableIpMasking')]", + "DisableLocalAuth": "[parameters('disableLocalAuth')]", + "ForceCustomerStorageForProfiler": "[parameters('forceCustomerStorageForProfiler')]", + "WorkspaceResourceId": "[parameters('workspaceResourceId')]", + "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]", + "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]", + "RetentionInDays": "[parameters('retentionInDays')]", + "SamplingPercentage": "[parameters('samplingPercentage')]" + } + }, + "appInsights_roleAssignments": { + "copy": { + "name": "appInsights_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Insights/components/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Insights/components', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "appInsights" + ] + }, + "appInsights_diagnosticSettings": { + "copy": { + "name": "appInsights_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Insights/components/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "appInsights" + ] + }, + "linkedStorageAccount": { + "condition": "[not(empty(parameters('linkedStorageAccountResourceId')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-appInsights-linkedStorageAccount', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "appInsightsName": { + "value": "[parameters('name')]" + }, + "storageAccountResourceId": { + "value": "[parameters('linkedStorageAccountResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "216781367921725873" + }, + "name": "Application Insights Linked Storage Account", + "description": "This component deploys an Application Insights Linked Storage Account.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "appInsightsName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Application Insights instance. Required if the template is used in a standalone deployment." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Linked storage account resource ID." + } + } + }, + "resources": [ + { + "type": "microsoft.insights/components/linkedStorageAccounts", + "apiVersion": "2020-03-01-preview", + "name": "[format('{0}/{1}', parameters('appInsightsName'), 'ServiceProfiler')]", + "properties": { + "linkedStorageAccount": "[parameters('storageAccountResourceId')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Linked Storage Account." + }, + "value": "ServiceProfiler" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Linked Storage Account." + }, + "value": "[resourceId('microsoft.insights/components/linkedStorageAccounts', parameters('appInsightsName'), 'ServiceProfiler')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the agent pool was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "appInsights" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the application insights component." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the application insights component." + }, + "value": "[resourceId('Microsoft.Insights/components', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the application insights component was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "applicationId": { + "type": "string", + "metadata": { + "description": "The application ID of the application insights component." + }, + "value": "[reference('appInsights').AppId]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('appInsights', '2020-02-02', 'full').location]" + }, + "instrumentationKey": { + "type": "string", + "metadata": { + "description": "Application Insights Instrumentation key. A read-only value that applications can use to identify the destination for all telemetry sent to Azure Application Insights. This value will be supplied upon construction of each new Application Insights component." + }, + "value": "[reference('appInsights').InstrumentationKey]" + }, + "connectionString": { + "type": "string", + "metadata": { + "description": "Application Insights Connection String." + }, + "value": "[reference('appInsights').ConnectionString]" + } + } + } + } + }, + "applicationInsightsDashboard": { + "condition": "[not(empty(parameters('dashboardName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "application-insights-dashboard", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('dashboardName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "applicationInsightsName": { + "value": "[reference('applicationInsights').outputs.name.value]" + }, + "applicationInsightsResourceId": { + "value": "[reference('applicationInsights').outputs.resourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "12382758204242351890" + }, + "name": "Azure Portal Dashboard", + "description": "Creates a dashboard for an Application Insights instance.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The portal dashboard name." + } + }, + "applicationInsightsName": { + "type": "string", + "metadata": { + "description": "Required. The resource insights components name." + } + }, + "applicationInsightsResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource insights components ID." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "example": " {\n \"key1\": \"value1\"\n \"key2\": \"value2\"\n }\n ", + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "dashboard": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "dashboard-deployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "lenses": { + "value": [ + { + "order": 0, + "parts": [ + { + "position": { + "x": 0, + "y": 0, + "colSpan": 2, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "id", + "value": "[parameters('applicationInsightsResourceId')]" + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart", + "asset": { + "idInputName": "id", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "overview" + } + }, + { + "position": { + "x": 2, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "ProactiveDetection" + } + }, + { + "position": { + "x": 3, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "ResourceId", + "value": "[parameters('applicationInsightsResourceId')]" + } + ], + "type": "Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:20:33.345Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 5, + "y": 0, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-08T18:47:35.237Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "ConfigurationId", + "value": "78ce933e-e864-4b05-a27b-71fd55a6afad" + } + ], + "type": "Extension/AppInsightsExtension/PartType/AppMapButtonPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 0, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Usage", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 3, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "endTime": null, + "createdTime": "2018-05-04T01:22:35.782Z", + "isInitialTime": true, + "grain": 1, + "useDashboardTimeRange": false + } + } + ], + "type": "Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + } + } + }, + { + "position": { + "x": 4, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Reliability", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 7, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[parameters('applicationInsightsResourceId')]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:42:40.072Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "failures" + } + }, + { + "position": { + "x": 8, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Responsiveness\r\n", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 11, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ResourceId", + "value": "[parameters('applicationInsightsResourceId')]" + }, + { + "name": "DataModel", + "value": { + "version": "1.0.0", + "timeContext": { + "durationMs": 86400000, + "createdTime": "2018-05-04T23:43:37.804Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + "isOptional": true + }, + { + "name": "ConfigurationId", + "value": "2a8ede4f-2bee-4b9c-aed9-2db0e8a01865", + "isOptional": true + } + ], + "type": "Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart", + "isAdapter": true, + "asset": { + "idInputName": "ResourceId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "performance" + } + }, + { + "position": { + "x": 12, + "y": 1, + "colSpan": 3, + "rowSpan": 1 + }, + "metadata": { + "inputs": [], + "type": "Extension/HubsExtension/PartType/MarkdownPart", + "settings": { + "content": { + "settings": { + "content": "# Browser", + "title": "", + "subtitle": "" + } + } + } + } + }, + { + "position": { + "x": 15, + "y": 1, + "colSpan": 1, + "rowSpan": 1 + }, + "metadata": { + "inputs": [ + { + "name": "ComponentId", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "MetricsExplorerJsonDefinitionId", + "value": "BrowserPerformanceTimelineMetrics" + }, + { + "name": "TimeContext", + "value": { + "durationMs": 86400000, + "createdTime": "2018-05-08T12:16:27.534Z", + "isInitialTime": false, + "grain": 1, + "useDashboardTimeRange": false + } + }, + { + "name": "CurrentFilter", + "value": { + "eventTypes": [ + 4, + 1, + 3, + 5, + 2, + 6, + 13 + ], + "typeFacets": {}, + "isPermissive": false + } + }, + { + "name": "id", + "value": { + "Name": "[parameters('applicationInsightsName')]", + "SubscriptionId": "[subscription().subscriptionId]", + "ResourceGroup": "[resourceGroup().name]" + } + }, + { + "name": "Version", + "value": "1.0" + } + ], + "type": "Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart", + "asset": { + "idInputName": "ComponentId", + "type": "ApplicationInsights" + }, + "defaultMenuItemId": "browser" + } + }, + { + "position": { + "x": 0, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "sessions/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Sessions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "users/count", + "aggregationType": 5, + "namespace": "microsoft.insights/components/kusto", + "metricVisualization": { + "displayName": "Users", + "color": "#7E58FF" + } + } + ], + "title": "Unique sessions and users", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[parameters('applicationInsightsResourceId')]", + "menuid": "segmentationUsers" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "requests/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Failed requests", + "color": "#EC008C" + } + } + ], + "title": "Failed requests", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[parameters('applicationInsightsResourceId')]", + "menuid": "failures" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "requests/duration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server response time", + "color": "#00BCF2" + } + } + ], + "title": "Server response time", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[parameters('applicationInsightsResourceId')]", + "menuid": "performance" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 2, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "browserTimings/networkDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Page load network connect time", + "color": "#7E58FF" + } + }, + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "browserTimings/processingDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Client processing time", + "color": "#44F1C8" + } + }, + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "browserTimings/sendDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Send request time", + "color": "#EB9371" + } + }, + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "browserTimings/receiveDuration", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Receiving response time", + "color": "#0672F1" + } + } + ], + "title": "Average page load time breakdown", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "availabilityResults/availabilityPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability", + "color": "#47BDF5" + } + } + ], + "title": "Average availability", + "visualization": { + "chartType": 3, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + }, + "openBladeOnClick": { + "openBlade": true, + "destinationBlade": { + "extensionName": "HubsExtension", + "bladeName": "ResourceMenuBlade", + "parameters": { + "id": "[parameters('applicationInsightsResourceId')]", + "menuid": "availability" + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "exceptions/server", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Server exceptions", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "dependencies/failed", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Dependency failures", + "color": "#7E58FF" + } + } + ], + "title": "Server exceptions and Dependency failures", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "performanceCounters/processorCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Processor time", + "color": "#47BDF5" + } + }, + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "performanceCounters/processCpuPercentage", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process CPU", + "color": "#7E58FF" + } + } + ], + "title": "Average processor and process CPU utilization", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 12, + "y": 5, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "exceptions/browser", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Browser exceptions", + "color": "#47BDF5" + } + } + ], + "title": "Browser exceptions", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 0, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "availabilityResults/count", + "aggregationType": 7, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Availability test results count", + "color": "#47BDF5" + } + } + ], + "title": "Availability test results count", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 4, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "performanceCounters/processIOBytesPerSecond", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Process IO rate", + "color": "#47BDF5" + } + } + ], + "title": "Average process I/O rate", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + }, + { + "position": { + "x": 8, + "y": 8, + "colSpan": 4, + "rowSpan": 3 + }, + "metadata": { + "inputs": [ + { + "name": "options", + "value": { + "chart": { + "metrics": [ + { + "resourceMetadata": { + "id": "[parameters('applicationInsightsResourceId')]" + }, + "name": "performanceCounters/memoryAvailableBytes", + "aggregationType": 4, + "namespace": "microsoft.insights/components", + "metricVisualization": { + "displayName": "Available memory", + "color": "#47BDF5" + } + } + ], + "title": "Average available memory", + "visualization": { + "chartType": 2, + "legendVisualization": { + "isVisible": true, + "position": 2, + "hideSubtitle": false + }, + "axisVisualization": { + "x": { + "isVisible": true, + "axisType": 2 + }, + "y": { + "isVisible": true, + "axisType": 1 + } + } + } + } + } + }, + { + "name": "sharedTimeRange", + "isOptional": true + } + ], + "type": "Extension/HubsExtension/PartType/MonitorChartPart", + "settings": {} + } + } + ] + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "12676032921679464791" + }, + "name": "Portal Dashboards", + "description": "This module deploys a Portal Dashboard.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the dashboard to create." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "lenses": { + "type": "array", + "items": { + "type": "object" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. The dashboard lenses." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The dashboard metadata." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.portal-dashboard.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "dashboard": { + "type": "Microsoft.Portal/dashboards", + "apiVersion": "2020-09-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "lenses": "[parameters('lenses')]", + "metadata": "[parameters('metadata')]" + } + }, + "dashboard_roleAssignments": { + "copy": { + "name": "dashboard_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Portal/dashboards/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Portal/dashboards', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "dashboard" + ] + }, + "dashboard_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Portal/dashboards/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "dashboard" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the dashboard." + }, + "value": "[resourceId('Microsoft.Portal/dashboards', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the dashboard was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the dashboard." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the dashboard was deployed into." + }, + "value": "[reference('dashboard', '2020-09-01-preview', 'full').location]" + } + } + } + } + } + }, + "outputs": { + "dashboardResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the dashboard." + }, + "value": "[reference('dashboard').outputs.resourceId.value]" + }, + "dashboardName": { + "type": "string", + "metadata": { + "description": "The resource name of the dashboard." + }, + "value": "[reference('dashboard').outputs.name.value]" + } + } + } + }, + "dependsOn": [ + "applicationInsights" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the application insights components were deployed into." + }, + "value": "[resourceGroup().name]" + }, + "applicationInsightsName": { + "type": "string", + "metadata": { + "description": "The name of the application insights." + }, + "value": "[reference('applicationInsights').outputs.name.value]" + }, + "dashboardName": { + "type": "string", + "metadata": { + "description": "The resource name of the dashboard." + }, + "value": "[if(not(empty(parameters('dashboardName'))), reference('applicationInsightsDashboard').outputs.dashboardName.value, '')]" + }, + "applicationInsightsResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the application insights." + }, + "value": "[reference('applicationInsights').outputs.resourceId.value]" + }, + "dashboardResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the dashboard." + }, + "value": "[if(not(empty(parameters('dashboardName'))), reference('applicationInsightsDashboard').outputs.dashboardResourceId.value, '')]" + }, + "applicationInsightsConnectionString": { + "type": "string", + "metadata": { + "description": "The connection string of the application insights." + }, + "value": "[reference('applicationInsights').outputs.connectionString.value]" + }, + "applicationInsightsInstrumentationKey": { + "type": "string", + "metadata": { + "description": "The instrumentation key of the application insights." + }, + "value": "[reference('applicationInsights').outputs.instrumentationKey.value]" + } + } +} \ No newline at end of file diff --git a/avm/ptn/azd/insights-dashboard/modules/applicationinsights-dashboard.bicep b/avm/ptn/azd/insights-dashboard/modules/applicationinsights-dashboard.bicep new file mode 100644 index 00000000000..4a08622f499 --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/modules/applicationinsights-dashboard.bicep @@ -0,0 +1,1267 @@ +metadata name = 'Azure Portal Dashboard' +metadata description = 'Creates a dashboard for an Application Insights instance.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The portal dashboard name.') +param name string + +@description('Required. The resource insights components name.') +param applicationInsightsName string + +@description('Required. The resource insights components ID.') +param applicationInsightsResourceId string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Tags of the resource.') +@metadata({ + example: ''' + { + "key1": "value1" + "key2": "value2" + } + ''' +}) +param tags object? + +// ============== // +// Resources // +// ============== // + +module dashboard 'br/public:avm/res/portal/dashboard:0.1.0' = { + name: 'dashboard-deployment' + params: { + name: name + location: location + tags: tags + lenses: [ + { + order: 0 + parts: [ + { + position: { + x: 0 + y: 0 + colSpan: 2 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'id' + value: applicationInsightsResourceId + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AspNetOverviewPinnedPart' + asset: { + idInputName: 'id' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'overview' + } + } + { + position: { + x: 2 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/ProactiveDetectionAsyncPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'ProactiveDetection' + } + } + { + position: { + x: 3 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'ResourceId' + value: applicationInsightsResourceId + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/QuickPulseButtonSmallPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:20:33.345Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AvailabilityNavButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 5 + y: 0 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-08T18:47:35.237Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'ConfigurationId' + value: '78ce933e-e864-4b05-a27b-71fd55a6afad' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/AppMapButtonPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 0 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Usage' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 3 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + endTime: null + createdTime: '2018-05-04T01:22:35.782Z' + isInitialTime: true + grain: 1 + useDashboardTimeRange: false + } + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/UsageUsersOverviewPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + } + } + { + position: { + x: 4 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Reliability' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 7 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: applicationInsightsResourceId + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:42:40.072Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '8a02f7bf-ac0f-40e1-afe9-f0e72cfee77f' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladeFailuresPinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'failures' + } + } + { + position: { + x: 8 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Responsiveness\r\n' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 11 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ResourceId' + value: applicationInsightsResourceId + } + { + name: 'DataModel' + value: { + version: '1.0.0' + timeContext: { + durationMs: 86400000 + createdTime: '2018-05-04T23:43:37.804Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + isOptional: true + } + { + name: 'ConfigurationId' + value: '2a8ede4f-2bee-4b9c-aed9-2db0e8a01865' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/CuratedBladePerformancePinnedPart' + isAdapter: true + asset: { + idInputName: 'ResourceId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'performance' + } + } + { + position: { + x: 12 + y: 1 + colSpan: 3 + rowSpan: 1 + } + metadata: { + inputs: [] + type: 'Extension/HubsExtension/PartType/MarkdownPart' + settings: { + content: { + settings: { + content: '# Browser' + title: '' + subtitle: '' + } + } + } + } + } + { + position: { + x: 15 + y: 1 + colSpan: 1 + rowSpan: 1 + } + metadata: { + inputs: [ + { + name: 'ComponentId' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'MetricsExplorerJsonDefinitionId' + value: 'BrowserPerformanceTimelineMetrics' + } + { + name: 'TimeContext' + value: { + durationMs: 86400000 + createdTime: '2018-05-08T12:16:27.534Z' + isInitialTime: false + grain: 1 + useDashboardTimeRange: false + } + } + { + name: 'CurrentFilter' + value: { + eventTypes: [ + 4 + 1 + 3 + 5 + 2 + 6 + 13 + ] + typeFacets: {} + isPermissive: false + } + } + { + name: 'id' + value: { + Name: applicationInsightsName + SubscriptionId: subscription().subscriptionId + ResourceGroup: resourceGroup().name + } + } + { + name: 'Version' + value: '1.0' + } + ] + #disable-next-line BCP036 + type: 'Extension/AppInsightsExtension/PartType/MetricsExplorerBladePinnedPart' + asset: { + idInputName: 'ComponentId' + type: 'ApplicationInsights' + } + defaultMenuItemId: 'browser' + } + } + { + position: { + x: 0 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'sessions/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Sessions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'users/count' + aggregationType: 5 + namespace: 'microsoft.insights/components/kusto' + metricVisualization: { + displayName: 'Users' + color: '#7E58FF' + } + } + ] + title: 'Unique sessions and users' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: applicationInsightsResourceId + menuid: 'segmentationUsers' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'requests/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Failed requests' + color: '#EC008C' + } + } + ] + title: 'Failed requests' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: applicationInsightsResourceId + menuid: 'failures' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'requests/duration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server response time' + color: '#00BCF2' + } + } + ] + title: 'Server response time' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: applicationInsightsResourceId + menuid: 'performance' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 2 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'browserTimings/networkDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Page load network connect time' + color: '#7E58FF' + } + } + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'browserTimings/processingDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Client processing time' + color: '#44F1C8' + } + } + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'browserTimings/sendDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Send request time' + color: '#EB9371' + } + } + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'browserTimings/receiveDuration' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Receiving response time' + color: '#0672F1' + } + } + ] + title: 'Average page load time breakdown' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'availabilityResults/availabilityPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability' + color: '#47BDF5' + } + } + ] + title: 'Average availability' + visualization: { + chartType: 3 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + openBladeOnClick: { + openBlade: true + destinationBlade: { + extensionName: 'HubsExtension' + bladeName: 'ResourceMenuBlade' + parameters: { + id: applicationInsightsResourceId + menuid: 'availability' + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'exceptions/server' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Server exceptions' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'dependencies/failed' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Dependency failures' + color: '#7E58FF' + } + } + ] + title: 'Server exceptions and Dependency failures' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'performanceCounters/processorCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Processor time' + color: '#47BDF5' + } + } + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'performanceCounters/processCpuPercentage' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process CPU' + color: '#7E58FF' + } + } + ] + title: 'Average processor and process CPU utilization' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 12 + y: 5 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'exceptions/browser' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Browser exceptions' + color: '#47BDF5' + } + } + ] + title: 'Browser exceptions' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 0 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'availabilityResults/count' + aggregationType: 7 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Availability test results count' + color: '#47BDF5' + } + } + ] + title: 'Availability test results count' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 4 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'performanceCounters/processIOBytesPerSecond' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Process IO rate' + color: '#47BDF5' + } + } + ] + title: 'Average process I/O rate' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + { + position: { + x: 8 + y: 8 + colSpan: 4 + rowSpan: 3 + } + metadata: { + inputs: [ + { + name: 'options' + value: { + chart: { + metrics: [ + { + resourceMetadata: { + id: applicationInsightsResourceId + } + name: 'performanceCounters/memoryAvailableBytes' + aggregationType: 4 + namespace: 'microsoft.insights/components' + metricVisualization: { + displayName: 'Available memory' + color: '#47BDF5' + } + } + ] + title: 'Average available memory' + visualization: { + chartType: 2 + legendVisualization: { + isVisible: true + position: 2 + hideSubtitle: false + } + axisVisualization: { + x: { + isVisible: true + axisType: 2 + } + y: { + isVisible: true + axisType: 1 + } + } + } + } + } + } + { + name: 'sharedTimeRange' + isOptional: true + } + ] + #disable-next-line BCP036 + type: 'Extension/HubsExtension/PartType/MonitorChartPart' + settings: {} + } + } + ] + } + ] + } +} + +// ============ // +// Outputs // +// ============ // + +@description('The resource ID of the dashboard.') +output dashboardResourceId string = dashboard.outputs.resourceId + +@description('The resource name of the dashboard.') +output dashboardName string = dashboard.outputs.name diff --git a/avm/ptn/azd/insights-dashboard/tests/e2e/defaults/dependencies.bicep b/avm/ptn/azd/insights-dashboard/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 00000000000..3781246fcb3 --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,30 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The resource operational insights workspaces name.') +param name string + +@description('Optional. Tags of the resource.') +@metadata({ + example: ''' + { + "key1": "value1" + "key2": "value2" + } + ''' +}) +param tags object? + +module logAnalytics 'br/public:avm/res/operational-insights/workspace:0.5.0' = { + name: '${uniqueString(deployment().name, location)}-loganalytics' + params: { + name: name + location: location + tags: tags + dataRetention: 30 + skuName: 'PerGB2018' + } +} + +@description('The resource ID of the loganalytics workspace.') +output logAnalyticsWorkspaceResourceId string = logAnalytics.outputs.resourceId diff --git a/avm/ptn/azd/insights-dashboard/tests/e2e/defaults/main.test.bicep b/avm/ptn/azd/insights-dashboard/tests/e2e/defaults/main.test.bicep new file mode 100644 index 00000000000..b53b750d22f --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,57 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-azd-insights-dashboard-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'aidmin' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module dependencies 'dependencies.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-test-dependencies' + scope: resourceGroup + params: { + name: 'dep-${namePrefix}-law-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +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 + logAnalyticsWorkspaceResourceId: dependencies.outputs.logAnalyticsWorkspaceResourceId + } + } +] diff --git a/avm/ptn/azd/insights-dashboard/tests/e2e/max/dependencies.bicep b/avm/ptn/azd/insights-dashboard/tests/e2e/max/dependencies.bicep new file mode 100644 index 00000000000..3781246fcb3 --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/tests/e2e/max/dependencies.bicep @@ -0,0 +1,30 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The resource operational insights workspaces name.') +param name string + +@description('Optional. Tags of the resource.') +@metadata({ + example: ''' + { + "key1": "value1" + "key2": "value2" + } + ''' +}) +param tags object? + +module logAnalytics 'br/public:avm/res/operational-insights/workspace:0.5.0' = { + name: '${uniqueString(deployment().name, location)}-loganalytics' + params: { + name: name + location: location + tags: tags + dataRetention: 30 + skuName: 'PerGB2018' + } +} + +@description('The resource ID of the loganalytics workspace.') +output logAnalyticsWorkspaceResourceId string = logAnalytics.outputs.resourceId diff --git a/avm/ptn/azd/insights-dashboard/tests/e2e/max/main.test.bicep b/avm/ptn/azd/insights-dashboard/tests/e2e/max/main.test.bicep new file mode 100644 index 00000000000..fd3f0fe2ec3 --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/tests/e2e/max/main.test.bicep @@ -0,0 +1,60 @@ +targetScope = 'subscription' + +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module using large parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-azd-insights-dashboard-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'icmax' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module dependencies 'dependencies.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-test-dependencies' + scope: resourceGroup + params: { + name: 'dep-${namePrefix}-law-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +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 + logAnalyticsWorkspaceResourceId: dependencies.outputs.logAnalyticsWorkspaceResourceId + dashboardName: '${namePrefix}${serviceShort}db001' + kind: 'web' + applicationType: 'web' + } + } +] diff --git a/avm/ptn/azd/insights-dashboard/version.json b/avm/ptn/azd/insights-dashboard/version.json new file mode 100644 index 00000000000..7245f148728 --- /dev/null +++ b/avm/ptn/azd/insights-dashboard/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] + } \ No newline at end of file From 36b45011bcb5a433eeed4b26fae94325f6dc137c Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Mon, 9 Sep 2024 18:21:39 +0200 Subject: [PATCH 06/46] feat: Enable the introduction of `utl` modules in CI & utilities (#2756) ## Description - Enable the introduction of `utl` modules in CI (e.g., testing) & utilities (e.g., readme generation) - Using https://github.com/Azure/Azure-Verified-Modules/issues/898 as the backdrop - Updated readme to deal with functions and enabled it to only add specific parts if they have content (like telemetry disclaimer only if there's telemetry) Special changes - Utility modules don't necessarily have a telemetry deployment as they are not actually deployed - We may not have 'usage examples' in the readme for utility modules if they only export functions and don't actually deploy anything Notes/changes to static tests - Any module that does not deploy any parameters is not tested for telemetry (parameter/deployment/condition/output) - Resource group output is only expected for modules that contain resource deployments (as opposed to all modules in the rg scope) - utl (like ptn) modules are not tested for the existence of a `waf-aligned` e2e test - utl (like ptn) modules are not tested for the existence of a `defaults` e2e test - e2e tests only require a `serviceShort` parameter if they deploy resources (as opposed to always) - Noticed that the test was duplicated, so I removed one - e2e tests only require a `namePrefix` parameter if they deploy resources (as opposed to always) - e2e tests only require a module deployment using the syntax `module testDeployment '../.*main.bicep' = ` if they deploy resources (as opposed to always) - e2e tests only require `-test-` in their test deployment name only if they deploy resources (as opposed to always) Removal changes - As the example utility module only exports functions, the removal logic had to be adapted to not only work if resources are deployed - The logic now differentiates in between 'I can not find a deployment' and 'I can find a deployment, but it has no resources linked' (before it was only the former) - Further, the logic was slightly altered to not give up as soon as no deployment is found, but instead try and find as many as possible and only throw an exception after the removal logic ran end to end. If interested, [this](https://github.com/AlexanderSehr/bicep-registry-modules/pull/6/files) is how modules would look like. TODO - [x] Find out why bicep build fails the example template - [x] Test CI with new utility module(s) - [x] Retest CI for res & ptn modules - [x] Run all custom utilities for res & ptn, as well as utl modules - [x] Remove `avm/utl/general/get-environment` function after all tests passed - [x] Remove `avm/utl/general/any-deployment-script` function after all tests passed Closes #2755 ## Pipeline Reference | Pipeline | | -------- | | [![avm.utl.general.any-deployment-script](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.any-deployment-script.yml/badge.svg)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.any-deployment-script.yml) (fails because the module isn't existing in AVM) | | [Successful deployment run](https://github.com/AlexanderSehr/bicep-registry-modules/actions/runs/10130797213/job/28012687558) | | [![avm.utl.general.get-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.get-environment.yml/badge.svg)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.get-environment.yml) | | [Successful deployment run](https://github.com/AlexanderSehr/bicep-registry-modules/actions/runs/10130869534) | [![avm.res.container-registry.registry](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml)

All workflows run | Pipeline | | -------- | [![avm.ptn.ai-platform.baseline](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml) [![avm.ptn.authorization.policy-assignment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.policy-assignment.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.policy-assignment.yml) [![avm.ptn.authorization.resource-role-assignment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.resource-role-assignment.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.resource-role-assignment.yml) [![avm.ptn.authorization.role-assignment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.authorization.role-assignment.yml) [![avm.ptn.deployment-script.import-image-to-acr](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml) [![avm.ptn.finops-toolkit.finops-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.finops-toolkit.finops-hub.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.finops-toolkit.finops-hub.yml) [![avm.ptn.lz.sub-vending](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.lz.sub-vending.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.lz.sub-vending.yml) [![avm.ptn.network.private-link-private-dns-zones](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.network.private-link-private-dns-zones.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.network.private-link-private-dns-zones.yml) [![avm.ptn.policy-insights.remediation](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.policy-insights.remediation.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.policy-insights.remediation.yml) [![avm.ptn.security.security-center](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.security.security-center.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.security.security-center.yml) [![avm.res.aad.domain-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.aad.domain-service.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.aad.domain-service.yml) [![avm.res.alerts-management.action-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.alerts-management.action-rule.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.alerts-management.action-rule.yml) [![avm.res.analysis-services.server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.analysis-services.server.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.analysis-services.server.yml) [![avm.res.api-management.service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml) [![avm.res.app-configuration.configuration-store](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app-configuration.configuration-store.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app-configuration.configuration-store.yml) [![avm.res.app.container-app](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml) [![avm.res.app.job](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.job.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.job.yml) [![avm.res.app.managed-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.managed-environment.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.managed-environment.yml) [![avm.res.automation.automation-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.automation.automation-account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.automation.automation-account.yml) [![avm.res.batch.batch-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.batch.batch-account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.batch.batch-account.yml) [![avm.res.cache.redis](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml) [![avm.res.cdn.profile](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cdn.profile.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cdn.profile.yml) [![avm.res.cognitive-services.account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml) [![avm.res.communication.communication-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.communication-service.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.communication-service.yml) [![avm.res.communication.email-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.email-service.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.email-service.yml) [![avm.res.compute.availability-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.availability-set.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.availability-set.yml) [![avm.res.compute.disk-encryption-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk-encryption-set.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk-encryption-set.yml) [![avm.res.compute.disk](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk.yml) [![avm.res.compute.gallery](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.gallery.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.gallery.yml) [![avm.res.compute.image](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.image.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.image.yml) [![avm.res.compute.proximity-placement-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.proximity-placement-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.proximity-placement-group.yml) [![avm.res.compute.ssh-public-key](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.ssh-public-key.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.ssh-public-key.yml) [![avm.res.compute.virtual-machine-scale-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine-scale-set.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine-scale-set.yml) [![avm.res.compute.virtual-machine](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml) [![avm.res.consumption.budget](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.consumption.budget.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.consumption.budget.yml) [![avm.res.container-instance.container-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-instance.container-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-instance.container-group.yml) [![avm.res.container-registry.registry](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml) [![avm.res.container-service.managed-cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) [![avm.res.data-factory.factory](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-factory.factory.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-factory.factory.yml) [![avm.res.data-protection.backup-vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-protection.backup-vault.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-protection.backup-vault.yml) [![avm.res.databricks.access-connector](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.access-connector.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.access-connector.yml) [![avm.res.databricks.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml) [![avm.res.db-for-my-sql.flexible-server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-my-sql.flexible-server.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-my-sql.flexible-server.yml) (unrelated, WAF-reliability tests failed) [![avm.res.db-for-postgre-sql.flexible-server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml) (unrelated, WAF-reliability tests failed) [![avm.res.desktop-virtualization.application-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.application-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.application-group.yml) [![avm.res.desktop-virtualization.host-pool](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.host-pool.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.host-pool.yml) (unrelated, WAF-reliability tests failed) [![avm.res.desktop-virtualization.scaling-plan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.scaling-plan.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.scaling-plan.yml) [![avm.res.desktop-virtualization.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.workspace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.workspace.yml) [![avm.res.dev-test-lab.lab](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml) [![avm.res.digital-twins.digital-twins-instance](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.digital-twins.digital-twins-instance.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.digital-twins.digital-twins-instance.yml) [![avm.res.document-db.database-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml) (unrelated, WAF-reliability tests failed) [![avm.res.event-grid.domain](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.domain.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.domain.yml) [![avm.res.event-grid.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.namespace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.namespace.yml) [![avm.res.event-grid.system-topic](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.system-topic.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.system-topic.yml) [![avm.res.event-grid.topic](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.topic.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.topic.yml) [![avm.res.event-hub.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-hub.namespace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-hub.namespace.yml) [![avm.res.health-bot.health-bot](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.health-bot.health-bot.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.health-bot.health-bot.yml) [![avm.res.healthcare-apis.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.healthcare-apis.workspace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.healthcare-apis.workspace.yml) [![avm.res.hybrid-compute.machine](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.hybrid-compute.machine.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.hybrid-compute.machine.yml) [![avm.res.insights.action-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.action-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.action-group.yml) [![avm.res.insights.activity-log-alert](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml) [![avm.res.insights.component](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.component.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.component.yml) [![avm.res.insights.data-collection-endpoint](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-endpoint.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-endpoint.yml) [![avm.res.insights.data-collection-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml) [![avm.res.insights.diagnostic-setting](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.diagnostic-setting.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.diagnostic-setting.yml) [![avm.res.insights.metric-alert](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.metric-alert.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.metric-alert.yml) [![avm.res.insights.private-link-scope](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml) [![avm.res.insights.scheduled-query-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.scheduled-query-rule.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.scheduled-query-rule.yml) [![avm.res.insights.webtest](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.webtest.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.webtest.yml) [![avm.res.key-vault.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) [![avm.res.kubernetes-configuration.extension](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.extension.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.extension.yml) [![avm.res.kubernetes-configuration.flux-configuration](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.flux-configuration.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.flux-configuration.yml) [![avm.res.kusto.cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kusto.cluster.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kusto.cluster.yml) [![avm.res.load-test-service.load-test](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml) [![avm.res.logic.workflow](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.logic.workflow.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.logic.workflow.yml) [![avm.res.machine-learning-services.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml) [![avm.res.maintenance.maintenance-configuration](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.maintenance.maintenance-configuration.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.maintenance.maintenance-configuration.yml) [![avm.res.managed-identity.user-assigned-identity](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml) [![avm.res.managed-services.registration-definition](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-services.registration-definition.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-services.registration-definition.yml) [![avm.res.management.management-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.management.management-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.management.management-group.yml) [![avm.res.net-app.net-app-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.net-app.net-app-account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.net-app.net-app-account.yml) [![avm.res.network.application-gateway-web-application-firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway-web-application-firewall-policy.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway-web-application-firewall-policy.yml) (unrelated, WAF-reliability tests failed) [![avm.res.network.application-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml) [![avm.res.network.application-security-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-security-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-security-group.yml) [![avm.res.network.azure-firewall](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.azure-firewall.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.azure-firewall.yml) [![avm.res.network.bastion-host](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.bastion-host.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.bastion-host.yml) [![avm.res.network.connection](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.connection.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.connection.yml) [![avm.res.network.ddos-protection-plan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ddos-protection-plan.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ddos-protection-plan.yml) [![avm.res.network.dns-forwarding-ruleset](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml) [![avm.res.network.dns-resolver](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-resolver.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-resolver.yml) [![avm.res.network.dns-zone](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-zone.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-zone.yml) [![avm.res.network.express-route-circuit](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml) [![avm.res.network.express-route-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml) [![avm.res.network.firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.firewall-policy.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.firewall-policy.yml) [![avm.res.network.front-door-web-application-firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door-web-application-firewall-policy.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door-web-application-firewall-policy.yml) [![avm.res.network.front-door](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml) [![avm.res.network.ip-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ip-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ip-group.yml) [![avm.res.network.load-balancer](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.load-balancer.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.load-balancer.yml) [![avm.res.network.local-network-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.local-network-gateway.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.local-network-gateway.yml) [![avm.res.network.nat-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml) [![avm.res.network.network-interface](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-interface.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-interface.yml) [![avm.res.network.network-manager](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-manager.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-manager.yml) [![avm.res.network.network-security-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-security-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-security-group.yml) [![avm.res.network.network-watcher](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-watcher.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-watcher.yml) [![avm.res.network.private-dns-zone](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-dns-zone.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-dns-zone.yml) [![avm.res.network.private-endpoint](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-endpoint.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-endpoint.yml) [![avm.res.network.private-link-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-link-service.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-link-service.yml) [![avm.res.network.public-ip-address](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml) [![avm.res.network.public-ip-prefix](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-prefix.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-prefix.yml) [![avm.res.network.route-table](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.route-table.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.route-table.yml) [![avm.res.network.service-endpoint-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml) [![avm.res.network.trafficmanagerprofile](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml) [![avm.res.network.virtual-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-hub.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-hub.yml) [![avm.res.network.virtual-network-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml) [![avm.res.network.virtual-network](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network.yml) [![avm.res.network.virtual-wan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-wan.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-wan.yml) [![avm.res.network.vpn-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-gateway.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-gateway.yml) [![avm.res.network.vpn-site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-site.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-site.yml) [![avm.res.operational-insights.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml) [![avm.res.operations-management.solution](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml) [![avm.res.portal.dashboard](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.portal.dashboard.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.portal.dashboard.yml) [![avm.res.power-bi-dedicated.capacity](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.power-bi-dedicated.capacity.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.power-bi-dedicated.capacity.yml) [![avm.res.purview.account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml) [![avm.res.recovery-services.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml) [![avm.res.relay.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml) [![avm.res.resource-graph.query](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml) [![avm.res.resources.deployment-script](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml) [![avm.res.resources.resource-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml) [![avm.res.search.search-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml) [![avm.res.service-bus.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml) [![avm.res.service-fabric.cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml) [![avm.res.signal-r-service.signal-r](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml) [![avm.res.signal-r-service.web-pub-sub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml) [![avm.res.sql.instance-pool](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml) [![avm.res.sql.managed-instance](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml) (unrelated, WAF-reliability tests failed) [![avm.res.sql.server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml) [![avm.res.storage.storage-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) [![avm.res.synapse.private-link-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml) [![avm.res.synapse.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml) [![avm.res.virtual-machine-images.image-template](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml) [![avm.res.web.connection](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml) [![avm.res.web.hosting-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.hosting-environment.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.hosting-environment.yml) (unrealted, WAF-reliablity tests failed) [![avm.res.web.serverfarm](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml) (unrealted, WAF-reliablity tests failed) [![avm.res.web.site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.site.yml) [![avm.res.web.static-site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml) [![avm.utl.general.any-deployment-script](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.any-deployment-script.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.any-deployment-script.yml) [![avm.utl.general.get-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.get-environment.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.utl.general.get-environment.yml)
Platform pipelines | Pipeline | | - | [![.Platform - Check PSRule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.check.psrule.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.check.psrule.yml) (unrelated) [![.Platform - Clean up deployment history](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.deployment.history.cleanup.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.deployment.history.cleanup.yml) [![.Platform - Manage workflow issue](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.manage-workflow-issue.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.manage-workflow-issue.yml) (unrelated permission issue) [![.Platform - Publish [moduleIndex.json]](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.publish-module-index-json.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.publish-module-index-json.yml) (unrelated permission issue) [![.Platform - Run CI tests](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.ci-tests.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.ci-tests.yml) [![.Platform - Sync repo labels from CSV](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.sync-repo-labels-from-csv.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.sync-repo-labels-from-csv.yml) [![.Platform - Toggle AVM workflows](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.toggle-avm-workflows.yml/badge.svg?branch=users%2Falsehr%2Futl&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/platform.toggle-avm-workflows.yml)
## Type of Change - [x] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --------- Co-authored-by: Maher Aldineh Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .github/workflows/avm.template.module.yml | 2 +- .../platform.toggle-avm-workflows.yml | 2 +- avm/ptn/azd/container-apps/README.md | 2 - .../registry/cache-rule/README.md | 4 +- .../registry/cache-rule/main.bicep | 2 +- .../registry/cache-rule/main.json | 4 +- avm/res/container-registry/registry/main.json | 4 +- .../Get-AvailableResourceLocation.ps1 | 2 +- .../Get-DeploymentTargetResourceList.ps1 | 2 +- .../Invoke-AvmJsonModuleIndexGeneration.ps1 | 2 +- .../Set-AvmGitHubIssueOwnerConfig.ps1 | 2 +- .../Set-AvmGithubIssueForWorkflow.ps1 | 2 +- .../platform/Switch-WorkflowState.ps1 | 6 +- .../platform/Sync-AvmModulesList.ps1 | 58 +++++- .../publish/helper/Get-ModuleReadmeLink.ps1 | 2 +- .../helper/Get-ModuleTargetPatchVersion.ps1 | 2 +- .../publish/helper/Get-ModulesToPublish.ps1 | 4 +- .../publish/helper/New-ModuleReleaseTag.ps1 | 2 +- .../sharedScripts/Get-BRMRepositoryName.ps1 | 2 +- .../helper/Get-CrossReferencedModuleList.ps1 | 4 +- .../compliance/Set-PesterGitHubOutput.ps1 | 2 +- .../compliance/helper/helper.psm1 | 5 +- .../compliance/module.tests.ps1 | 169 ++++++++++++------ 23 files changed, 198 insertions(+), 88 deletions(-) diff --git a/.github/workflows/avm.template.module.yml b/.github/workflows/avm.template.module.yml index 55bbbc0abd6..4df961c68e5 100644 --- a/.github/workflows/avm.template.module.yml +++ b/.github/workflows/avm.template.module.yml @@ -140,7 +140,7 @@ jobs: job_publish_module: # Note: Please don't change this job name. It is used by the setEnvironment action to define which PS modules to install on runners. name: "Publishing" runs-on: ubuntu-latest - # Note: Below always() required in condition due to psrule jobs being skipped for ptn modules not having defaults or waf-aligned folders + # Note: Below always() required in condition due to psrule jobs being skipped for ptn & utl modules not having defaults or waf-aligned folders if: github.ref == 'refs/heads/main' && github.repository == 'Azure/bicep-registry-modules' && always() && diff --git a/.github/workflows/platform.toggle-avm-workflows.yml b/.github/workflows/platform.toggle-avm-workflows.yml index 468f942a5bb..2c119b2473a 100644 --- a/.github/workflows/platform.toggle-avm-workflows.yml +++ b/.github/workflows/platform.toggle-avm-workflows.yml @@ -15,7 +15,7 @@ on: type: string description: "RegEx which workflows are included" required: false - default: "avm\\.(?:res|ptn)" + default: "avm\\.(?:res|ptn|utl)" excludePattern: type: string description: "RegEx which workflows are excluded" diff --git a/avm/ptn/azd/container-apps/README.md b/avm/ptn/azd/container-apps/README.md index 144948db210..413bf894bef 100644 --- a/avm/ptn/azd/container-apps/README.md +++ b/avm/ptn/azd/container-apps/README.md @@ -145,7 +145,6 @@ module containerApps 'br/public:avm/ptn/azd/container-apps:' = {

- ## Parameters **Required parameters** @@ -338,7 +337,6 @@ Zone redundancy setting. - Type: bool - Default: `True` - ## Outputs | Output | Type | Description | diff --git a/avm/res/container-registry/registry/cache-rule/README.md b/avm/res/container-registry/registry/cache-rule/README.md index c5b94b0a52c..064d6dc2020 100644 --- a/avm/res/container-registry/registry/cache-rule/README.md +++ b/avm/res/container-registry/registry/cache-rule/README.md @@ -28,7 +28,7 @@ Cache for Azure Container Registry (Preview) feature allows users to cache conta | Parameter | Type | Description | | :-- | :-- | :-- | -| [`name`](#parameter-name) | string | The name of the cache rule. Will be dereived from the source repository name if not defined. | +| [`name`](#parameter-name) | string | The name of the cache rule. Will be derived from the source repository name if not defined. | | [`targetRepository`](#parameter-targetrepository) | string | Target repository specified in docker pull command. E.g.: docker pull myregistry.azurecr.io/{targetRepository}:{tag}. | ### Parameter: `credentialSetResourceId` @@ -54,7 +54,7 @@ Source repository pulled from upstream. ### Parameter: `name` -The name of the cache rule. Will be dereived from the source repository name if not defined. +The name of the cache rule. Will be derived from the source repository name if not defined. - Required: No - Type: string diff --git a/avm/res/container-registry/registry/cache-rule/main.bicep b/avm/res/container-registry/registry/cache-rule/main.bicep index 55a1c1387cb..3a58558bc32 100644 --- a/avm/res/container-registry/registry/cache-rule/main.bicep +++ b/avm/res/container-registry/registry/cache-rule/main.bicep @@ -5,7 +5,7 @@ metadata owner = 'Azure/module-maintainers' @description('Required. The name of the parent registry. Required if the template is used in a standalone deployment.') param registryName string -@description('Optional. The name of the cache rule. Will be dereived from the source repository name if not defined.') +@description('Optional. The name of the cache rule. Will be derived from the source repository name if not defined.') param name string = replace(replace(sourceRepository, '/', '-'), '.', '-') @description('Required. Source repository pulled from upstream.') diff --git a/avm/res/container-registry/registry/cache-rule/main.json b/avm/res/container-registry/registry/cache-rule/main.json index c517e757796..3536c9537a5 100644 --- a/avm/res/container-registry/registry/cache-rule/main.json +++ b/avm/res/container-registry/registry/cache-rule/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4294329625336671928" + "templateHash": "14369419482171687857" }, "name": "Container Registries Cache", "description": "Cache for Azure Container Registry (Preview) feature allows users to cache container images in a private container registry. Cache for ACR, is a preview feature available in Basic, Standard, and Premium service tiers ([ref](https://learn.microsoft.com/en-us/azure/container-registry/tutorial-registry-cache)).", @@ -22,7 +22,7 @@ "type": "string", "defaultValue": "[replace(replace(parameters('sourceRepository'), '/', '-'), '.', '-')]", "metadata": { - "description": "Optional. The name of the cache rule. Will be dereived from the source repository name if not defined." + "description": "Optional. The name of the cache rule. Will be derived from the source repository name if not defined." } }, "sourceRepository": { diff --git a/avm/res/container-registry/registry/main.json b/avm/res/container-registry/registry/main.json index 5b71f153bf1..bc420f1e32c 100644 --- a/avm/res/container-registry/registry/main.json +++ b/avm/res/container-registry/registry/main.json @@ -1479,7 +1479,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4294329625336671928" + "templateHash": "14369419482171687857" }, "name": "Container Registries Cache", "description": "Cache for Azure Container Registry (Preview) feature allows users to cache container images in a private container registry. Cache for ACR, is a preview feature available in Basic, Standard, and Premium service tiers ([ref](https://learn.microsoft.com/en-us/azure/container-registry/tutorial-registry-cache)).", @@ -1496,7 +1496,7 @@ "type": "string", "defaultValue": "[replace(replace(parameters('sourceRepository'), '/', '-'), '.', '-')]", "metadata": { - "description": "Optional. The name of the cache rule. Will be dereived from the source repository name if not defined." + "description": "Optional. The name of the cache rule. Will be derived from the source repository name if not defined." } }, "sourceRepository": { diff --git a/avm/utilities/pipelines/e2eValidation/regionSelector/Get-AvailableResourceLocation.ps1 b/avm/utilities/pipelines/e2eValidation/regionSelector/Get-AvailableResourceLocation.ps1 index 3ea25a2600d..69cac89b0c5 100644 --- a/avm/utilities/pipelines/e2eValidation/regionSelector/Get-AvailableResourceLocation.ps1 +++ b/avm/utilities/pipelines/e2eValidation/regionSelector/Get-AvailableResourceLocation.ps1 @@ -66,7 +66,7 @@ function Get-AvailableResourceLocation { . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'sharedScripts' 'helper' 'Get-SpecsAlignedResourceName.ps1') # Configure Resource Type - $fullModuleIdentifier = ($ModuleRoot -split '[\/|\\]{0,1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' + $fullModuleIdentifier = ($ModuleRoot -split '[\/|\\]{0,1}avm[\/|\\]{1}(res|ptn|utl)[\/|\\]{1}')[2] -replace '\\', '/' if ($ModuleRoot -like 'avm/res*') { diff --git a/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Get-DeploymentTargetResourceList.ps1 b/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Get-DeploymentTargetResourceList.ps1 index 24d76e58d96..3338aef342c 100644 --- a/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Get-DeploymentTargetResourceList.ps1 +++ b/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Get-DeploymentTargetResourceList.ps1 @@ -1,4 +1,4 @@ -#region helper +#region helper <# .SYNOPSIS Get all deployment operations at a given scope diff --git a/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 b/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 index 03dd99c99cd..0c0a5a2f07d 100644 --- a/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 +++ b/avm/utilities/pipelines/platform/Invoke-AvmJsonModuleIndexGeneration.ps1 @@ -74,7 +74,7 @@ function Invoke-AvmJsonModuleIndexGeneration { $anyErrorsOccurred = $false $moduleIndexData = @() - foreach ($avmModuleRoot in @('avm/res', 'avm/ptn')) { + foreach ($avmModuleRoot in @('avm/res', 'avm/ptn', 'avm/utl')) { $avmModuleGroups = (Get-ChildItem -Path $avmModuleRoot -Directory).Name foreach ($moduleGroup in $avmModuleGroups) { diff --git a/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 b/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 index afc1d33bd30..d38bd012b9a 100644 --- a/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 +++ b/avm/utilities/pipelines/platform/Set-AvmGitHubIssueOwnerConfig.ps1 @@ -40,7 +40,7 @@ function Set-AvmGitHubIssueOwnerConfig { $issue = gh issue view $IssueUrl.Replace('api.', '').Replace('repos/', '') --json 'author,title,url,body,comments' --repo $Repo | ConvertFrom-Json -Depth 100 if ($issue.title.StartsWith('[AVM Module Issue]')) { - $moduleName = ($issue.body.Split("`n") -match 'avm/(?:res|ptn)')[0].Trim().Replace(' ', '') + $moduleName = ($issue.body.Split("`n") -match 'avm/(?:res|ptn|utl)')[0].Trim().Replace(' ', '') if ([string]::IsNullOrEmpty($moduleName)) { throw 'No valid module name was found in the issue.' diff --git a/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 b/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 index 07c0a282959..4d91fa68d56 100644 --- a/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 +++ b/avm/utilities/pipelines/platform/Set-AvmGithubIssueForWorkflow.ps1 @@ -100,7 +100,7 @@ function Set-AvmGithubIssueForWorkflow { > @Azure/avm-core-team-technical-bicep, the workflow for the ``$moduleName`` module has failed. Please investigate the failed workflow run. "@ - if ($workflowRun.workflowName -match 'avm.(?:res|ptn)') { + if ($workflowRun.workflowName -match 'avm.(?:res|ptn|utl)') { $moduleIndex = $moduleName.StartsWith('avm/res') ? 'Bicep-Resource' : 'Bicep-Pattern' # get CSV data $module = Get-AvmCsvData -ModuleIndex $moduleIndex | Where-Object ModuleName -EQ $moduleName diff --git a/avm/utilities/pipelines/platform/Switch-WorkflowState.ps1 b/avm/utilities/pipelines/platform/Switch-WorkflowState.ps1 index 1d02a854e47..abd7b9f878c 100644 --- a/avm/utilities/pipelines/platform/Switch-WorkflowState.ps1 +++ b/avm/utilities/pipelines/platform/Switch-WorkflowState.ps1 @@ -15,7 +15,7 @@ Mandatory. The owning organization of the repository. For example, 'MyOrg'. Optional. The name of the repository. Defaults to 'bicep-registry-modules'. .PARAMETER IncludePattern -Optional. A regex pattern to match against the workflow names. Defaults to 'avm\.(?:res|ptn)' - avm.res & avm.ptn. +Optional. A regex pattern to match against the workflow names. Defaults to 'avm\.(?:res|ptn|utl)' - avm.res, avm.ptn & avm.utl. .PARAMETER ExlcudePattern Optional. A regex pattern that should not match against the workflow names. Defaults to '^$' - empty. @@ -26,7 +26,7 @@ Optional. The GitHub PAT token to use for authentication when interacting with G .EXAMPLE Switch-WorkflowState -RepositoryOwner 'Paul' -RepositoryName 'bicep-registry-modules' -TargetState 'enable' -GitHubToken ('iAmAToken' | ConvertTo-SecureString -AsPlainText -Force) -Enable any AVM res/ptn workflow in the [Paul/bicep-registry-modules] repository that is not in state 'active' using a custom GitHub PAT token. +Enable any AVM res/ptn/utl workflow in the [Paul/bicep-registry-modules] repository that is not in state 'active' using a custom GitHub PAT token. .EXAMPLE Switch-WorkflowState -RepositoryOwner 'Paul' -RepositoryName 'bicep-registry-modules' -TargetState 'disable' @@ -58,7 +58,7 @@ function Switch-WorkflowState { [string] $RepositoryName = 'bicep-registry-modules', [Parameter(Mandatory = $false)] - [string] $IncludePattern = 'avm\.(?:res|ptn)', + [string] $IncludePattern = 'avm\.(?:res|ptn|utl)', [Parameter(Mandatory = $false)] [string] $ExlcudePattern = '^$', diff --git a/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 b/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 index d7b59763c61..2038ff5d301 100644 --- a/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 +++ b/avm/utilities/pipelines/platform/Sync-AvmModulesList.ps1 @@ -33,6 +33,7 @@ function Sync-AvmModulesList { # get CSV data $targetModules = Get-AvmCsvData -ModuleIndex 'Bicep-Resource' | Where-Object { ($_.ModuleStatus -eq 'Available :green_circle:') -or ($_.ModuleStatus -eq 'Orphaned :eyes:') } | Select-Object -ExpandProperty 'ModuleName' | Sort-Object $targetPatterns = Get-AvmCsvData -ModuleIndex 'Bicep-Pattern' | Where-Object { ($_.ModuleStatus -eq 'Available :green_circle:') -or ($_.ModuleStatus -eq 'Orphaned :eyes:') } | Select-Object -ExpandProperty 'ModuleName' | Sort-Object + $targetUtilities = Get-AvmCsvData -ModuleIndex 'Bicep-Utility' | Where-Object { ($_.ModuleStatus -eq 'Available :green_circle:') -or ($_.ModuleStatus -eq 'Orphaned :eyes:') } | Select-Object -ExpandProperty 'ModuleName' | Sort-Object $issueTemplatePath = Join-Path $RepoRoot '.github' 'ISSUE_TEMPLATE' 'avm_module_issue.yml' $issueTemplateContent = Get-Content $issueTemplatePath @@ -51,14 +52,21 @@ function Sync-AvmModulesList { $listedModules = $issueTemplateContent[$startIndex..$endIndex] | Where-Object { -not $_.Contains('#') } | ForEach-Object { $_ -replace '.*- "(avm\/.*)".*', '$1' } | Where-Object { $_ -match 'avm\/res\/.*' } $listedPatterns = $issueTemplateContent[$startIndex..$endIndex] | Where-Object { -not $_.Contains('#') } | ForEach-Object { $_ -replace '.*- "(avm\/.*)".*', '$1' } | Where-Object { $_ -match 'avm\/ptn\/.*' } + $listedUtilities = $issueTemplateContent[$startIndex..$endIndex] | Where-Object { -not $_.Contains('#') } | ForEach-Object { $_ -replace '.*- "(avm\/.*)".*', '$1' } | Where-Object { $_ -match 'avm\/utl\/.*' } $body = '' $missingModules = $targetModules | Where-Object { $listedModules -NotContains $_ } $unexpectedModules = $listedModules | Where-Object { $targetModules -NotContains $_ } - $unexpectedPatterns = $listedPatterns | Where-Object { $targetPatterns -NotContains $_ } + $missingPatterns = $targetPatterns | Where-Object { $listedPatterns -NotContains $_ } + $unexpectedPatterns = $listedPatterns | Where-Object { $targetPatterns -NotContains $_ } + + $missingUtilities = $targetUtilities | Where-Object { $listedUtilities -NotContains $_ } + $unexpectedUtilities = $listedUtilities | Where-Object { $targetUtilities -NotContains $_ } + # Resource modules + # ---------------- if ($missingModules.Count -gt 0) { $body += @" **Missing resource modules:** @@ -77,6 +85,8 @@ $([Environment]::NewLine) "@ } + # Patterns + # -------- if ($missingPatterns.Count -gt 0) { $body += @" **Missing pattern modules:** @@ -95,6 +105,29 @@ $([Environment]::NewLine) "@ } + # Utilities + # --------- + if ($missingUtilities.Count -gt 0) { + $body += @" +**Missing utility modules:** + +$($missingUtilities -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + if ($unexpectedUtilities.Count -gt 0) { + $body += @" +**Unexpected utility modules:** + +$($unexpectedUtilities -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + + + # Resource modules + # ---------------- # Should be at correct location $incorrectModuleLines = @() foreach ($finding in (Compare-Object $listedModules ($listedModules | Sort-Object) -SyncWindow 0)) { @@ -113,6 +146,8 @@ $([Environment]::NewLine) "@ } + # Patterns + # -------- $incorrectPatternLines = @() foreach ($finding in (Compare-Object $listedPatterns ($listedPatterns | Sort-Object) -SyncWindow 0)) { if ($finding.SideIndicator -eq '<=') { @@ -130,6 +165,25 @@ $([Environment]::NewLine) "@ } + # Utilities + # --------- + $incorrectUtilityLines = @() + foreach ($finding in (Compare-Object $listedUtilities ($listedUtilities | Sort-Object) -SyncWindow 0)) { + if ($finding.SideIndicator -eq '<=') { + $incorrectUtilityLines += $finding.InputObject + } + } + $incorrectUtilityLines = $incorrectUtilityLines | Sort-Object -Unique + + if ($incorrectUtilityLines.Count -gt 0) { + $body += @" +**Utility modules that are not correctly sorted:** + +$($incorrectUtilityLines -join ([Environment]::NewLine)) +$([Environment]::NewLine) +"@ + } + $issuesFound = $body -ne '' $title = '[AVM core] AVM Module Issue template is not in sync with published resource modules and pattern modules list' @@ -138,7 +192,7 @@ $([Environment]::NewLine) $body = @" > [!IMPORTANT] -> The file [avm_module_issue.yml](https://github.com/Azure/bicep-registry-modules/blob/main/.github/ISSUE_TEMPLATE/avm_module_issue.yml?plain=1) which lists all modules when creating a new issue, is not in sync with the CSV files, that can be found under [resource modules](https://aka.ms/avm/index/bicep/res/csv) and [pattern modules](https://aka.ms/avm/index/bicep/ptn/csv). These CSV files are the single source of truth regarding published modules. Please update the ``avm_module_issue.yml`` accordingly. Please see the following differences that were found. +> The file [avm_module_issue.yml](https://github.com/Azure/bicep-registry-modules/blob/main/.github/ISSUE_TEMPLATE/avm_module_issue.yml?plain=1) which lists all modules when creating a new issue, is not in sync with the CSV files, that can be found under [resource modules](https://aka.ms/avm/index/bicep/res/csv), [pattern modules](https://aka.ms/avm/index/bicep/ptn/csv) and [utility modules](https://aka.ms/avm/index/bicep/utl/csv). These CSV files are the single source of truth regarding published modules. Please update the ``avm_module_issue.yml`` accordingly. Please see the following differences that were found. $([Environment]::NewLine) "@ + $body diff --git a/avm/utilities/pipelines/publish/helper/Get-ModuleReadmeLink.ps1 b/avm/utilities/pipelines/publish/helper/Get-ModuleReadmeLink.ps1 index c4df0f8afcb..e4a078e995b 100644 --- a/avm/utilities/pipelines/publish/helper/Get-ModuleReadmeLink.ps1 +++ b/avm/utilities/pipelines/publish/helper/Get-ModuleReadmeLink.ps1 @@ -33,6 +33,6 @@ function Get-ModuleReadmeLink { [string] $RegistryBaseUri = 'https://github.com/Azure/bicep-registry-modules/tree' ) - $ModuleRelativeFolderPath = ('avm/{0}' -f ($ModuleFolderPath -split '[\/|\\]avm[\/|\\]')[-1]) -replace '\\', '/' + $ModuleRelativeFolderPath = (($ModuleFolderPath -split '[\/|\\](avm)[\/|\\](res|ptn|utl)[\/|\\]')[-3..-1] -join '/') -replace '\\', '/' return (('{0}/{1}/{2}/README.md' -f $RegistryBaseUri, $TagName, $ModuleRelativeFolderPath) -replace '\\', '/') } diff --git a/avm/utilities/pipelines/publish/helper/Get-ModuleTargetPatchVersion.ps1 b/avm/utilities/pipelines/publish/helper/Get-ModuleTargetPatchVersion.ps1 index 71f76cfe31c..b001fdc87cf 100644 --- a/avm/utilities/pipelines/publish/helper/Get-ModuleTargetPatchVersion.ps1 +++ b/avm/utilities/pipelines/publish/helper/Get-ModuleTargetPatchVersion.ps1 @@ -38,7 +38,7 @@ function Get-ModuleTargetPatchVersion { [string] $MajMinVersion ) - $ModuleRelativeFolderPath = ('avm/{0}' -f ($ModuleFolderPath -split '[\/|\\]avm[\/|\\]')[-1]) -replace '\\', '/' + $ModuleRelativeFolderPath = (($ModuleFolderPath -split '[\/|\\](avm)[\/|\\](res|ptn|utl)[\/|\\]')[-3..-1] -join '/') -replace '\\', '/' # Get all released module tags (using upstream specifically to work in forks) $existingTagList = git ls-remote --tag 'https://github.com/Azure/bicep-registry-modules.git' "$ModuleRelativeFolderPath/$MajMinVersion*" diff --git a/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 b/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 index 52c3e0da759..528734d9922 100644 --- a/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 +++ b/avm/utilities/pipelines/publish/helper/Get-ModulesToPublish.ps1 @@ -84,7 +84,7 @@ function Get-TemplateFileToPublish { [string[]] $PathsToInclude = @() ) - $ModuleRelativeFolderPath = ('avm/{0}' -f ($ModuleFolderPath -split '[\/|\\]avm[\/|\\]')[-1]) -replace '\\', '/' + $ModuleRelativeFolderPath = (($ModuleFolderPath -split '[\/|\\](avm)[\/|\\](res|ptn|utl)[\/|\\]')[-3..-1] -join '/') -replace '\\', '/' $ModifiedFiles = Get-ModifiedFileList -Verbose Write-Verbose "Looking for modified files under: [$ModuleRelativeFolderPath]" -Verbose $modifiedModuleFiles = $ModifiedFiles.FullName | Where-Object { $_ -like "*$ModuleFolderPath*" } @@ -110,7 +110,7 @@ function Get-TemplateFileToPublish { Write-Verbose ('Modified modules found: [{0}]' -f $TemplateFilesToPublish.count) -Verbose $TemplateFilesToPublish | ForEach-Object { - $RelPath = ('avm/{0}' -f ($_ -split '[\/|\\]avm[\/|\\]')[-1]) -replace '\\', '/' + $RelPath = (($_ -split '[\/|\\](avm)[\/|\\](res|ptn|utl)[\/|\\]')[-3..-1] -join '/') -replace '\\', '/' $RelPath = $RelPath.Split('/main.')[0] Write-Verbose " - [$RelPath]" -Verbose } diff --git a/avm/utilities/pipelines/publish/helper/New-ModuleReleaseTag.ps1 b/avm/utilities/pipelines/publish/helper/New-ModuleReleaseTag.ps1 index 1298f449a75..8c82651d849 100644 --- a/avm/utilities/pipelines/publish/helper/New-ModuleReleaseTag.ps1 +++ b/avm/utilities/pipelines/publish/helper/New-ModuleReleaseTag.ps1 @@ -28,7 +28,7 @@ function New-ModuleReleaseTag { [string] $TargetVersion ) - $ModuleRelativeFolderPath = ('avm/{0}' -f ($ModuleFolderPath -split '[\/|\\]avm[\/|\\]')[-1]) -replace '\\', '/' + $ModuleRelativeFolderPath = (($ModuleFolderPath -split '[\/|\\](avm)[\/|\\](res|ptn|utl)[\/|\\]')[-3..-1] -join '/') -replace '\\', '/' # 1 Build Tag $tagName = '{0}/{1}' -f $ModuleRelativeFolderPath, $TargetVersion diff --git a/avm/utilities/pipelines/sharedScripts/Get-BRMRepositoryName.ps1 b/avm/utilities/pipelines/sharedScripts/Get-BRMRepositoryName.ps1 index 27fb9f531ca..19de31a1e18 100644 --- a/avm/utilities/pipelines/sharedScripts/Get-BRMRepositoryName.ps1 +++ b/avm/utilities/pipelines/sharedScripts/Get-BRMRepositoryName.ps1 @@ -21,6 +21,6 @@ function Get-BRMRepositoryName { [string] $TemplateFilePath ) - $moduleIdentifier = (Split-Path $TemplateFilePath -Parent) -split '[\/|\\]avm[\/|\\](res|ptn)[\/|\\]' + $moduleIdentifier = (Split-Path $TemplateFilePath -Parent) -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]' return ('avm/{0}/{1}' -f $moduleIdentifier[1], $moduleIdentifier[2]) -replace '\\', '/' } diff --git a/avm/utilities/pipelines/sharedScripts/helper/Get-CrossReferencedModuleList.ps1 b/avm/utilities/pipelines/sharedScripts/helper/Get-CrossReferencedModuleList.ps1 index 46ca3bb6770..ec32409602f 100644 --- a/avm/utilities/pipelines/sharedScripts/helper/Get-CrossReferencedModuleList.ps1 +++ b/avm/utilities/pipelines/sharedScripts/helper/Get-CrossReferencedModuleList.ps1 @@ -148,7 +148,7 @@ function Get-CrossReferencedModuleList { [string] $Path = (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent ) - $repoRoot = ($Path -split '[\/|\\]{1}avm[\/|\\]{1}')[0] + $repoRoot = ($Path -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]')[0] $resultSet = [ordered]@{} # Collect data @@ -176,7 +176,7 @@ function Get-CrossReferencedModuleList { $moduleFolderPath = Split-Path $moduleTemplatePath -Parent ## avm/res// - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' + $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]')[2] -replace '\\', '/' $providerNamespace = ($resourceTypeIdentifier -split '[\/|\\]')[0] $resourceType = $resourceTypeIdentifier -replace "$providerNamespace[\/|\\]", '' diff --git a/avm/utilities/pipelines/staticValidation/compliance/Set-PesterGitHubOutput.ps1 b/avm/utilities/pipelines/staticValidation/compliance/Set-PesterGitHubOutput.ps1 index a8b4d77db73..83c24536cd7 100644 --- a/avm/utilities/pipelines/staticValidation/compliance/Set-PesterGitHubOutput.ps1 +++ b/avm/utilities/pipelines/staticValidation/compliance/Set-PesterGitHubOutput.ps1 @@ -100,7 +100,7 @@ function Set-PesterGitHubOutput { Write-Verbose ('Formatting [{0}] skipped tests' -f $skippedTests.Count) Write-Verbose ('Formatting [{0}] tests with explicit warnings' -f $warnings.Count) - $moduleSplitRegex = '[\/|\\]avm[\/|\\](res|ptn)[\/|\\]' + $moduleSplitRegex = '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]' ###################### # Set output content # diff --git a/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 b/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 index e7871482269..8673ec05f4e 100644 --- a/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 +++ b/avm/utilities/pipelines/staticValidation/compliance/helper/helper.psm1 @@ -221,7 +221,10 @@ function Resolve-ReadMeParameterList { $parameterSet = @{} - if (-not $Properties) { + if (-not $Properties -and -not $TemplateFileContent.parameters) { + # no Parameters / properties on this level or in the template + return $parameterSet + } elseif (-not $Properties) { # Top-level invocation # Add name as property for later reference $TemplateFileContent.parameters.Keys | ForEach-Object { $TemplateFileContent.parameters[$_]['name'] = $_ } diff --git a/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 b/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 index 890ff321baf..53a95089534 100644 --- a/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 +++ b/avm/utilities/pipelines/staticValidation/compliance/module.tests.ps1 @@ -22,6 +22,7 @@ $script:MgDeploymentSchema = 'https://schema.management.azure.com/schemas/2019-0 $script:TenantDeploymentSchema = 'https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#' $script:telemetryResCsvLink = 'https://aka.ms/avm/index/bicep/res/csv' $script:telemetryPtnCsvLink = 'https://aka.ms/avm/index/bicep/ptn/csv' +$script:telemetryUtlCsvLink = 'https://aka.ms/avm/index/bicep/utl/csv' $script:moduleFolderPaths = $moduleFolderPaths # Shared exception messages @@ -59,11 +60,13 @@ Describe 'File/folder tests' -Tag 'Modules' { $moduleFolderTestCases = [System.Collections.ArrayList] @() foreach ($moduleFolderPath in $moduleFolderPaths) { - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' + $null, $moduleType, $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]') # 'avm/res|ptn|utl//' would return 'avm', 'res|ptn|utl', '/' + $resourceTypeIdentifier = $resourceTypeIdentifier -replace '\\', '/' $moduleFolderTestCases += @{ moduleFolderName = $resourceTypeIdentifier moduleFolderPath = $moduleFolderPath isTopLevelModule = ($resourceTypeIdentifier -split '[\/|\\]').Count -eq 2 + moduleType = $moduleType } } @@ -100,13 +103,19 @@ Describe 'File/folder tests' -Tag 'Modules' { It '[] Module should contain a [` ORPHANED.md `] file only if orphaned.' -TestCases ($moduleFolderTestCases | Where-Object { $_.isTopLevelModule }) { param( - [string] $moduleFolderPath + [string] $moduleFolderPath, + [string] $moduleType ) $templateFilePath = Join-Path -Path $moduleFolderPath 'main.bicep' # Use correct telemetry link based on file path - $telemetryCsvLink = $moduleFolderPath -match '[\\|\/]res[\\|\/]' ? $telemetryResCsvLink : $telemetryPtnCsvLink + switch ($moduleType) { + 'res' { $telemetryCsvLink = $telemetryResCsvLink; break } + 'ptn' { $telemetryCsvLink = $telemetryPtnCsvLink; break } + 'utl' { $telemetryCsvLink = $telemetryUtlCsvLink; break } + Default {} + } # Fetch CSV # ========= @@ -146,13 +155,13 @@ Describe 'File/folder tests' -Tag 'Modules' { $topLevelModuleTestCases = [System.Collections.ArrayList]@() foreach ($moduleFolderPath in $moduleFolderPaths) { - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' - $moduleTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[1] # 'avm/res|ptn//' would return 'res|ptn' + $null, $moduleType, $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]') # 'avm/res|ptn|utl//' would return 'avm', 'res|ptn|utl', '/' + $resourceTypeIdentifier = $resourceTypeIdentifier -replace '\\', '/' if (($resourceTypeIdentifier -split '[\/|\\]').Count -eq 2) { $topLevelModuleTestCases += @{ - moduleFolderName = $moduleFolderPath.Replace('\', '/').Split('/avm/')[1] - moduleFolderPath = $moduleFolderPath - moduleTypeIdentifier = $moduleTypeIdentifier + moduleFolderName = $moduleFolderPath.Replace('\', '/').Split('/avm/')[1] + moduleFolderPath = $moduleFolderPath + moduleType = $moduleType } } } @@ -187,7 +196,7 @@ Describe 'File/folder tests' -Tag 'Modules' { $pathExisting | Should -Be $true } - It '[] Module should contain a [` tests/e2e/*waf-aligned `] folder.' -TestCases ($topLevelModuleTestCases | Where-Object { $_.moduleTypeIdentifier -eq 'res' }) { + It '[] Module should contain a [` tests/e2e/*waf-aligned `] folder.' -TestCases ($topLevelModuleTestCases | Where-Object { $_.moduleType -eq 'res' }) { param( [string] $moduleFolderPath @@ -197,7 +206,7 @@ Describe 'File/folder tests' -Tag 'Modules' { $wafAlignedFolder | Should -Not -BeNullOrEmpty } - It '[] Module should contain a [` tests/e2e/*defaults `] folder.' -TestCases ($topLevelModuleTestCases | Where-Object { $_.moduleTypeIdentifier -eq 'res' }) { + It '[] Module should contain a [` tests/e2e/*defaults `] folder.' -TestCases ($topLevelModuleTestCases | Where-Object { $_.moduleType -eq 'res' }) { param( [string] $moduleFolderPath @@ -235,8 +244,8 @@ Describe 'Pipeline tests' -Tag 'Pipeline' { $pipelineTestCases = [System.Collections.ArrayList] @() foreach ($moduleFolderPath in $moduleFolderPaths) { - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' - $relativeModulePath = Join-Path 'avm' ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}')[1] + $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]')[2] -replace '\\', '/' # 'avm/res|ptn|utl//' would return '/' + $relativeModulePath = Join-Path 'avm' ($moduleFolderPath -split '[\/|\\]avm[\/|\\]')[1] $isTopLevelModule = ($resourceTypeIdentifier -split '[\/|\\]').Count -eq 2 if ($isTopLevelModule) { @@ -290,7 +299,7 @@ Describe 'Module tests' -Tag 'Module' { foreach ($moduleFolderPath in $moduleFolderPaths) { - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' + $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]')[2] -replace '\\', '/' # 'avm/res|ptn|utl//' would return '/' $templateFilePath = Join-Path $moduleFolderPath 'main.bicep' $readmeFileTestCases += @{ @@ -348,7 +357,7 @@ Describe 'Module tests' -Tag 'Module' { continue } - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' + $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]')[2] -replace '\\', '/' # 'avm/res|ptn|utl//' would return '/' $armTemplateTestCases += @{ moduleFolderName = $resourceTypeIdentifier @@ -385,7 +394,7 @@ Describe 'Module tests' -Tag 'Module' { $newJson = ConvertTo-OrderedHashtable -JSONInputObject (ConvertTo-Json $newJson -Depth 99) # compare - (ConvertTo-Json $originalJson -Depth 99) | Should -Be (ConvertTo-Json $newJson -Depth 99) -Because "the [$moduleFolderName] [main.json] should be based on the latest [main.bicep] file. Please run [` bicep build >bicepFilePath< `] using the latest Bicep CLI version." + (ConvertTo-Json $originalJson -Depth 99) | Should -Be (ConvertTo-Json $newJson -Depth 99) -Because "the [$moduleFolderName] [main.json] should be based on the latest [main.bicep] file. Please run [` bicep build >bicepFilePath< `] using the latest Bicep CLI version." # Reset template file to original state git checkout HEAD -- $armTemplatePath @@ -400,7 +409,8 @@ Describe 'Module tests' -Tag 'Module' { $templateFilePath = Join-Path $moduleFolderPath 'main.bicep' $templateFileContent = $builtTestFileMap[$templateFilePath] - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' + $null, $moduleType, $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]') # 'avm/res|ptn|utl//' would return 'avm', 'res|ptn|utl', '/' + $resourceTypeIdentifier = $resourceTypeIdentifier -replace '\\', '/' # Test file setup $moduleFolderTestCases += @{ @@ -410,6 +420,7 @@ Describe 'Module tests' -Tag 'Module' { templateFileParameters = Resolve-ReadMeParameterList -TemplateFileContent $templateFileContent readMeFilePath = Join-Path (Split-Path $templateFilePath) 'README.md' isTopLevelModule = ($resourceTypeIdentifier -split '[\/|\\]').Count -eq 2 + moduleType = $moduleType } } @@ -453,7 +464,7 @@ Describe 'Module tests' -Tag 'Module' { [hashtable] $templateFileContent ) $Schemaverion = $templateFileContent.'$schema' - ($Schemaverion.Substring(0, 5) -eq 'https') | Should -Be $true + ($Schemaverion.Substring(0, 5) -eq 'https') | Should -Be $true } It '[] The template file should contain required elements [schema], [contentVersion], [resources].' -TestCases $moduleFolderTestCases { @@ -509,7 +520,8 @@ Describe 'Module tests' -Tag 'Module' { } } - It '[] The telemetry parameter should be present & have the expected type, default value & metadata description.' -TestCases ($moduleFolderTestCases | Where-Object { $_.isTopLevelModule }) { + # If any resources in the module are deployed, a telemetry deployment should be carried out as well + It '[] The telemetry parameter should be present & have the expected type, default value & metadata description.' -TestCases ($moduleFolderTestCases | Where-Object { $_.isTopLevelModule -and $_.templateFileContent.resources.count -gt 0 }) { param( [hashtable] $templateFileParameters @@ -633,12 +645,13 @@ Describe 'Module tests' -Tag 'Module' { $udtSpecificTestCases = [System.Collections.ArrayList] @() # Specific UDT test cases for singular UDTs (e.g. tags) foreach ($moduleFolderPath in $moduleFolderPaths) { - if ($moduleFolderPath -match '[\/|\\]avm[\/|\\]ptn[\/|\\]') { - # Skip UDT interface tests for ptn modules + if ($moduleFolderPath -match '[\/|\\]avm[\/|\\](ptn|utl)[\/|\\]') { + # Skip UDT interface tests for ptn & utl modules continue } - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' + $null, $moduleType, $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]') # 'avm/res|ptn|utl//' would return 'avm', 'res|ptn|utl', '/' + $resourceTypeIdentifier = $resourceTypeIdentifier -replace '\\', '/' $templateFilePath = Join-Path $moduleFolderPath 'main.bicep' $templateFileContent = $builtTestFileMap[$templateFilePath] @@ -646,6 +659,7 @@ Describe 'Module tests' -Tag 'Module' { moduleFolderName = $resourceTypeIdentifier templateFileContent = $templateFileContent templateFileContentBicep = Get-Content $templateFilePath + moduleType = $moduleType } # Setting expected URL only for those that doen't have multiple different variants @@ -832,7 +846,8 @@ Describe 'Module tests' -Tag 'Module' { } Context 'Resources' { - It '[] Telemetry deployment should be present in the template.' -TestCases ($moduleFolderTestCases | Where-Object { $_.isTopLevelModule }) { + # If any resources in the module are deployed, a telemetry deployment should be carried out as well + It '[] Telemetry deployment should be present in the template.' -TestCases ($moduleFolderTestCases | Where-Object { $_.isTopLevelModule -and $_.templateFileContent.resources.count -gt 0 }) { param( [hashtable] $templateFileContent @@ -865,7 +880,7 @@ Describe 'Module tests' -Tag 'Module' { $telemetryDeployment = $templateResources | Where-Object { $_.condition -like '*telemetry*' -and $_.name -like '*46d3xbcp*' } # The AVM telemetry prefix if (-not $telemetryDeployment) { - Set-ItResult -Skipped -Because 'Skipping this test as telemetry was not implemented in template' + Set-ItResult -Skipped -Because 'telemetry was not implemented in template' return } @@ -888,7 +903,7 @@ Describe 'Module tests' -Tag 'Module' { $telemetryDeployment = $templateResources | Where-Object { $_.condition -like '*telemetry*' -and $_.name -like '*46d3xbcp*' } # The AVM telemetry prefix if (-not $telemetryDeployment) { - Set-ItResult -Skipped -Because 'Skipping this test as telemetry was not implemented in template' + Set-ItResult -Skipped -Because 'telemetry was not implemented in template' return } @@ -900,11 +915,31 @@ Describe 'Module tests' -Tag 'Module' { param( [string] $templateFilePath, + [string] $moduleType, [hashtable] $templateFileContent ) + # With the introduction of user defined types, the way resources are configured in the schema slightly changed. We have to account for that. + if ($templateFileContent.resources.GetType().Name -eq 'Object[]') { + $templateResources = $templateFileContent.resources + } else { + $templateResources = $templateFileContent.resources.Keys | ForEach-Object { $templateFileContent.resources[$_] } + } + + $telemetryDeployment = $templateResources | Where-Object { $_.condition -like '*telemetry*' -and $_.name -like '*46d3xbcp*' } # The AVM telemetry prefix + + if (-not $telemetryDeployment) { + Set-ItResult -Skipped -Because 'telemetry was not implemented in template' + return + } + # Use correct telemetry link based on file path - $telemetryCsvLink = $templateFilePath -match '[\\|\/]res[\\|\/]' ? $telemetryResCsvLink : $telemetryPtnCsvLink + switch ($moduleType) { + 'res' { $telemetryCsvLink = $telemetryResCsvLink; break } + 'ptn' { $telemetryCsvLink = $telemetryPtnCsvLink; break } + 'utl' { $telemetryCsvLink = $telemetryUtlCsvLink; break } + Default {} + } # Fetch CSV # ========= @@ -933,13 +968,8 @@ Describe 'Module tests' -Tag 'Module' { # Collect resource & compare # ========================== - # With the introduction of user defined types, the way resources are configured in the schema slightly changed. We have to account for that. - if ($templateFileContent.resources.GetType().Name -eq 'Object[]') { - $templateResources = $templateFileContent.resources - } else { - $templateResources = $templateFileContent.resources.Keys | ForEach-Object { $templateFileContent.resources[$_] } - } - $telemetryDeploymentName = ($templateResources | Where-Object { $_.condition -like '*telemetry*' -and $_.name -like '*46d3xbcp*' }).name # The AVM telemetry prefix + + $telemetryDeploymentName = $telemetryDeployment.name # The AVM telemetry prefix $telemetryDeploymentName | Should -Match "$expectedTelemetryIdentifier" } } @@ -1016,6 +1046,11 @@ Describe 'Module tests' -Tag 'Module' { [string] $templateFilePath ) + if ($templateFileContent.resources.count -eq 0) { + Set-ItResult -Skipped -Because 'with no resources are deployed in the template, the test is not required' + return + } + $outputs = $templateFileContent.outputs.Keys $deploymentScope = Get-ScopeOfTemplateFile -TemplateFilePath $templateFilePath @@ -1122,8 +1157,9 @@ Describe 'Governance tests' { $governanceTestCases = [System.Collections.ArrayList] @() foreach ($moduleFolderPath in $moduleFolderPaths) { - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' - $relativeModulePath = Join-Path 'avm' ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}')[1] + $null, $moduleType, $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]') # 'avm/res|ptn|utl//' would return 'avm', 'res|ptn|utl', '/' + $resourceTypeIdentifier = $resourceTypeIdentifier -replace '\\', '/' + $relativeModulePath = Join-Path 'avm' ($moduleFolderPath -split '[\/|\\]avm[\/|\\]')[1] $isTopLevelModule = ($resourceTypeIdentifier -split '[\/|\\]').Count -eq 2 if ($isTopLevelModule) { @@ -1132,6 +1168,7 @@ Describe 'Governance tests' { relativeModulePath = $relativeModulePath repoRootPath = $repoRootPath moduleFolderName = $resourceTypeIdentifier + moduleType = $moduleType } } } @@ -1213,13 +1250,16 @@ Describe 'Test file tests' -Tag 'TestTemplate' { $testFilePaths = (Get-ChildItem -Path $moduleFolderPath -Recurse -Filter 'main.test.bicep').FullName | Sort-Object -Culture 'en-US' foreach ($testFilePath in $testFilePaths) { $testFileContent = Get-Content $testFilePath - $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]{1}avm[\/|\\]{1}(res|ptn)[\/|\\]{1}')[2] -replace '\\', '/' # 'avm/res|ptn//' would return '/' + $null, $moduleType, $resourceTypeIdentifier = ($moduleFolderPath -split '[\/|\\]avm[\/|\\](res|ptn|utl)[\/|\\]') # 'avm/res|ptn|utl//' would return 'avm', 'res|ptn|utl', '/' + $resourceTypeIdentifier = $resourceTypeIdentifier -replace '\\', '/' $deploymentTestFileTestCases += @{ - testName = Split-Path (Split-Path $testFilePath) -Leaf - testFilePath = $testFilePath - testFileContent = $testFileContent - moduleFolderName = $resourceTypeIdentifier + testName = Split-Path (Split-Path $testFilePath) -Leaf + testFilePath = $testFilePath + testFileContent = $testFileContent + compiledTestFileContent = $builtTestFileMap[$testFilePath] + moduleFolderName = $resourceTypeIdentifier + moduleType = $moduleType } } } @@ -1228,9 +1268,16 @@ Describe 'Test file tests' -Tag 'TestTemplate' { It '[] Bicep test deployment files should contain a parameter [serviceShort] for test case []' -TestCases $deploymentTestFileTestCases { param( - [object[]] $testFileContent + [object[]] $testFileContent, + [hashtable] $compiledTestFileContent ) - ($testFileContent -match "^param serviceShort string = '(.*)$") | Should -Not -BeNullOrEmpty -Because 'the module test deployment file should contain a parameter [serviceShort] using the syntax [param serviceShort string = ''*''].' + + if ($compiledTestFileContent.resources.count -eq 0) { + Set-ItResult -Skipped -Because 'with no deployments in the test file, the test is not required.' + return + } + + ($testFileContent -match "^param serviceShort string = '(.*)$") | Should -Not -BeNullOrEmpty -Because 'the module test deployment file should contain a parameter [serviceShort] using the syntax [param serviceShort string = ''*''].' } It '[] [] Bicep test deployment files in a [defaults] folder should have a parameter [serviceShort] with a value ending with [min]' -TestCases ($deploymentTestFileTestCases | Where-Object { $_.testFilePath -match '.*[\\|\/](.+\.)?defaults[\\|\/].*' }) { @@ -1277,7 +1324,7 @@ Describe 'Test file tests' -Tag 'TestTemplate' { param( [object[]] $testFileContent ) - ($testFileContent | Out-String) | Should -Match 'metadata name = .+' -Because 'Test cases should contain a metadata string [name] in the format `metadata name = ''One cake of a name''` to be more descriptive. If provided, the tooling will automatically inject it into the module''s readme.md file.' + ($testFileContent | Out-String) | Should -Match 'metadata name = .+' -Because 'Test cases should contain a metadata string [name] in the format `metadata name = ''One cake of a name''` to be more descriptive. If provided, the tooling will automatically inject it into the module''s readme.md file.' } It '[] Bicep test deployment files should contain a metadata string [description] for test case []' -TestCases $deploymentTestFileTestCases { @@ -1285,24 +1332,36 @@ Describe 'Test file tests' -Tag 'TestTemplate' { param( [object[]] $testFileContent ) - ($testFileContent | Out-String) | Should -Match 'metadata description = .+' -Because 'Test cases should contain a metadata string [description] in the format `metadata description = ''The cake is a lie''` to be more descriptive. If provided, the tooling will automatically inject it into the module''s readme.md file.' + ($testFileContent | Out-String) | Should -Match 'metadata description = .+' -Because 'Test cases should contain a metadata string [description] in the format `metadata description = ''The cake is a lie''` to be more descriptive. If provided, the tooling will automatically inject it into the module''s readme.md file.' } It "[] Bicep test deployment files should contain a parameter [namePrefix] with value ['#_namePrefix_#'] for test case []" -TestCases $deploymentTestFileTestCases { param( - [object[]] $testFileContent + [object[]] $testFileContent, + [hashtable] $compiledTestFileContent ) - ($testFileContent | Out-String) | Should -Match "param namePrefix string = '#_namePrefix_#'" -Because 'The test CI needs this value to ensure that deployed resources have unique names per fork.' + if ($compiledTestFileContent.resources.count -eq 0) { + Set-ItResult -Skipped -Because 'without deployments in the test file, the test is not required.' + return + } + + ($testFileContent | Out-String) | Should -Match "param namePrefix string = '#_namePrefix_#'" -Because 'The test CI needs this value to ensure that deployed resources have unique names per fork.' } It "[] Bicep test deployment files should invoke test like [`module testDeployment '../.*main.bicep' = [ or {`] for test case []" -TestCases $deploymentTestFileTestCases { param( - [object[]] $testFileContent + [object[]] $testFileContent, + [hashtable] $compiledTestFileContent ) + if ($compiledTestFileContent.resources.count -eq 0) { + Set-ItResult -Skipped -Because 'without deployments in the test file, the test is not required.' + return + } + $testIndex = ($testFileContent | Select-String ("^module testDeployment '..\/.*main.bicep' = .*[\[|\{]$") | ForEach-Object { $_.LineNumber - 1 })[0] $testIndex -ne -1 | Should -Be $true -Because 'the module test invocation should be in the expected format to allow identification.' @@ -1311,23 +1370,19 @@ Describe 'Test file tests' -Tag 'TestTemplate' { It '[] Bicep test deployment name should contain [`-test-`] for test case []' -TestCases $deploymentTestFileTestCases { param( - [object[]] $testFileContent + [object[]] $testFileContent, + [hashtable] $compiledTestFileContent ) + if ($compiledTestFileContent.resources.count -eq 0) { + Set-ItResult -Skipped -Because 'without deployments in the test file, the test is not required.' + return + } + $expectedNameFormat = ($testFileContent | Out-String) -match '\s*name:.+-test-.+\s*' $expectedNameFormat | Should -Be $true -Because 'the handle ''-test-'' should be part of the module test invocation''s resource name to allow identification.' } - - It '[] Bicep test deployment should have parameter [`serviceShort`] for test case []' -TestCases $deploymentTestFileTestCases { - - param( - [object[]] $testFileContent - ) - - $hasExpectedParam = ($testFileContent | Out-String) -match '\s*param\s+serviceShort\s+string\s*' - $hasExpectedParam | Should -Be $true - } } } From 7b37781d52b7c2f7e6da7f5ac946e8bf06b48c00 Mon Sep 17 00:00:00 2001 From: Chinedum Echeta <60179183+cecheta@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:15:14 +0100 Subject: [PATCH 07/46] fix: `avm/res/machine-learning-services/workspace` Enforce location for AI Services (#3151) ## Description Hardcodes region when deploying AI Services, as not available in East Asia. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.machine-learning-services.workspace](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml/badge.svg?branch=ai-region)](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [x] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../workspace/tests/e2e/ai/main.test.bicep | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep b/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep index 8ea4b81b797..927fe46d6c5 100644 --- a/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep +++ b/avm/res/machine-learning-services/workspace/tests/e2e/ai/main.test.bicep @@ -11,9 +11,6 @@ metadata description = 'This instance deploys an Azure AI hub workspace.' @maxLength(90) param resourceGroupName string = 'dep-${namePrefix}-machinelearningservices.workspaces-${serviceShort}-rg' -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location - @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') param serviceShort string = 'mlswai' @@ -23,6 +20,10 @@ param baseTime string = utcNow('u') @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' +// AI Services not available in all regions +#disable-next-line no-hardcoded-location +var enforcedLocation = 'uksouth' + // ============ // // Dependencies // // ============ // @@ -31,19 +32,19 @@ param namePrefix string = '#_namePrefix_#' // ================= resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: resourceGroupName - location: resourceLocation + location: enforcedLocation } module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}-${substring(uniqueString(baseTime), 0, 3)}' applicationInsightsName: 'dep-${namePrefix}-appI-${serviceShort}' storageAccountName: 'dep${namePrefix}st${serviceShort}' secondaryStorageAccountName: 'dep${namePrefix}st${serviceShort}2' aiServicesName: 'dep-${namePrefix}-ai-${serviceShort}' - location: resourceLocation + location: enforcedLocation } } @@ -55,10 +56,10 @@ module nestedDependencies 'dependencies.bicep' = { module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}001' - location: resourceLocation + location: enforcedLocation associatedApplicationInsightsResourceId: nestedDependencies.outputs.applicationInsightsResourceId associatedKeyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId associatedStorageAccountResourceId: nestedDependencies.outputs.storageAccountResourceId @@ -78,7 +79,7 @@ module testDeployment '../../../main.bicep' = [ metadata: { ApiType: 'Azure' ResourceId: nestedDependencies.outputs.aiServicesResourceId - Location: resourceLocation + Location: enforcedLocation ApiVersion: '2023-07-01-preview' DeploymentApiVersion: '2023-10-01-preview' } @@ -99,10 +100,10 @@ module testDeployment '../../../main.bicep' = [ module testProjectDeployment '../../../main.bicep' = [ for (iteration, i) in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-proj-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-proj-${iteration}' params: { name: '${namePrefix}${serviceShort}002' - location: resourceLocation + location: enforcedLocation sku: 'Basic' kind: 'Project' hubResourceId: testDeployment[i].outputs.resourceId From e5ac2f62b5e0a180c5cc76db0e75c3744badb116 Mon Sep 17 00:00:00 2001 From: Clint Grove Date: Tue, 10 Sep 2024 15:01:15 +0300 Subject: [PATCH 08/46] feat: Add defaultCatalog `avm/res/databricks/workspace` (#3178) ## Description Fixes #3087 Closes #3087 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.databricks.workspace](https://github.com/clintgrove/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml/badge.svg?branch=3807-issue-addcatalog)](https://github.com/clintgrove/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/databricks/workspace/README.md | 36 +++++++++++++++++++ avm/res/databricks/workspace/main.bicep | 20 +++++++++++ avm/res/databricks/workspace/main.json | 26 ++++++++++++-- .../workspace/tests/e2e/max/main.test.bicep | 4 +++ avm/res/databricks/workspace/version.json | 2 +- 5 files changed, 85 insertions(+), 3 deletions(-) diff --git a/avm/res/databricks/workspace/README.md b/avm/res/databricks/workspace/README.md index 6ee2ca03c24..b3084f34a59 100644 --- a/avm/res/databricks/workspace/README.md +++ b/avm/res/databricks/workspace/README.md @@ -112,6 +112,9 @@ module workspace 'br/public:avm/res/databricks/workspace:' = { customPrivateSubnetName: '' customPublicSubnetName: '' customVirtualNetworkResourceId: '' + defaultCatalog: { + initialType: 'UnityCatalog' + } diagnosticSettings: [ { eventHubAuthorizationRuleResourceId: '' @@ -246,6 +249,11 @@ module workspace 'br/public:avm/res/databricks/workspace:' = { "customVirtualNetworkResourceId": { "value": "" }, + "defaultCatalog": { + "value": { + "initialType": "UnityCatalog" + } + }, "diagnosticSettings": { "value": [ { @@ -677,6 +685,7 @@ module workspace 'br/public:avm/res/databricks/workspace:' = { | [`customPrivateSubnetName`](#parameter-customprivatesubnetname) | string | The name of the Private Subnet within the Virtual Network. | | [`customPublicSubnetName`](#parameter-custompublicsubnetname) | string | The name of a Public Subnet within the Virtual Network. | | [`customVirtualNetworkResourceId`](#parameter-customvirtualnetworkresourceid) | string | The resource ID of a Virtual Network where this Databricks Cluster should be created. | +| [`defaultCatalog`](#parameter-defaultcatalog) | object | The default catalog configuration for the Databricks workspace. | | [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | | [`disablePublicIp`](#parameter-disablepublicip) | bool | Disable Public IP. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | @@ -854,6 +863,33 @@ The resource ID of a Virtual Network where this Databricks Cluster should be cre - Type: string - Default: `''` +### Parameter: `defaultCatalog` + +The default catalog configuration for the Databricks workspace. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`initialType`](#parameter-defaultcataloginitialtype) | string | Choose between HiveMetastore or UnityCatalog. | + +### Parameter: `defaultCatalog.initialType` + +Choose between HiveMetastore or UnityCatalog. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'HiveMetastore' + 'UnityCatalog' + ] + ``` + ### Parameter: `diagnosticSettings` The diagnostic settings of the service. diff --git a/avm/res/databricks/workspace/main.bicep b/avm/res/databricks/workspace/main.bicep index 152811530e0..38f9840878c 100644 --- a/avm/res/databricks/workspace/main.bicep +++ b/avm/res/databricks/workspace/main.bicep @@ -113,6 +113,9 @@ param storageAccountPrivateEndpoints privateEndpointType @description('Conditional. The resource ID of the associated access connector for private access to the managed workspace storage account. Required if privateStorageAccount is enabled.') param accessConnectorResourceId string = '' +@description('Optional. The default catalog configuration for the Databricks workspace.') +param defaultCatalog defaultCatalogType? + var builtInRoleNames = { Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') @@ -328,6 +331,14 @@ resource workspace 'Microsoft.Databricks/workspaces@2024-05-01' = { identityType: 'SystemAssigned' } } + : {}, + !empty(defaultCatalog) + ? { + defaultCatalog: { + initialName: '' + initialType: defaultCatalog.?initialType + } + } : {} ) } @@ -740,3 +751,12 @@ type diagnosticSettingType = { @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') marketplacePartnerResourceId: string? }[]? + +type defaultCatalogType = { + //This value cannot be set to a custom value. Reason --> 'InvalidInitialCatalogName' message: 'Currently custom initial catalog name is not supported. This capability will be added in future.' + //@description('Optional. Set the name of the Catalog.') + //initialName: '' + + @description('Required. Choose between HiveMetastore or UnityCatalog.') + initialType: 'HiveMetastore' | 'UnityCatalog' +} diff --git a/avm/res/databricks/workspace/main.json b/avm/res/databricks/workspace/main.json index 8579f7478a2..2f4f7b5b64f 100644 --- a/avm/res/databricks/workspace/main.json +++ b/avm/res/databricks/workspace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6248899045759773040" + "templateHash": "8011778884263076951" }, "name": "Azure Databricks Workspaces", "description": "This module deploys an Azure Databricks Workspace.", @@ -495,6 +495,21 @@ } }, "nullable": true + }, + "defaultCatalogType": { + "type": "object", + "properties": { + "initialType": { + "type": "string", + "allowedValues": [ + "HiveMetastore", + "UnityCatalog" + ], + "metadata": { + "description": "Required. Choose between HiveMetastore or UnityCatalog." + } + } + } } }, "parameters": { @@ -724,6 +739,13 @@ "metadata": { "description": "Conditional. The resource ID of the associated access connector for private access to the managed workspace storage account. Required if privateStorageAccount is enabled." } + }, + "defaultCatalog": { + "$ref": "#/definitions/defaultCatalogType", + "nullable": true, + "metadata": { + "description": "Optional. The default catalog configuration for the Databricks workspace." + } } }, "variables": { @@ -814,7 +836,7 @@ "sku": { "name": "[parameters('skuName')]" }, - "properties": "[union(createObject('managedResourceGroupId', if(not(empty(parameters('managedResourceGroupResourceId'))), parameters('managedResourceGroupResourceId'), format('{0}/resourceGroups/rg-{1}-managed', subscription().id, parameters('name'))), 'parameters', union(createObject('enableNoPublicIp', createObject('value', parameters('disablePublicIp')), 'prepareEncryption', createObject('value', parameters('prepareEncryption')), 'vnetAddressPrefix', createObject('value', parameters('vnetAddressPrefix')), 'requireInfrastructureEncryption', createObject('value', parameters('requireInfrastructureEncryption'))), if(not(empty(parameters('customVirtualNetworkResourceId'))), createObject('customVirtualNetworkId', createObject('value', parameters('customVirtualNetworkResourceId'))), createObject()), if(not(empty(parameters('amlWorkspaceResourceId'))), createObject('amlWorkspaceId', createObject('value', parameters('amlWorkspaceResourceId'))), createObject()), if(not(empty(parameters('customPrivateSubnetName'))), createObject('customPrivateSubnetName', createObject('value', parameters('customPrivateSubnetName'))), createObject()), if(not(empty(parameters('customPublicSubnetName'))), createObject('customPublicSubnetName', createObject('value', parameters('customPublicSubnetName'))), createObject()), if(not(empty(parameters('loadBalancerBackendPoolName'))), createObject('loadBalancerBackendPoolName', createObject('value', parameters('loadBalancerBackendPoolName'))), createObject()), if(not(empty(parameters('loadBalancerResourceId'))), createObject('loadBalancerId', createObject('value', parameters('loadBalancerResourceId'))), createObject()), if(not(empty(parameters('natGatewayName'))), createObject('natGatewayName', createObject('value', parameters('natGatewayName'))), createObject()), if(not(empty(parameters('publicIpName'))), createObject('publicIpName', createObject('value', parameters('publicIpName'))), createObject()), if(not(empty(parameters('storageAccountName'))), createObject('storageAccountName', createObject('value', parameters('storageAccountName'))), createObject()), if(not(empty(parameters('storageAccountSkuName'))), createObject('storageAccountSkuName', createObject('value', parameters('storageAccountSkuName'))), createObject())), 'publicNetworkAccess', parameters('publicNetworkAccess'), 'requiredNsgRules', parameters('requiredNsgRules'), 'encryption', if(or(not(empty(parameters('customerManagedKey'))), not(empty(parameters('customerManagedKeyManagedDisk')))), createObject('entities', createObject('managedServices', if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.Keyvault', 'keyVaultProperties', createObject('keyVaultUri', reference('cMKKeyVault').vaultUri, 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/'))))), null()), 'managedDisk', if(not(empty(parameters('customerManagedKeyManagedDisk'))), createObject('keySource', 'Microsoft.Keyvault', 'keyVaultProperties', createObject('keyVaultUri', reference('cMKManagedDiskKeyVault').vaultUri, 'keyName', parameters('customerManagedKeyManagedDisk').keyName, 'keyVersion', if(not(empty(coalesce(tryGet(parameters('customerManagedKeyManagedDisk'), 'keyVersion'), ''))), parameters('customerManagedKeyManagedDisk').keyVersion, last(split(reference('cMKManagedDiskKeyVault::cMKKey').keyUriWithVersion, '/')))), 'rotationToLatestKeyVersionEnabled', coalesce(tryGet(parameters('customerManagedKeyManagedDisk'), 'rotationToLatestKeyVersionEnabled'), true())), null()))), null())), if(not(empty(parameters('privateStorageAccount'))), createObject('defaultStorageFirewall', parameters('privateStorageAccount'), 'accessConnector', createObject('id', parameters('accessConnectorResourceId'), 'identityType', 'SystemAssigned')), createObject()))]", + "properties": "[union(createObject('managedResourceGroupId', if(not(empty(parameters('managedResourceGroupResourceId'))), parameters('managedResourceGroupResourceId'), format('{0}/resourceGroups/rg-{1}-managed', subscription().id, parameters('name'))), 'parameters', union(createObject('enableNoPublicIp', createObject('value', parameters('disablePublicIp')), 'prepareEncryption', createObject('value', parameters('prepareEncryption')), 'vnetAddressPrefix', createObject('value', parameters('vnetAddressPrefix')), 'requireInfrastructureEncryption', createObject('value', parameters('requireInfrastructureEncryption'))), if(not(empty(parameters('customVirtualNetworkResourceId'))), createObject('customVirtualNetworkId', createObject('value', parameters('customVirtualNetworkResourceId'))), createObject()), if(not(empty(parameters('amlWorkspaceResourceId'))), createObject('amlWorkspaceId', createObject('value', parameters('amlWorkspaceResourceId'))), createObject()), if(not(empty(parameters('customPrivateSubnetName'))), createObject('customPrivateSubnetName', createObject('value', parameters('customPrivateSubnetName'))), createObject()), if(not(empty(parameters('customPublicSubnetName'))), createObject('customPublicSubnetName', createObject('value', parameters('customPublicSubnetName'))), createObject()), if(not(empty(parameters('loadBalancerBackendPoolName'))), createObject('loadBalancerBackendPoolName', createObject('value', parameters('loadBalancerBackendPoolName'))), createObject()), if(not(empty(parameters('loadBalancerResourceId'))), createObject('loadBalancerId', createObject('value', parameters('loadBalancerResourceId'))), createObject()), if(not(empty(parameters('natGatewayName'))), createObject('natGatewayName', createObject('value', parameters('natGatewayName'))), createObject()), if(not(empty(parameters('publicIpName'))), createObject('publicIpName', createObject('value', parameters('publicIpName'))), createObject()), if(not(empty(parameters('storageAccountName'))), createObject('storageAccountName', createObject('value', parameters('storageAccountName'))), createObject()), if(not(empty(parameters('storageAccountSkuName'))), createObject('storageAccountSkuName', createObject('value', parameters('storageAccountSkuName'))), createObject())), 'publicNetworkAccess', parameters('publicNetworkAccess'), 'requiredNsgRules', parameters('requiredNsgRules'), 'encryption', if(or(not(empty(parameters('customerManagedKey'))), not(empty(parameters('customerManagedKeyManagedDisk')))), createObject('entities', createObject('managedServices', if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.Keyvault', 'keyVaultProperties', createObject('keyVaultUri', reference('cMKKeyVault').vaultUri, 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/'))))), null()), 'managedDisk', if(not(empty(parameters('customerManagedKeyManagedDisk'))), createObject('keySource', 'Microsoft.Keyvault', 'keyVaultProperties', createObject('keyVaultUri', reference('cMKManagedDiskKeyVault').vaultUri, 'keyName', parameters('customerManagedKeyManagedDisk').keyName, 'keyVersion', if(not(empty(coalesce(tryGet(parameters('customerManagedKeyManagedDisk'), 'keyVersion'), ''))), parameters('customerManagedKeyManagedDisk').keyVersion, last(split(reference('cMKManagedDiskKeyVault::cMKKey').keyUriWithVersion, '/')))), 'rotationToLatestKeyVersionEnabled', coalesce(tryGet(parameters('customerManagedKeyManagedDisk'), 'rotationToLatestKeyVersionEnabled'), true())), null()))), null())), if(not(empty(parameters('privateStorageAccount'))), createObject('defaultStorageFirewall', parameters('privateStorageAccount'), 'accessConnector', createObject('id', parameters('accessConnectorResourceId'), 'identityType', 'SystemAssigned')), createObject()), if(not(empty(parameters('defaultCatalog'))), createObject('defaultCatalog', createObject('initialName', '', 'initialType', tryGet(parameters('defaultCatalog'), 'initialType'))), createObject()))]", "dependsOn": [ "cMKKeyVault", "cMKManagedDiskKeyVault" diff --git a/avm/res/databricks/workspace/tests/e2e/max/main.test.bicep b/avm/res/databricks/workspace/tests/e2e/max/main.test.bicep index b1745bfe1d0..3ee2aeb6e5e 100644 --- a/avm/res/databricks/workspace/tests/e2e/max/main.test.bicep +++ b/avm/res/databricks/workspace/tests/e2e/max/main.test.bicep @@ -188,6 +188,10 @@ module testDeployment '../../../main.bicep' = [ managedResourceGroupResourceId: '${subscription().id}/resourceGroups/rg-${resourceGroupName}-managed' requireInfrastructureEncryption: true vnetAddressPrefix: '10.100' + defaultCatalog: { + //initialName: '' Cannot be set to anything other than an empty string. {"code":"InvalidInitialCatalogName","message":"Currently custom initial catalog name is not supported. This capability will be added in future."} + initialType: 'UnityCatalog' // Choose between 'HiveCatalog' OR 'UnityCatalog' + } } dependsOn: [ nestedDependencies diff --git a/avm/res/databricks/workspace/version.json b/avm/res/databricks/workspace/version.json index 0f81d22abc4..e0a6c494b96 100644 --- a/avm/res/databricks/workspace/version.json +++ b/avm/res/databricks/workspace/version.json @@ -4,4 +4,4 @@ "pathFilters": [ "./main.json" ] -} \ No newline at end of file +} From 4ca0ccdedfb5c8f7443af708aa33907d7751164a Mon Sep 17 00:00:00 2001 From: "Jianing Wang (MSFT)" <141212663+jianingwang123@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:12:09 +0800 Subject: [PATCH 09/46] feat: Add new ptn modules `azd/apim-api` (#3206) ## Description Fixes [Azure/Azure-Verified-Modules/issues#1232](https://github.com/Azure/Azure-Verified-Modules/issues/1232) ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.azd.apim-api](https://github.com/jianingwang123/bicep-registry-modules/actions/workflows/avm.ptn.azd.apim-api.yml/badge.svg?branch=apim-api)](https://github.com/jianingwang123/bicep-registry-modules/actions/workflows/avm.ptn.azd.apim-api.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings @jongio for notification. --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .github/workflows/avm.ptn.azd.apim-api.yml | 88 +++++ avm/ptn/azd/apim-api/README.md | 217 ++++++++++++ avm/ptn/azd/apim-api/main.bicep | 168 ++++++++++ avm/ptn/azd/apim-api/main.json | 214 ++++++++++++ .../azd/apim-api/modules/apim-api-policy.xml | 92 ++++++ avm/ptn/azd/apim-api/modules/openapi.yaml | 312 ++++++++++++++++++ .../tests/e2e/defaults/dependencies.bicep | 84 +++++ .../tests/e2e/defaults/main.test.bicep | 63 ++++ avm/ptn/azd/apim-api/version.json | 7 + 11 files changed, 1247 insertions(+) create mode 100644 .github/workflows/avm.ptn.azd.apim-api.yml create mode 100644 avm/ptn/azd/apim-api/README.md create mode 100644 avm/ptn/azd/apim-api/main.bicep create mode 100644 avm/ptn/azd/apim-api/main.json create mode 100644 avm/ptn/azd/apim-api/modules/apim-api-policy.xml create mode 100644 avm/ptn/azd/apim-api/modules/openapi.yaml create mode 100644 avm/ptn/azd/apim-api/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/ptn/azd/apim-api/tests/e2e/defaults/main.test.bicep create mode 100644 avm/ptn/azd/apim-api/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c294eb5bc4a..69fd75e5c7b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -12,6 +12,7 @@ #/avm/ptn/avd-lza/management-plane/ @Azure/avm-ptn-avd-lza-managementplane-module-owners-bicep @Azure/avm-module-reviewers-bicep #/avm/ptn/avd-lza/networking/ @Azure/avm-ptn-avd-lza-networking-module-owners-bicep @Azure/avm-module-reviewers-bicep #/avm/ptn/avd-lza/session-hosts/ @Azure/avm-ptn-avd-lza-sessionhosts-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/ptn/azd/apim-api/ @Azure/avm-ptn-azd-apimapi-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/azd/container-apps/ @Azure/avm-ptn-azd-containerapps-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/azd/insights-dashboard/ @Azure/avm-ptn-azd-insightsdashboard-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/deployment-script/import-image-to-acr/ @Azure/avm-ptn-deploymentscript-importimagetoacr-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index bc5bdfbed51..76f4173ed13 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -47,6 +47,7 @@ body: # - "avm/ptn/avd-lza/management-plane" # - "avm/ptn/avd-lza/networking" # - "avm/ptn/avd-lza/session-hosts" + - "avm/ptn/azd/apim-api" - "avm/ptn/azd/container-apps" - "avm/ptn/azd/insights-dashboard" - "avm/ptn/deployment-script/import-image-to-acr" diff --git a/.github/workflows/avm.ptn.azd.apim-api.yml b/.github/workflows/avm.ptn.azd.apim-api.yml new file mode 100644 index 00000000000..f02556626cd --- /dev/null +++ b/.github/workflows/avm.ptn.azd.apim-api.yml @@ -0,0 +1,88 @@ +name: "avm.ptn.azd.apim-api" + +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" + - ".github/workflows/avm.ptn.azd.apim-api.yml" + - "avm/ptn/azd/apim-api/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/ptn/azd/apim-api" + workflowPath: ".github/workflows/avm.ptn.azd.apim-api.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 \ No newline at end of file diff --git a/avm/ptn/azd/apim-api/README.md b/avm/ptn/azd/apim-api/README.md new file mode 100644 index 00000000000..681638af793 --- /dev/null +++ b/avm/ptn/azd/apim-api/README.md @@ -0,0 +1,217 @@ +# avm/ptn/azd/apim-api `[Azd/ApimApi]` + +Creates and configure an API within an API Management service instance. + +**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.ApiManagement/service/apis` | [2022-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ApiManagement/2022-08-01/service/apis) | +| `Microsoft.ApiManagement/service/apis/diagnostics` | [2022-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ApiManagement/2022-08-01/service/apis/diagnostics) | +| `Microsoft.ApiManagement/service/apis/policies` | [2022-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ApiManagement/2022-08-01/service/apis/policies) | +| `Microsoft.Web/sites/config` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/ptn/azd/apim-api:`. + +- [Using only defaults](#example-1-using-only-defaults) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module apimApi 'br/public:avm/ptn/azd/apim-api:' = { + name: 'apimApiDeployment' + params: { + // Required parameters + apiBackendUrl: '' + apiDescription: 'api description' + apiDisplayName: 'apd-aapmin' + apiName: 'an-aapmin001' + apiPath: 'apipath-aapmin' + name: '' + webFrontendUrl: '' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "apiBackendUrl": { + "value": "" + }, + "apiDescription": { + "value": "api description" + }, + "apiDisplayName": { + "value": "apd-aapmin" + }, + "apiName": { + "value": "an-aapmin001" + }, + "apiPath": { + "value": "apipath-aapmin" + }, + "name": { + "value": "" + }, + "webFrontendUrl": { + "value": "" + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`apiBackendUrl`](#parameter-apibackendurl) | string | Absolute URL of the backend service implementing this API. | +| [`apiDescription`](#parameter-apidescription) | string | Description of the API. May include HTML formatting tags. | +| [`apiDisplayName`](#parameter-apidisplayname) | string | The Display Name of the API. | +| [`apiName`](#parameter-apiname) | string | Resource name to uniquely identify this API within the API Management service instance. | +| [`apiPath`](#parameter-apipath) | string | Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API. | +| [`name`](#parameter-name) | string | Name of the API Management service instance. | +| [`webFrontendUrl`](#parameter-webfrontendurl) | string | Absolute URL of the web frontend. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`apiAppName`](#parameter-apiappname) | string | Resource name for backend Web App or Function App. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | Location for all Resources. | + +### Parameter: `apiBackendUrl` + +Absolute URL of the backend service implementing this API. + +- Required: Yes +- Type: string + +### Parameter: `apiDescription` + +Description of the API. May include HTML formatting tags. + +- Required: Yes +- Type: string + +### Parameter: `apiDisplayName` + +The Display Name of the API. + +- Required: Yes +- Type: string + +### Parameter: `apiName` + +Resource name to uniquely identify this API within the API Management service instance. + +- Required: Yes +- Type: string + +### Parameter: `apiPath` + +Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API. + +- Required: Yes +- Type: string + +### Parameter: `name` + +Name of the API Management service instance. + +- Required: Yes +- Type: string + +### Parameter: `webFrontendUrl` + +Absolute URL of the web frontend. + +- Required: Yes +- Type: string + +### Parameter: `apiAppName` + +Resource name for backend Web App or Function App. + +- Required: No +- Type: string +- Default: `''` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `location` + +Location for all Resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `resourceGroupName` | string | The name of the resource group. | +| `serviceApiUri` | string | The complete URL for accessing the API. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/azd/apim-api/main.bicep b/avm/ptn/azd/apim-api/main.bicep new file mode 100644 index 00000000000..22c2fd5435a --- /dev/null +++ b/avm/ptn/azd/apim-api/main.bicep @@ -0,0 +1,168 @@ +metadata name = 'avm/ptn/azd/apim-api' +metadata description = '''Creates and configure an API within an API Management service instance. + +**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case.''' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the API Management service instance.') +param name string + +@description('Required. Resource name to uniquely identify this API within the API Management service instance.') +@minLength(1) +param apiName string + +@description('Required. The Display Name of the API.') +@minLength(1) +@maxLength(300) +param apiDisplayName string + +@description('Required. Description of the API. May include HTML formatting tags.') +@minLength(1) +param apiDescription string + +@description('Required. Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API.') +@minLength(1) +param apiPath string + +@description('Required. Absolute URL of the web frontend.') +param webFrontendUrl string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Required. Absolute URL of the backend service implementing this API.') +param apiBackendUrl string + +@description('Optional. Resource name for backend Web App or Function App.') +param apiAppName string = '' + +// ============== // +// Variables // +// ============== // + +var apiPolicyContent = replace(loadTextContent('modules/apim-api-policy.xml'), '{origin}', webFrontendUrl) + +// Necessary due to https://github.com/Azure/bicep/issues/9594 +// placeholderName is never deployed, it is merely used to make the child name validation pass +var appNameForBicep = !empty(apiAppName) ? apiAppName : 'placeholderName' + +// ============== // +// Resources // +// ============== // + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.ptn.azd-apimapi.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + 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' + } + } + } + } +} + +resource restApi 'Microsoft.ApiManagement/service/apis@2022-08-01' = { + name: apiName + parent: apimService + properties: { + description: apiDescription + displayName: apiDisplayName + path: apiPath + protocols: ['https'] + subscriptionRequired: false + type: 'http' + format: 'openapi' + serviceUrl: apiBackendUrl + value: loadTextContent('modules/openapi.yaml') + } +} + +resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2022-08-01' = { + name: 'policy' + parent: restApi + properties: { + format: 'rawxml' + value: apiPolicyContent + } +} + +resource apiDiagnostics 'Microsoft.ApiManagement/service/apis/diagnostics@2022-08-01' = { + name: 'applicationinsights' + parent: restApi + properties: { + alwaysLog: 'allErrors' + backend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + frontend: { + request: { + body: { + bytes: 1024 + } + } + response: { + body: { + bytes: 1024 + } + } + } + httpCorrelationProtocol: 'W3C' + logClientIp: true + loggerId: apimLogger.id + metrics: true + sampling: { + percentage: 100 + samplingType: 'fixed' + } + verbosity: 'verbose' + } +} + +resource apimService 'Microsoft.ApiManagement/service@2022-08-01' existing = { + name: name +} + +resource apiAppProperties 'Microsoft.Web/sites/config@2022-09-01' = if (!empty(apiAppName)) { + name: '${appNameForBicep}/web' + kind: 'string' + properties: { + apiManagementConfig: { + id: '${apimService.id}/apis/${apiName}' + } + } +} + +resource apimLogger 'Microsoft.ApiManagement/service/loggers@2022-08-01' existing = { + name: 'app-insights-logger' + parent: apimService +} + +// ============ // +// Outputs // +// ============ // + +@description('The name of the resource group.') +output resourceGroupName string = resourceGroup().name + +@description('The complete URL for accessing the API.') +output serviceApiUri string = '${apimService.properties.gatewayUrl}/${apiPath}' diff --git a/avm/ptn/azd/apim-api/main.json b/avm/ptn/azd/apim-api/main.json new file mode 100644 index 00000000000..dacd440b8a4 --- /dev/null +++ b/avm/ptn/azd/apim-api/main.json @@ -0,0 +1,214 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "7935494033060946539" + }, + "name": "avm/ptn/azd/apim-api", + "description": "Creates and configure an API within an API Management service instance.\n\n**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the API Management service instance." + } + }, + "apiName": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Required. Resource name to uniquely identify this API within the API Management service instance." + } + }, + "apiDisplayName": { + "type": "string", + "minLength": 1, + "maxLength": 300, + "metadata": { + "description": "Required. The Display Name of the API." + } + }, + "apiDescription": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Required. Description of the API. May include HTML formatting tags." + } + }, + "apiPath": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Required. Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API." + } + }, + "webFrontendUrl": { + "type": "string", + "metadata": { + "description": "Required. Absolute URL of the web frontend." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "apiBackendUrl": { + "type": "string", + "metadata": { + "description": "Required. Absolute URL of the backend service implementing this API." + } + }, + "apiAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource name for backend Web App or Function App." + } + } + }, + "variables": { + "$fxv#0": "\n\n \n \n \n \n \n {origin}\n \n \n PUT\n GET\n POST\n DELETE\n PATCH\n \n \n

*
\n \n \n
*
\n
\n \n \n \n \n \n \n \n Call to the @(context.Api.Name)\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n = 200 && context.Response.StatusCode < 300)\">\n \n \n \n \n \n \n \n = 400 && context.Response.StatusCode < 600)\">\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Failed to process the @(context.Api.Name)\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n An unexpected error has occurred.\n \n \n", + "$fxv#1": "openapi: 3.0.0\ninfo:\n description: Simple Todo API\n version: 3.0.0\n title: Simple Todo API\n contact:\n email: azdevteam@microsoft.com\n\ncomponents:\n schemas:\n TodoItem:\n type: object\n required:\n - listId\n - name\n - description\n description: A task that needs to be completed\n properties:\n id:\n type: string\n listId:\n type: string\n name:\n type: string\n description:\n type: string\n state:\n $ref: \"#/components/schemas/TodoState\"\n dueDate:\n type: string\n format: date-time\n completedDate:\n type: string\n format: date-time\n TodoList:\n type: object\n required:\n - name\n properties:\n id:\n type: string\n name:\n type: string\n description:\n type: string\n description: \" A list of related Todo items\"\n TodoState:\n type: string\n enum:\n - todo\n - inprogress\n - done\n parameters:\n listId:\n in: path\n required: true\n name: listId\n description: The Todo list unique identifier\n schema:\n type: string\n itemId:\n in: path\n required: true\n name: itemId\n description: The Todo item unique identifier\n schema:\n type: string\n state:\n in: path\n required: true\n name: state\n description: The Todo item state\n schema:\n $ref: \"#/components/schemas/TodoState\"\n top:\n in: query\n required: false\n name: top\n description: The max number of items to returns in a result\n schema:\n type: number\n default: 20\n skip:\n in: query\n required: false\n name: skip\n description: The number of items to skip within the results\n schema:\n type: number\n default: 0\n\n requestBodies:\n TodoList:\n description: The Todo List\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoList\"\n TodoItem:\n description: The Todo Item\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoItem\"\n\n responses:\n TodoList:\n description: A Todo list result\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoList\"\n TodoListArray:\n description: An array of Todo lists\n content:\n application/json:\n schema:\n type: array\n items:\n $ref: \"#/components/schemas/TodoList\"\n TodoItem:\n description: A Todo item result\n content:\n application/json:\n schema:\n $ref: \"#/components/schemas/TodoItem\"\n TodoItemArray:\n description: An array of Todo items\n content:\n application/json:\n schema:\n type: array\n items:\n $ref: \"#/components/schemas/TodoItem\"\n\npaths:\n /lists:\n get:\n operationId: GetLists\n summary: Gets an array of Todo lists\n tags:\n - Lists\n parameters:\n - $ref: \"#/components/parameters/top\"\n - $ref: \"#/components/parameters/skip\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoListArray\"\n post:\n operationId: CreateList\n summary: Creates a new Todo list\n tags:\n - Lists\n requestBody:\n $ref: \"#/components/requestBodies/TodoList\"\n responses:\n 201:\n $ref: \"#/components/responses/TodoList\"\n 400:\n description: Invalid request schema\n /lists/{listId}:\n get:\n operationId: GetListById\n summary: Gets a Todo list by unique identifier\n tags:\n - Lists\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoList\"\n 404:\n description: Todo list not found\n put:\n operationId: UpdateListById\n summary: Updates a Todo list by unique identifier\n tags:\n - Lists\n requestBody:\n $ref: \"#/components/requestBodies/TodoList\"\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoList\"\n 404:\n description: Todo list not found\n 400:\n description: Todo list is invalid\n delete:\n operationId: DeleteListById\n summary: Deletes a Todo list by unique identifier\n tags:\n - Lists\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 204:\n description: Todo list deleted successfully\n 404:\n description: Todo list not found\n /lists/{listId}/items:\n post:\n operationId: CreateItem\n summary: Creates a new Todo item within a list\n tags:\n - Items\n requestBody:\n $ref: \"#/components/requestBodies/TodoItem\"\n parameters:\n - $ref: \"#/components/parameters/listId\"\n responses:\n 201:\n $ref: \"#/components/responses/TodoItem\"\n 404:\n description: Todo list not found\n get:\n operationId: GetItemsByListId\n summary: Gets Todo items within the specified list\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/top\"\n - $ref: \"#/components/parameters/skip\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItemArray\"\n 404:\n description: Todo list not found\n /lists/{listId}/items/{itemId}:\n get:\n operationId: GetItemById\n summary: Gets a Todo item by unique identifier\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/itemId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItem\"\n 404:\n description: Todo list or item not found\n put:\n operationId: UpdateItemById\n summary: Updates a Todo item by unique identifier\n tags:\n - Items\n requestBody:\n $ref: \"#/components/requestBodies/TodoItem\"\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/itemId\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItem\"\n 400:\n description: Todo item is invalid\n 404:\n description: Todo list or item not found\n delete:\n operationId: DeleteItemById\n summary: Deletes a Todo item by unique identifier\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/itemId\"\n responses:\n 204:\n description: Todo item deleted successfully\n 404:\n description: Todo list or item not found\n /lists/{listId}/items/state/{state}:\n get:\n operationId: GetItemsByListIdAndState\n summary: Gets a list of Todo items of a specific state\n tags:\n - Items\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/state\"\n - $ref: \"#/components/parameters/top\"\n - $ref: \"#/components/parameters/skip\"\n responses:\n 200:\n $ref: \"#/components/responses/TodoItemArray\"\n 404:\n description: Todo list or item not found\n put:\n operationId: UpdateItemsStateByListId\n summary: Changes the state of the specified list items\n tags:\n - Items\n requestBody:\n description: unique identifiers of the Todo items to update\n content:\n application/json:\n schema:\n type: array\n items:\n description: The Todo item unique identifier\n type: string\n parameters:\n - $ref: \"#/components/parameters/listId\"\n - $ref: \"#/components/parameters/state\"\n responses:\n 204:\n description: Todo items updated\n 400:\n description: Update request is invalid", + "apiPolicyContent": "[replace(variables('$fxv#0'), '{origin}', parameters('webFrontendUrl'))]", + "appNameForBicep": "[if(not(empty(parameters('apiAppName'))), parameters('apiAppName'), 'placeholderName')]" + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.ptn.azd-apimapi.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + { + "type": "Microsoft.ApiManagement/service/apis", + "apiVersion": "2022-08-01", + "name": "[format('{0}/{1}', parameters('name'), parameters('apiName'))]", + "properties": { + "description": "[parameters('apiDescription')]", + "displayName": "[parameters('apiDisplayName')]", + "path": "[parameters('apiPath')]", + "protocols": [ + "https" + ], + "subscriptionRequired": false, + "type": "http", + "format": "openapi", + "serviceUrl": "[parameters('apiBackendUrl')]", + "value": "[variables('$fxv#1')]" + } + }, + { + "type": "Microsoft.ApiManagement/service/apis/policies", + "apiVersion": "2022-08-01", + "name": "[format('{0}/{1}/{2}', parameters('name'), parameters('apiName'), 'policy')]", + "properties": { + "format": "rawxml", + "value": "[variables('apiPolicyContent')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/apis', parameters('name'), parameters('apiName'))]" + ] + }, + { + "type": "Microsoft.ApiManagement/service/apis/diagnostics", + "apiVersion": "2022-08-01", + "name": "[format('{0}/{1}/{2}', parameters('name'), parameters('apiName'), 'applicationinsights')]", + "properties": { + "alwaysLog": "allErrors", + "backend": { + "request": { + "body": { + "bytes": 1024 + } + }, + "response": { + "body": { + "bytes": 1024 + } + } + }, + "frontend": { + "request": { + "body": { + "bytes": 1024 + } + }, + "response": { + "body": { + "bytes": 1024 + } + } + }, + "httpCorrelationProtocol": "W3C", + "logClientIp": true, + "loggerId": "[resourceId('Microsoft.ApiManagement/service/loggers', parameters('name'), 'app-insights-logger')]", + "metrics": true, + "sampling": { + "percentage": 100, + "samplingType": "fixed" + }, + "verbosity": "verbose" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/apis', parameters('name'), parameters('apiName'))]" + ] + }, + { + "condition": "[not(empty(parameters('apiAppName')))]", + "type": "Microsoft.Web/sites/config", + "apiVersion": "2022-09-01", + "name": "[format('{0}/web', variables('appNameForBicep'))]", + "kind": "string", + "properties": { + "apiManagementConfig": { + "id": "[format('{0}/apis/{1}', resourceId('Microsoft.ApiManagement/service', parameters('name')), parameters('apiName'))]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group." + }, + "value": "[resourceGroup().name]" + }, + "serviceApiUri": { + "type": "string", + "metadata": { + "description": "The complete URL for accessing the API." + }, + "value": "[format('{0}/{1}', reference(resourceId('Microsoft.ApiManagement/service', parameters('name')), '2022-08-01').gatewayUrl, parameters('apiPath'))]" + } + } +} \ No newline at end of file diff --git a/avm/ptn/azd/apim-api/modules/apim-api-policy.xml b/avm/ptn/azd/apim-api/modules/apim-api-policy.xml new file mode 100644 index 00000000000..3c42f53fce6 --- /dev/null +++ b/avm/ptn/azd/apim-api/modules/apim-api-policy.xml @@ -0,0 +1,92 @@ + + + + + + + + {origin} + + + PUT + GET + POST + DELETE + PATCH + + +
*
+
+ +
*
+
+
+ + + + + + + Call to the @(context.Api.Name) + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Failed to process the @(context.Api.Name) + + + + + + + + + + + + + + + + + + An unexpected error has occurred. + + +
\ No newline at end of file diff --git a/avm/ptn/azd/apim-api/modules/openapi.yaml b/avm/ptn/azd/apim-api/modules/openapi.yaml new file mode 100644 index 00000000000..e28b058860d --- /dev/null +++ b/avm/ptn/azd/apim-api/modules/openapi.yaml @@ -0,0 +1,312 @@ +openapi: 3.0.0 +info: + description: Simple Todo API + version: 3.0.0 + title: Simple Todo API + contact: + email: azdevteam@microsoft.com + +components: + schemas: + TodoItem: + type: object + required: + - listId + - name + - description + description: A task that needs to be completed + properties: + id: + type: string + listId: + type: string + name: + type: string + description: + type: string + state: + $ref: "#/components/schemas/TodoState" + dueDate: + type: string + format: date-time + completedDate: + type: string + format: date-time + TodoList: + type: object + required: + - name + properties: + id: + type: string + name: + type: string + description: + type: string + description: " A list of related Todo items" + TodoState: + type: string + enum: + - todo + - inprogress + - done + parameters: + listId: + in: path + required: true + name: listId + description: The Todo list unique identifier + schema: + type: string + itemId: + in: path + required: true + name: itemId + description: The Todo item unique identifier + schema: + type: string + state: + in: path + required: true + name: state + description: The Todo item state + schema: + $ref: "#/components/schemas/TodoState" + top: + in: query + required: false + name: top + description: The max number of items to returns in a result + schema: + type: number + default: 20 + skip: + in: query + required: false + name: skip + description: The number of items to skip within the results + schema: + type: number + default: 0 + + requestBodies: + TodoList: + description: The Todo List + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: The Todo Item + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + + responses: + TodoList: + description: A Todo list result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoList" + TodoListArray: + description: An array of Todo lists + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoList" + TodoItem: + description: A Todo item result + content: + application/json: + schema: + $ref: "#/components/schemas/TodoItem" + TodoItemArray: + description: An array of Todo items + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TodoItem" + +paths: + /lists: + get: + operationId: GetLists + summary: Gets an array of Todo lists + tags: + - Lists + parameters: + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoListArray" + post: + operationId: CreateList + summary: Creates a new Todo list + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + responses: + 201: + $ref: "#/components/responses/TodoList" + 400: + description: Invalid request schema + /lists/{listId}: + get: + operationId: GetListById + summary: Gets a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + put: + operationId: UpdateListById + summary: Updates a Todo list by unique identifier + tags: + - Lists + requestBody: + $ref: "#/components/requestBodies/TodoList" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 200: + $ref: "#/components/responses/TodoList" + 404: + description: Todo list not found + 400: + description: Todo list is invalid + delete: + operationId: DeleteListById + summary: Deletes a Todo list by unique identifier + tags: + - Lists + parameters: + - $ref: "#/components/parameters/listId" + responses: + 204: + description: Todo list deleted successfully + 404: + description: Todo list not found + /lists/{listId}/items: + post: + operationId: CreateItem + summary: Creates a new Todo item within a list + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + responses: + 201: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list not found + get: + operationId: GetItemsByListId + summary: Gets Todo items within the specified list + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list not found + /lists/{listId}/items/{itemId}: + get: + operationId: GetItemById + summary: Gets a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemById + summary: Updates a Todo item by unique identifier + tags: + - Items + requestBody: + $ref: "#/components/requestBodies/TodoItem" + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 200: + $ref: "#/components/responses/TodoItem" + 400: + description: Todo item is invalid + 404: + description: Todo list or item not found + delete: + operationId: DeleteItemById + summary: Deletes a Todo item by unique identifier + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/itemId" + responses: + 204: + description: Todo item deleted successfully + 404: + description: Todo list or item not found + /lists/{listId}/items/state/{state}: + get: + operationId: GetItemsByListIdAndState + summary: Gets a list of Todo items of a specific state + tags: + - Items + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + - $ref: "#/components/parameters/top" + - $ref: "#/components/parameters/skip" + responses: + 200: + $ref: "#/components/responses/TodoItemArray" + 404: + description: Todo list or item not found + put: + operationId: UpdateItemsStateByListId + summary: Changes the state of the specified list items + tags: + - Items + requestBody: + description: unique identifiers of the Todo items to update + content: + application/json: + schema: + type: array + items: + description: The Todo item unique identifier + type: string + parameters: + - $ref: "#/components/parameters/listId" + - $ref: "#/components/parameters/state" + responses: + 204: + description: Todo items updated + 400: + description: Update request is invalid \ No newline at end of file diff --git a/avm/ptn/azd/apim-api/tests/e2e/defaults/dependencies.bicep b/avm/ptn/azd/apim-api/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 00000000000..fd100a6b643 --- /dev/null +++ b/avm/ptn/azd/apim-api/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,84 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the API Management service to create.') +param apimServiceName string + +@description('Required. The name of the owner of the API Management service.') +param publisherName string + +@description('Required. The name of the App Service to create.') +param appServiceName string + +@description('Required. The name of the App Service Plan to create.') +param appServicePlanName string + +@description('Required. The name of the Log Analytics Workspace to create.') +param logAnalyticsWorkspaceName string + +@description('Required. The name of the Application Insights to create.') +param applicationInsightsName string + +module apimService 'br/public:avm/res/api-management/service:0.4.0' = { + name: 'serviceDeployment' + params: { + name: apimServiceName + publisherEmail: 'apimgmt-noreply@mail.windowsazure.com' + publisherName: publisherName + location: location + loggers: [ + { + loggerType: 'applicationInsights' + name: 'app-insights-logger' + credentials: { + instrumentationKey: applicationInsights.properties.InstrumentationKey + } + resourceId: applicationInsights.id + } + ] + } +} + +module app 'br/public:avm/res/web/site:0.6.0' = { + name: 'siteDeployment' + params: { + kind: 'app' + name: appServiceName + serverFarmResourceId: appServicePlan.outputs.resourceId + } +} + +module appServicePlan 'br/public:avm/res/web/serverfarm:0.2.2' = { + name: 'serverDeployment' + params: { + name: appServicePlanName + skuCapacity: 2 + skuName: 'S1' + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { + name: logAnalyticsWorkspaceName + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } +} + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { + name: applicationInsightsName + location: location + kind: 'web' + properties: { + Application_Type: 'web' + WorkspaceResourceId: logAnalyticsWorkspace.id + } +} + +@description('The default hostname of the site.') +output siteHostName string = 'https://${app.outputs.defaultHostname}' + +@description('The name of the API Management service.') +output apimName string = apimService.outputs.name diff --git a/avm/ptn/azd/apim-api/tests/e2e/defaults/main.test.bicep b/avm/ptn/azd/apim-api/tests/e2e/defaults/main.test.bicep new file mode 100644 index 00000000000..27f5f5a9dce --- /dev/null +++ b/avm/ptn/azd/apim-api/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,63 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-apim-api-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'aapmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + appServicePlanName: 'dep-${namePrefix}-sp-${serviceShort}' + appServiceName: 'dep-${namePrefix}-aps-${serviceShort}' + apimServiceName: '${namePrefix}-as-${serviceShort}001' + publisherName: 'dep-${namePrefix}-pn-x-001' + applicationInsightsName: 'dep-${namePrefix}-ais-${serviceShort}' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + location: resourceLocation + name: nestedDependencies.outputs.apimName + apiDisplayName: '${namePrefix}-apd-${serviceShort}' + apiPath: '${namePrefix}-apipath-${serviceShort}' + webFrontendUrl: nestedDependencies.outputs.siteHostName + apiBackendUrl: nestedDependencies.outputs.siteHostName + apiDescription: 'api description' + apiName: '${namePrefix}-an-${serviceShort}001' + } +} diff --git a/avm/ptn/azd/apim-api/version.json b/avm/ptn/azd/apim-api/version.json new file mode 100644 index 00000000000..8def869edeb --- /dev/null +++ b/avm/ptn/azd/apim-api/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 97f616fd68c1366d2d062052edec35dc12defd67 Mon Sep 17 00:00:00 2001 From: Buddy <38195643+tsc-buddy@users.noreply.github.com> Date: Wed, 11 Sep 2024 23:09:43 +1200 Subject: [PATCH 10/46] feat: Service Bus Namespace AZ Resiliency Updates (#3248) ## Description The changes in this PR address the requirement for Zone Redundant configurations by default. ## Pipeline Reference | Pipeline | | -------- | |[![avm.res.service-bus.namespace](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml/badge.svg?branch=feat%2Fsb-az-defaults)](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml)| ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/service-bus/namespace/README.md | 105 +++++++++--------- avm/res/service-bus/namespace/main.bicep | 11 +- avm/res/service-bus/namespace/main.json | 12 +- .../tests/e2e/defaults/main.test.bicep | 3 +- .../namespace/tests/e2e/max/main.test.bicep | 1 - .../tests/e2e/waf-aligned/main.test.bicep | 1 - avm/res/service-bus/namespace/version.json | 2 +- 7 files changed, 71 insertions(+), 64 deletions(-) diff --git a/avm/res/service-bus/namespace/README.md b/avm/res/service-bus/namespace/README.md index 6cee870109c..0e05ec12f12 100644 --- a/avm/res/service-bus/namespace/README.md +++ b/avm/res/service-bus/namespace/README.md @@ -59,11 +59,12 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { params: { // Required parameters name: 'sbnmin001' - skuObject: { - name: 'Basic' - } // Non-required parameters location: '' + skuObject: { + capacity: 2 + name: 'Premium' + } } } ``` @@ -84,14 +85,15 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "name": { "value": "sbnmin001" }, - "skuObject": { - "value": { - "name": "Basic" - } - }, // Non-required parameters "location": { "value": "" + }, + "skuObject": { + "value": { + "capacity": 2, + "name": "Premium" + } } } } @@ -115,10 +117,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { params: { // Required parameters name: 'sbnencr001' - skuObject: { - capacity: 1 - name: 'Premium' - } // Non-required parameters customerManagedKey: { keyName: '' @@ -132,6 +130,10 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { '' ] } + skuObject: { + capacity: 1 + name: 'Premium' + } } } ``` @@ -152,12 +154,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "name": { "value": "sbnencr001" }, - "skuObject": { - "value": { - "capacity": 1, - "name": "Premium" - } - }, // Non-required parameters "customerManagedKey": { "value": { @@ -176,6 +172,12 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "" ] } + }, + "skuObject": { + "value": { + "capacity": 1, + "name": "Premium" + } } } } @@ -199,10 +201,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { params: { // Required parameters name: 'sbnmax001' - skuObject: { - capacity: 16 - name: 'Premium' - } // Non-required parameters authorizationRules: [ { @@ -382,6 +380,10 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { roleDefinitionIdOrName: '' } ] + skuObject: { + capacity: 16 + name: 'Premium' + } tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' @@ -431,7 +433,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { ] } ] - zoneRedundant: true } } ``` @@ -452,12 +453,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "name": { "value": "sbnmax001" }, - "skuObject": { - "value": { - "capacity": 16, - "name": "Premium" - } - }, // Non-required parameters "authorizationRules": { "value": [ @@ -663,6 +658,12 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { } ] }, + "skuObject": { + "value": { + "capacity": 16, + "name": "Premium" + } + }, "tags": { "value": { "Environment": "Non-Prod", @@ -715,9 +716,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { ] } ] - }, - "zoneRedundant": { - "value": true } } } @@ -741,10 +739,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { params: { // Required parameters name: 'sbnwaf001' - skuObject: { - capacity: 2 - name: 'Premium' - } // Non-required parameters authorizationRules: [ { @@ -850,6 +844,10 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { } ] roleAssignments: [] + skuObject: { + capacity: 2 + name: 'Premium' + } tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' @@ -878,7 +876,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { roleAssignments: [] } ] - zoneRedundant: true } } ``` @@ -899,12 +896,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "name": { "value": "sbnwaf001" }, - "skuObject": { - "value": { - "capacity": 2, - "name": "Premium" - } - }, // Non-required parameters "authorizationRules": { "value": [ @@ -1036,6 +1027,12 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "roleAssignments": { "value": [] }, + "skuObject": { + "value": { + "capacity": 2, + "name": "Premium" + } + }, "tags": { "value": { "Environment": "Non-Prod", @@ -1067,9 +1064,6 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { "roleAssignments": [] } ] - }, - "zoneRedundant": { - "value": true } } } @@ -1085,7 +1079,7 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { | Parameter | Type | Description | | :-- | :-- | :-- | | [`name`](#parameter-name) | string | Name of the Service Bus Namespace. | -| [`skuObject`](#parameter-skuobject) | object | The SKU of the Service Bus Namespace. | +| [`skuObject`](#parameter-skuobject) | object | The SKU of the Service Bus Namespace. Defaulted to Premium for ZoneRedundant configurations by default. | **Optional parameters** @@ -1112,7 +1106,7 @@ module namespace 'br/public:avm/res/service-bus/namespace:' = { | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`topics`](#parameter-topics) | array | The topics to create in the service bus namespace. | -| [`zoneRedundant`](#parameter-zoneredundant) | bool | Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones. | +| [`zoneRedundant`](#parameter-zoneredundant) | bool | Enabled by default in order to align with resiliency best practices, thus requires Premium SKU. | ### Parameter: `name` @@ -1123,10 +1117,17 @@ Name of the Service Bus Namespace. ### Parameter: `skuObject` -The SKU of the Service Bus Namespace. +The SKU of the Service Bus Namespace. Defaulted to Premium for ZoneRedundant configurations by default. -- Required: Yes +- Required: No - Type: object +- Default: + ```Bicep + { + capacity: 2 + name: 'Premium' + } + ``` **Required parameters** @@ -3069,11 +3070,11 @@ Value that indicates whether the topic supports ordering. ### Parameter: `zoneRedundant` -Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones. +Enabled by default in order to align with resiliency best practices, thus requires Premium SKU. - Required: No - Type: bool -- Default: `False` +- Default: `True` ## Outputs diff --git a/avm/res/service-bus/namespace/main.bicep b/avm/res/service-bus/namespace/main.bicep index 08ecf97e725..7b00c06d4cb 100644 --- a/avm/res/service-bus/namespace/main.bicep +++ b/avm/res/service-bus/namespace/main.bicep @@ -9,11 +9,14 @@ param name string @description('Optional. Location for all resources.') param location string = resourceGroup().location -@description('Required. The SKU of the Service Bus Namespace.') -param skuObject skuType +@description('Required. The SKU of the Service Bus Namespace. Defaulted to Premium for ZoneRedundant configurations by default.') +param skuObject skuType = { + name: 'Premium' + capacity: 2 +} -@description('Optional. Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones.') -param zoneRedundant bool = false +@description('Optional. Enabled by default in order to align with resiliency best practices, thus requires Premium SKU.') +param zoneRedundant bool = true @allowed([ '1.0' diff --git a/avm/res/service-bus/namespace/main.json b/avm/res/service-bus/namespace/main.json index b3917916f07..8b30c416be5 100644 --- a/avm/res/service-bus/namespace/main.json +++ b/avm/res/service-bus/namespace/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "16628890374295506516" + "templateHash": "6397771352503979306" }, "name": "Service Bus Namespaces", "description": "This module deploys a Service Bus Namespace.", @@ -1137,15 +1137,19 @@ }, "skuObject": { "$ref": "#/definitions/skuType", + "defaultValue": { + "name": "Premium", + "capacity": 2 + }, "metadata": { - "description": "Required. The SKU of the Service Bus Namespace." + "description": "Required. The SKU of the Service Bus Namespace. Defaulted to Premium for ZoneRedundant configurations by default." } }, "zoneRedundant": { "type": "bool", - "defaultValue": false, + "defaultValue": true, "metadata": { - "description": "Optional. Enabling this property creates a Premium Service Bus Namespace in regions supported availability zones." + "description": "Optional. Enabled by default in order to align with resiliency best practices, thus requires Premium SKU." } }, "minimumTlsVersion": { diff --git a/avm/res/service-bus/namespace/tests/e2e/defaults/main.test.bicep b/avm/res/service-bus/namespace/tests/e2e/defaults/main.test.bicep index 3d75b0c635e..067d0c6f69d 100644 --- a/avm/res/service-bus/namespace/tests/e2e/defaults/main.test.bicep +++ b/avm/res/service-bus/namespace/tests/e2e/defaults/main.test.bicep @@ -44,7 +44,8 @@ module testDeployment '../../../main.bicep' = [ name: '${namePrefix}${serviceShort}001' location: resourceLocation skuObject: { - name: 'Basic' + name: 'Premium' + capacity: 2 } } } diff --git a/avm/res/service-bus/namespace/tests/e2e/max/main.test.bicep b/avm/res/service-bus/namespace/tests/e2e/max/main.test.bicep index 75f7aac4950..fcf8d82aee8 100644 --- a/avm/res/service-bus/namespace/tests/e2e/max/main.test.bicep +++ b/avm/res/service-bus/namespace/tests/e2e/max/main.test.bicep @@ -76,7 +76,6 @@ module testDeployment '../../../main.bicep' = [ capacity: 16 } premiumMessagingPartitions: 1 - zoneRedundant: true tags: { 'hidden-title': 'This is visible in the resource name' Environment: 'Non-Prod' diff --git a/avm/res/service-bus/namespace/tests/e2e/waf-aligned/main.test.bicep b/avm/res/service-bus/namespace/tests/e2e/waf-aligned/main.test.bicep index f40c29ea502..21605340801 100644 --- a/avm/res/service-bus/namespace/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/service-bus/namespace/tests/e2e/waf-aligned/main.test.bicep @@ -76,7 +76,6 @@ module testDeployment '../../../main.bicep' = [ capacity: 2 } premiumMessagingPartitions: 1 - zoneRedundant: true tags: { 'hidden-title': 'This is visible in the resource name' Environment: 'Non-Prod' diff --git a/avm/res/service-bus/namespace/version.json b/avm/res/service-bus/namespace/version.json index 9a9a06e8978..6b6be93891f 100644 --- a/avm/res/service-bus/namespace/version.json +++ b/avm/res/service-bus/namespace/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.8", + "version": "0.9", "pathFilters": [ "./main.json" ] From 4d7c891a3323450baed005c4893c1e75772502c6 Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:25:36 +0300 Subject: [PATCH 11/46] fix: [avm-ptn-lz-sub-vending] Add improvements to the vwan connections naming convention (#3110) ## Description Fixes #3085 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.lz.sub-vending](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.lz.sub-vending.yml/badge.svg?branch=avm-ptn-lz-sub-vending-vwan-connection-name)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.lz.sub-vending.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [X] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/lz/sub-vending/main.json | 70 +++++++++---------- .../modules/readTagsResourceGroup.bicep | 2 +- .../modules/readTagsSubscription.bicep | 2 +- .../modules/subResourceWrapper.bicep | 2 +- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/avm/ptn/lz/sub-vending/main.json b/avm/ptn/lz/sub-vending/main.json index 7d14c11f82d..35687dd6eac 100644 --- a/avm/ptn/lz/sub-vending/main.json +++ b/avm/ptn/lz/sub-vending/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "11010234208010675398" + "version": "0.29.47.4906", + "templateHash": "2409780926109914899" }, "name": "Sub-vending", "description": "This module deploys a subscription to accelerate deployment of landing zones. For more information on how to use it, please visit this [Wiki](https://github.com/Azure/bicep-lz-vending/wiki).", @@ -445,8 +445,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "3306118610933947345" + "version": "0.29.47.4906", + "templateHash": "3759867594724381121" } }, "parameters": { @@ -652,8 +652,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "15097801658394168144" + "version": "0.29.47.4906", + "templateHash": "10394721304346895394" }, "name": "`/subResourcesWrapper/deploy.bicep` Parameters", "description": "This module is used by the [`bicep-lz-vending`](https://aka.ms/sub-vending/bicep) module to help orchestrate the deployment", @@ -992,7 +992,7 @@ "virtualWanHubName": "[if(not(empty(variables('virtualHubResourceIdChecked'))), split(variables('virtualHubResourceIdChecked'), '/')[8], '')]", "virtualWanHubSubscriptionId": "[if(not(empty(variables('virtualHubResourceIdChecked'))), split(variables('virtualHubResourceIdChecked'), '/')[2], '')]", "virtualWanHubResourceGroupName": "[if(not(empty(variables('virtualHubResourceIdChecked'))), split(variables('virtualHubResourceIdChecked'), '/')[4], '')]", - "virtualWanHubConnectionName": "[format('vhc-{0}', guid(variables('virtualHubResourceIdChecked'), parameters('virtualNetworkName'), parameters('virtualNetworkResourceGroupName'), parameters('virtualNetworkLocation'), parameters('subscriptionId')))]", + "virtualWanHubConnectionName": "[format('vhc-{0}-{1}', parameters('virtualNetworkName'), substring(guid(variables('virtualHubResourceIdChecked'), parameters('virtualNetworkName'), parameters('virtualNetworkResourceGroupName'), parameters('virtualNetworkLocation'), parameters('subscriptionId')), 0, 5))]", "virtualWanHubConnectionAssociatedRouteTable": "[if(not(empty(parameters('virtualNetworkVwanAssociatedRouteTableResourceId'))), parameters('virtualNetworkVwanAssociatedRouteTableResourceId'), format('{0}/hubRouteTables/defaultRouteTable', variables('virtualHubResourceIdChecked')))]", "virutalWanHubDefaultRouteTableId": { "id": "[format('{0}/hubRouteTables/defaultRouteTable', variables('virtualHubResourceIdChecked'))]" @@ -1028,8 +1028,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "9313475896614087193" + "version": "0.29.47.4906", + "templateHash": "13961984943235030681" } }, "parameters": { @@ -1086,8 +1086,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "15181721731905574940" + "version": "0.29.47.4906", + "templateHash": "6592820624705341105" } }, "parameters": { @@ -1146,8 +1146,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "2065450289597496523" + "version": "0.29.47.4906", + "templateHash": "3589833223987550845" } }, "parameters": { @@ -1202,8 +1202,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "4876221897054252048" + "version": "0.29.47.4906", + "templateHash": "15687156082548283745" } }, "parameters": { @@ -1222,7 +1222,7 @@ "metadata": { "description": "Tags currently applied to the subscription level" }, - "value": "[if(contains(reference(subscriptionResourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), reference(subscriptionResourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01').tags, createObject())]" + "value": "[coalesce(tryGet(reference(subscriptionResourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), createObject())]" } } } @@ -1280,8 +1280,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "10772346649778391302" + "version": "0.29.47.4906", + "templateHash": "2885361202003966670" } }, "parameters": { @@ -1335,8 +1335,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "943757300004366392" + "version": "0.29.47.4906", + "templateHash": "4327209924100539632" } }, "parameters": { @@ -1355,7 +1355,7 @@ "metadata": { "description": "Tags currently applied to the subscription level" }, - "value": "[if(contains(reference(resourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), reference(resourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01').tags, createObject())]" + "value": "[coalesce(tryGet(reference(resourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), createObject())]" } } } @@ -1915,8 +1915,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "15181721731905574940" + "version": "0.29.47.4906", + "templateHash": "6592820624705341105" } }, "parameters": { @@ -1975,8 +1975,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "2065450289597496523" + "version": "0.29.47.4906", + "templateHash": "3589833223987550845" } }, "parameters": { @@ -2031,8 +2031,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "4876221897054252048" + "version": "0.29.47.4906", + "templateHash": "15687156082548283745" } }, "parameters": { @@ -2051,7 +2051,7 @@ "metadata": { "description": "Tags currently applied to the subscription level" }, - "value": "[if(contains(reference(subscriptionResourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), reference(subscriptionResourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01').tags, createObject())]" + "value": "[coalesce(tryGet(reference(subscriptionResourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), createObject())]" } } } @@ -2109,8 +2109,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "10772346649778391302" + "version": "0.29.47.4906", + "templateHash": "2885361202003966670" } }, "parameters": { @@ -2164,8 +2164,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "943757300004366392" + "version": "0.29.47.4906", + "templateHash": "4327209924100539632" } }, "parameters": { @@ -2184,7 +2184,7 @@ "metadata": { "description": "Tags currently applied to the subscription level" }, - "value": "[if(contains(reference(resourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), reference(resourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01').tags, createObject())]" + "value": "[coalesce(tryGet(reference(resourceId('Microsoft.Resources/tags', parameters('name')), '2019-10-01'), 'tags'), createObject())]" } } } @@ -3487,8 +3487,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "15855049387961116888" + "version": "0.29.47.4906", + "templateHash": "3320668363507906643" } }, "parameters": { diff --git a/avm/ptn/lz/sub-vending/modules/readTagsResourceGroup.bicep b/avm/ptn/lz/sub-vending/modules/readTagsResourceGroup.bicep index 0f3301f9741..b633ed65750 100644 --- a/avm/ptn/lz/sub-vending/modules/readTagsResourceGroup.bicep +++ b/avm/ptn/lz/sub-vending/modules/readTagsResourceGroup.bicep @@ -6,4 +6,4 @@ resource tags 'Microsoft.Resources/tags@2019-10-01' existing = { } @description('Tags currently applied to the subscription level') -output existingTags object = contains(tags.properties, 'tags') ? tags.properties.tags : {} +output existingTags object = tags.properties.?tags ?? {} diff --git a/avm/ptn/lz/sub-vending/modules/readTagsSubscription.bicep b/avm/ptn/lz/sub-vending/modules/readTagsSubscription.bicep index 65b24572592..6c9f04feef0 100644 --- a/avm/ptn/lz/sub-vending/modules/readTagsSubscription.bicep +++ b/avm/ptn/lz/sub-vending/modules/readTagsSubscription.bicep @@ -8,4 +8,4 @@ resource tags 'Microsoft.Resources/tags@2019-10-01' existing = { } @description('Tags currently applied to the subscription level') -output existingTags object = contains(tags.properties, 'tags') ? tags.properties.tags : {} +output existingTags object = tags.properties.?tags ?? {} diff --git a/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep b/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep index 6a6e2e6195b..bd8fddf1904 100644 --- a/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep +++ b/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep @@ -295,7 +295,7 @@ var virtualWanHubSubscriptionId = (!empty(virtualHubResourceIdChecked) ? split(v var virtualWanHubResourceGroupName = (!empty(virtualHubResourceIdChecked) ? split(virtualHubResourceIdChecked, '/')[4] : '') -var virtualWanHubConnectionName = 'vhc-${guid(virtualHubResourceIdChecked, virtualNetworkName, virtualNetworkResourceGroupName, virtualNetworkLocation, subscriptionId)}' +var virtualWanHubConnectionName = 'vhc-${virtualNetworkName}-${substring(guid(virtualHubResourceIdChecked, virtualNetworkName, virtualNetworkResourceGroupName, virtualNetworkLocation, subscriptionId), 0, 5)}' var virtualWanHubConnectionAssociatedRouteTable = !empty(virtualNetworkVwanAssociatedRouteTableResourceId) ? virtualNetworkVwanAssociatedRouteTableResourceId : '${virtualHubResourceIdChecked}/hubRouteTables/defaultRouteTable' From dc730bd3d538764afd391beb2ba2a2b08949a6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20H=C3=A9zser?= Date: Thu, 12 Sep 2024 09:03:50 +0200 Subject: [PATCH 12/46] feat: Adds authentication to image import `ptn/deployment-script/import-image-to-acr` (#3253) ## Description Adds the option to authenticate to the source container registry. Used for e.g. docker hub login to avoid throttling. Closes #3069 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.deployment-script.import-image-to-acr](https://github.com/ReneHezser/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml/badge.svg?branch=import-image-docker-authentication)](https://github.com/ReneHezser/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../import-image-to-acr/README.md | 33 ++- .../import-image-to-acr/main.bicep | 72 ++++--- .../import-image-to-acr/main.json | 191 +++++++++++------- .../tests/e2e/max/dependencies.bicep | 55 +++++ .../tests/e2e/max/main.test.bicep | 11 +- .../import-image-to-acr/version.json | 2 +- 6 files changed, 245 insertions(+), 119 deletions(-) diff --git a/avm/ptn/deployment-script/import-image-to-acr/README.md b/avm/ptn/deployment-script/import-image-to-acr/README.md index b079fe65c11..42038de2c6d 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/README.md +++ b/avm/ptn/deployment-script/import-image-to-acr/README.md @@ -281,6 +281,8 @@ module importImageToAcr 'br/public:avm/ptn/deployment-script/import-image-to-acr | [`overwriteExistingImage`](#parameter-overwriteexistingimage) | bool | The image will be overwritten if it already exists in the ACR with the same tag. Default is false. | | [`retryMax`](#parameter-retrymax) | int | The maximum number of retries for the script import operation. Default is 3. | | [`runOnce`](#parameter-runonce) | bool | How the deployment script should be forced to execute. Default is to force the script to deploy the image to run every time. | +| [`sourceRegistryPassword`](#parameter-sourceregistrypassword) | securestring | The password for the source registry. Required if the source registry is private, or to logon to the public docker registry. | +| [`sourceRegistryUsername`](#parameter-sourceregistryusername) | string | The username for the source registry. Required if the source registry is private, or to logon to the public docker registry. | | [`storageAccountResourceId`](#parameter-storageaccountresourceid) | string | The resource id of the storage account to use for the deployment script. An existing storage account is needed, if PrivateLink is going to be used for the deployment script. | | [`subnetResourceIds`](#parameter-subnetresourceids) | array | The subnet ids to use for the deployment script. An existing subnet is needed, if PrivateLink is going to be used for the deployment script. | | [`tags`](#parameter-tags) | object | Tags of the resource. | @@ -298,7 +300,12 @@ A fully qualified image name to import. - Required: Yes - Type: string -- Example: `mcr.microsoft.com/k8se/quickstart-jobs:latest` +- Example: + ```Bicep + mcr.microsoft.com/k8se/quickstart-jobs:latest + docker.io/library/image:latest + docker.io/hello-world:latest + ``` ### Parameter: `name` @@ -415,6 +422,22 @@ How the deployment script should be forced to execute. Default is to force the s - Type: bool - Default: `False` +### Parameter: `sourceRegistryPassword` + +The password for the source registry. Required if the source registry is private, or to logon to the public docker registry. + +- Required: No +- Type: securestring +- Default: `''` + +### Parameter: `sourceRegistryUsername` + +The username for the source registry. Required if the source registry is private, or to logon to the public docker registry. + +- Required: No +- Type: string +- Default: `''` + ### Parameter: `storageAccountResourceId` The resource id of the storage account to use for the deployment script. An existing storage account is needed, if PrivateLink is going to be used for the deployment script. @@ -458,13 +481,17 @@ This section gives you an overview of all local-referenced module files (i.e., o | Reference | Type | | :-- | :-- | -| `br/public:avm/res/resources/deployment-script:0.2.3` | Remote reference | +| `br/public:avm/res/resources/deployment-script:0.4.0` | Remote reference | ## Notes The deployment script service will need and provision a Storage Account as well as a Container Instance to execute the provided script. _The deployment script resource is available only in the regions where Azure Container Instances is available._ -> The service cleans up these resources after the deployment script finishes. You incur charges for these resources until they're removed. +> The service cleans up these resources after the deployment script finishes. You incur charges for these resources until they are removed. + +### Authentication to source Container Registry + +Authentication is possible by setting the ```sourceRegistryUsername``` and ```sourceRegistryPassword``` parameters. An example that uses Key Vault is in the max sample. It is commented out, as for the shared environments no user exists, that could be used to access e.g. docker hub images. ### Private network access diff --git a/avm/ptn/deployment-script/import-image-to-acr/main.bicep b/avm/ptn/deployment-script/import-image-to-acr/main.bicep index eb578a27a18..64be560c364 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/main.bicep +++ b/avm/ptn/deployment-script/import-image-to-acr/main.bicep @@ -28,10 +28,21 @@ param managedIdentityName string? @description('Required. A fully qualified image name to import.') @metadata({ - example: 'mcr.microsoft.com/k8se/quickstart-jobs:latest' + example: [ + 'mcr.microsoft.com/k8se/quickstart-jobs:latest' + 'docker.io/library/image:latest' + 'docker.io/hello-world:latest' + ] }) param image string +@description('Optional. The username for the source registry. Required if the source registry is private, or to logon to the public docker registry.') +param sourceRegistryUsername string = '' + +@description('Optional. The password for the source registry. Required if the source registry is private, or to logon to the public docker registry.') +@secure() +param sourceRegistryPassword string = '' + @description('Optional. The new image name in the ACR. You can use this to import a publically available image with a custom name for later updating from e.g., your build pipeline.') @metadata({ example: 'your-image-name:tag' @@ -147,7 +158,7 @@ resource acrRoleAssignmentNewManagedIdentity 'Microsoft.Authorization/roleAssign } } -module imageImport 'br/public:avm/res/resources/deployment-script:0.2.3' = { +module imageImport 'br/public:avm/res/resources/deployment-script:0.4.0' = { name: name ?? 'ACR-Import-${last(split(replace(image,':','-'),'/'))}' scope: resourceGroup() params: { @@ -159,41 +170,20 @@ module imageImport 'br/public:avm/res/resources/deployment-script:0.2.3' = { : { userAssignedResourcesIds: [newManagedIdentity.id] } kind: 'AzureCLI' runOnce: runOnce - azCliVersion: '2.61.0' // available tags are listed here: https://mcr.microsoft.com/v2/azure-cli/tags/list + azCliVersion: '2.63.0' // available tags are listed here: https://mcr.microsoft.com/v2/azure-cli/tags/list timeout: 'PT30M' // set timeout to 30m retentionInterval: 'PT1H' // cleanup after 1h - environmentVariables: { - secureList: [ - { - name: 'acrName' - value: acrName - } - { - name: 'imageName' - value: image - } - { - name: 'newImageName' - value: newImageName - } - { - name: 'overwriteExistingImage' - value: toLower(string(overwriteExistingImage)) - } - { - name: 'initialDelay' - value: '${string(initialScriptDelay)}s' - } - { - name: 'retryMax' - value: string(retryMax) - } - { - name: 'retrySleep' - value: '5s' - } - ] - } + environmentVariables: [ + { name: 'acrName', value: acrName } + { name: 'imageName', value: image } + { name: 'newImageName', value: newImageName } + { name: 'overwriteExistingImage', value: toLower(string(overwriteExistingImage)) } + { name: 'initialDelay', value: '${string(initialScriptDelay)}s' } + { name: 'retryMax', value: string(retryMax) } + { name: 'retrySleep', value: '5s' } + { name: 'sourceRegistryUsername', value: sourceRegistryUsername } + { name: 'sourceRegistryPassword', secureValue: sourceRegistryPassword } + ] cleanupPreference: cleanupPreference storageAccountResourceId: storageAccountResourceId containerGroupName: '${resourceGroup().name}-infrastructure' @@ -210,9 +200,17 @@ module imageImport 'br/public:avm/res/resources/deployment-script:0.2.3' = { do echo "Importing Image ($retryLoopCount): $imageName into ACR: $acrName\n" if [ $overwriteExistingImage = 'true' ]; then - az acr import -n $acrName --source $imageName --image $newImageName --force + if [ -n "$sourceRegistryUsername" ] && [ -n "$sourceRegistryPassword" ]; then + az acr import -n $acrName --source $imageName --image $newImageName --force --username $sourceRegistryUsername --password $sourceRegistryPassword + else + az acr import -n $acrName --source $imageName --image $newImageName --force + fi else - az acr import -n $acrName --source $imageName --image $newImageName + if [ -n "$sourceRegistryUsername" ] && [ -n "$sourceRegistryPassword" ]; then + az acr import -n $acrName --source $imageName --image $newImageName --username $sourceRegistryUsername --password $sourceRegistryPassword + else + az acr import -n $acrName --source $imageName --image $newImageName + fi fi sleep $retrySleep diff --git a/avm/ptn/deployment-script/import-image-to-acr/main.json b/avm/ptn/deployment-script/import-image-to-acr/main.json index ffc946ccc34..51751e27f8d 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/main.json +++ b/avm/ptn/deployment-script/import-image-to-acr/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "18410876545978102921" + "version": "0.29.47.4906", + "templateHash": "15179702678978782456" }, "name": "import-image-to-acr", "description": "This modules deployes an image to an Azure Container Registry.", @@ -103,10 +103,28 @@ "image": { "type": "string", "metadata": { - "example": "mcr.microsoft.com/k8se/quickstart-jobs:latest", + "example": [ + "mcr.microsoft.com/k8se/quickstart-jobs:latest", + "docker.io/library/image:latest", + "docker.io/hello-world:latest" + ], "description": "Required. A fully qualified image name to import." } }, + "sourceRegistryUsername": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The username for the source registry. Required if the source registry is private, or to logon to the public docker registry." + } + }, + "sourceRegistryPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. The password for the source registry. Required if the source registry is private, or to logon to the public docker registry." + } + }, "newImageName": { "type": "string", "defaultValue": "[last(split(parameters('image'), '/'))]", @@ -289,7 +307,7 @@ "value": "[parameters('runOnce')]" }, "azCliVersion": { - "value": "2.61.0" + "value": "2.63.0" }, "timeout": { "value": "PT30M" @@ -298,38 +316,44 @@ "value": "PT1H" }, "environmentVariables": { - "value": { - "secureList": [ - { - "name": "acrName", - "value": "[parameters('acrName')]" - }, - { - "name": "imageName", - "value": "[parameters('image')]" - }, - { - "name": "newImageName", - "value": "[parameters('newImageName')]" - }, - { - "name": "overwriteExistingImage", - "value": "[toLower(string(parameters('overwriteExistingImage')))]" - }, - { - "name": "initialDelay", - "value": "[format('{0}s', string(parameters('initialScriptDelay')))]" - }, - { - "name": "retryMax", - "value": "[string(parameters('retryMax'))]" - }, - { - "name": "retrySleep", - "value": "5s" - } - ] - } + "value": [ + { + "name": "acrName", + "value": "[parameters('acrName')]" + }, + { + "name": "imageName", + "value": "[parameters('image')]" + }, + { + "name": "newImageName", + "value": "[parameters('newImageName')]" + }, + { + "name": "overwriteExistingImage", + "value": "[toLower(string(parameters('overwriteExistingImage')))]" + }, + { + "name": "initialDelay", + "value": "[format('{0}s', string(parameters('initialScriptDelay')))]" + }, + { + "name": "retryMax", + "value": "[string(parameters('retryMax'))]" + }, + { + "name": "retrySleep", + "value": "5s" + }, + { + "name": "sourceRegistryUsername", + "value": "[parameters('sourceRegistryUsername')]" + }, + { + "name": "sourceRegistryPassword", + "secureValue": "[parameters('sourceRegistryPassword')]" + } + ] }, "cleanupPreference": { "value": "[parameters('cleanupPreference')]" @@ -344,7 +368,7 @@ "value": "[parameters('subnetResourceIds')]" }, "scriptContent": { - "value": "#!/bin/bash\n set -e\n\n echo \"Waiting on RBAC replication ($initialDelay)\\n\"\n sleep $initialDelay\n\n # retry loop to catch errors (usually RBAC delays, but 'Error copying blobs' is also not unheard of)\n retryLoopCount=0\n until [ $retryLoopCount -ge $retryMax ]\n do\n echo \"Importing Image ($retryLoopCount): $imageName into ACR: $acrName\\n\"\n if [ $overwriteExistingImage = 'true' ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --force\n else\n az acr import -n $acrName --source $imageName --image $newImageName\n fi\n\n sleep $retrySleep\n retryLoopCount=$((retryLoopCount+1))\n done\n\n echo \"done\\n\"" + "value": "#!/bin/bash\n set -e\n\n echo \"Waiting on RBAC replication ($initialDelay)\\n\"\n sleep $initialDelay\n\n # retry loop to catch errors (usually RBAC delays, but 'Error copying blobs' is also not unheard of)\n retryLoopCount=0\n until [ $retryLoopCount -ge $retryMax ]\n do\n echo \"Importing Image ($retryLoopCount): $imageName into ACR: $acrName\\n\"\n if [ $overwriteExistingImage = 'true' ]; then\n if [ -n \"$sourceRegistryUsername\" ] && [ -n \"$sourceRegistryPassword\" ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --force --username $sourceRegistryUsername --password $sourceRegistryPassword\n else\n az acr import -n $acrName --source $imageName --image $newImageName --force\n fi\n else\n if [ -n \"$sourceRegistryUsername\" ] && [ -n \"$sourceRegistryPassword\" ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --username $sourceRegistryUsername --password $sourceRegistryPassword\n else\n az acr import -n $acrName --source $imageName --image $newImageName\n fi\n fi\n\n sleep $retrySleep\n retryLoopCount=$((retryLoopCount+1))\n done\n\n echo \"done\\n\"" } }, "template": { @@ -354,8 +378,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "148060868388125113" + "version": "0.29.47.4906", + "templateHash": "5978422939896103340" }, "name": "Deployment Scripts", "description": "This module deploys Deployment Scripts.", @@ -407,6 +431,13 @@ "items": { "type": "object", "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, "roleDefinitionIdOrName": { "type": "string", "metadata": { @@ -469,32 +500,29 @@ "nullable": true }, "environmentVariableType": { - "type": "secureObject", + "type": "object", "properties": { - "secureList": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "secureValue": { - "type": "string", - "nullable": true - }, - "value": { - "type": "string", - "nullable": true - } - } - }, + "name": { + "type": "string", "metadata": { - "description": "Optional. The list of environment variables to pass over to the deployment script." + "description": "Required. The name of the environment variable." + } + }, + "secureValue": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Required. The value of the secure environment variable." + } + }, + "value": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The value of the environment variable." } } - }, - "nullable": true + } } }, "parameters": { @@ -564,9 +592,13 @@ } }, "environmentVariables": { - "$ref": "#/definitions/environmentVariableType", + "type": "array", + "items": { + "$ref": "#/definitions/environmentVariableType" + }, + "nullable": true, "metadata": { - "description": "Optional. The environment variables to pass over to the script. The list is passed as an object with a key name \"secureList\" and the value is the list of environment variables (array). The list must have a 'name' and a 'value' or a 'secretValue' property for each object." + "description": "Optional. The environment variables to pass over to the script." } }, "supportingScriptUris": { @@ -595,7 +627,7 @@ }, "retentionInterval": { "type": "string", - "nullable": true, + "defaultValue": "P1D", "metadata": { "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." } @@ -669,6 +701,11 @@ }, "variables": { "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + }, { "name": "subnetIds", "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", @@ -681,7 +718,7 @@ "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" }, "containerSettings": { @@ -705,7 +742,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.2.3', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -735,7 +772,7 @@ "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", "arguments": "[parameters('arguments')]", - "environmentVariables": "[if(not(equals(parameters('environmentVariables'), null())), parameters('environmentVariables').secureList, createArray())]", + "environmentVariables": "[parameters('environmentVariables')]", "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", @@ -765,20 +802,20 @@ "deploymentScript_roleAssignments": { "copy": { "name": "deploymentScript_roleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", - "name": "[guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", - "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ "deploymentScript" @@ -828,7 +865,7 @@ "metadata": { "description": "The output of the deployment script." }, - "value": "[if(contains(reference('deploymentScript'), 'outputs'), reference('deploymentScript').outputs, createObject())]" + "value": "[coalesce(tryGet(reference('deploymentScript'), 'outputs'), createObject())]" }, "deploymentScriptLogs": { "type": "array", diff --git a/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/dependencies.bicep b/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/dependencies.bicep index c66f2f19c27..f68b4451d58 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/dependencies.bicep +++ b/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/dependencies.bicep @@ -13,6 +13,9 @@ param acrName string @description('Required. The name of the Storage Account to create.') param storageAccountName string +@description('Required. The name of the Key Vault to create.') +param keyVaultName string + var ipRange = '10.0.0.0' module identity 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.1' = { @@ -105,6 +108,52 @@ module storage 'br/public:avm/res/storage/storage-account:0.9.0' = { } } +// KeyVault stores the password to login to the source container registry +resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + tenantId: tenant().tenantId + enablePurgeProtection: null + enabledForTemplateDeployment: true + enabledForDiskEncryption: true + enabledForDeployment: true + enableRbacAuthorization: true + accessPolicies: [] + } + dependsOn: [identity] + + resource containerRegistrySecret 'secrets@2023-07-01' = { + name: 'ContainerRegistryPassword' + properties: { + // put the password of the source container registry here + value: '' + } + } + + resource rbac 'accessPolicies@2023-07-01' = { + name: 'add' + properties: { + accessPolicies: [ + { + tenantId: tenant().tenantId + objectId: identity.outputs.principalId + permissions: { + keys: [] + secrets: ['get', 'list', 'set'] + certificates: [] + storage: [] + } + } + ] + } + } +} + // the container registry to upload the image into module acr 'br/public:avm/res/container-registry/registry:0.2.0' = { name: '${uniqueString(resourceGroup().name, location)}-acr' @@ -145,3 +194,9 @@ output storageAccountResourceId string = storage.outputs.resourceId @description('The resource ID of the created subnet designated for the Deployment Script.') output deploymentScriptSubnetResourceId string = vnet::subnet_deploymentscript.id + +@description('The resource ID of the created Key Vault.') +output keyVaultResourceId string = keyVault.id + +@description('The name of the created Key Vault secret.') +output keyVaultSecretName string = keyVault::containerRegistrySecret.name diff --git a/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep b/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep index a00d32d62ce..3c7e75c684d 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep +++ b/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep @@ -31,6 +31,7 @@ module dependencies 'dependencies.bicep' = { virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' acrName: 'dep${namePrefix}acr${serviceShort}' storageAccountName: 'dep${namePrefix}sa${serviceShort}' + keyVaultName: 'dep${namePrefix}kv${serviceShort}' managedIdentityName: 'dep-${namePrefix}-mi-${serviceShort}' } } @@ -46,6 +47,11 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { // Test Execution // // ============== // +resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { + name: last(split(dependencies.outputs.keyVaultResourceId, '/')) + scope: resourceGroup +} + @batchSize(1) module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { @@ -59,7 +65,10 @@ module testDeployment '../../../main.bicep' = [ } acrName: dependencies.outputs.acrName location: resourceLocation - image: 'mcr.microsoft.com/k8se/quickstart-jobs:latest' + image: 'mcr.microsoft.com/k8se/quickstart-jobs:latest' // e.g. for docker images, that will be authenticated with the below properties 'docker.io/hello-world:latest' + // commented out, as the user is not available in the test environment + // sourceRegistryUsername: 'username' + // sourceRegistryPassword: keyVault.getSecret(dependencies.outputs.keyVaultSecretName) newImageName: 'your-image-name:tag' cleanupPreference: 'OnExpiration' assignRbacRole: true diff --git a/avm/ptn/deployment-script/import-image-to-acr/version.json b/avm/ptn/deployment-script/import-image-to-acr/version.json index daf1a794d9c..17dd49a0b96 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/version.json +++ b/avm/ptn/deployment-script/import-image-to-acr/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.2", + "version": "0.3", "pathFilters": [ "./main.json" ] From 5e3198e9e9533aa74c786c9012ccd4fbe31c6cbc Mon Sep 17 00:00:00 2001 From: "Jianing Wang (MSFT)" <141212663+jianingwang123@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:53:05 +0800 Subject: [PATCH 13/46] fix: Run the `Set-AVMModule` script to update the files of ptn module `azd/apim-api` (#3256) ## Description Fixes [comment](https://github.com/Azure/bicep-registry-modules/pull/3206#issuecomment-2341363647) adout static test failed of ptn modules `azd/apim-api`. ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.azd.apim-api](https://github.com/jianingwang123/bicep-registry-modules/actions/workflows/avm.ptn.azd.apim-api.yml/badge.svg?branch=fix%2Fnew)](https://github.com/jianingwang123/bicep-registry-modules/actions/workflows/avm.ptn.azd.apim-api.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings @jongio for notification. --- avm/ptn/azd/apim-api/README.md | 11 ++--------- avm/ptn/azd/apim-api/main.bicep | 2 +- avm/ptn/azd/apim-api/main.json | 4 ++-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/avm/ptn/azd/apim-api/README.md b/avm/ptn/azd/apim-api/README.md index 681638af793..f86fc2befd8 100644 --- a/avm/ptn/azd/apim-api/README.md +++ b/avm/ptn/azd/apim-api/README.md @@ -10,7 +10,6 @@ Creates and configure an API within an API Management service instance. - [Usage examples](#Usage-examples) - [Parameters](#Parameters) - [Outputs](#Outputs) -- [Cross-referenced modules](#Cross-referenced-modules) - [Data Collection](#Data-Collection) ## Resource Types @@ -104,7 +103,6 @@ module apimApi 'br/public:avm/ptn/azd/apim-api:' = {

- ## Parameters **Required parameters** @@ -117,7 +115,7 @@ module apimApi 'br/public:avm/ptn/azd/apim-api:' = { | [`apiName`](#parameter-apiname) | string | Resource name to uniquely identify this API within the API Management service instance. | | [`apiPath`](#parameter-apipath) | string | Relative URL uniquely identifying this API and all of its resource paths within the API Management service instance. It is appended to the API endpoint base URL specified during the service instance creation to form a public URL for this API. | | [`name`](#parameter-name) | string | Name of the API Management service instance. | -| [`webFrontendUrl`](#parameter-webfrontendurl) | string | Absolute URL of the web frontend. | +| [`webFrontendUrl`](#parameter-webfrontendurl) | string | Absolute URL of web frontend. | **Optional parameters** @@ -171,7 +169,7 @@ Name of the API Management service instance. ### Parameter: `webFrontendUrl` -Absolute URL of the web frontend. +Absolute URL of web frontend. - Required: Yes - Type: string @@ -200,7 +198,6 @@ Location for all Resources. - Type: string - Default: `[resourceGroup().location]` - ## Outputs | Output | Type | Description | @@ -208,10 +205,6 @@ Location for all Resources. | `resourceGroupName` | string | The name of the resource group. | | `serviceApiUri` | string | The complete URL for accessing the API. | -## Cross-referenced modules - -_None_ - ## Data Collection The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/azd/apim-api/main.bicep b/avm/ptn/azd/apim-api/main.bicep index 22c2fd5435a..becb6b512e6 100644 --- a/avm/ptn/azd/apim-api/main.bicep +++ b/avm/ptn/azd/apim-api/main.bicep @@ -24,7 +24,7 @@ param apiDescription string @minLength(1) param apiPath string -@description('Required. Absolute URL of the web frontend.') +@description('Required. Absolute URL of web frontend.') param webFrontendUrl string @description('Optional. Location for all Resources.') diff --git a/avm/ptn/azd/apim-api/main.json b/avm/ptn/azd/apim-api/main.json index dacd440b8a4..912dbe44129 100644 --- a/avm/ptn/azd/apim-api/main.json +++ b/avm/ptn/azd/apim-api/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7935494033060946539" + "templateHash": "1542387667896789833" }, "name": "avm/ptn/azd/apim-api", "description": "Creates and configure an API within an API Management service instance.\n\n**Note:** This module is not intended for broad, generic use, as it was designed to cater for the requirements of the AZD CLI product. Feature requests and bug fix requests are welcome if they support the development of the AZD CLI but may not be incorporated if they aim to make this module more generic than what it needs to be for its primary use case.", @@ -50,7 +50,7 @@ "webFrontendUrl": { "type": "string", "metadata": { - "description": "Required. Absolute URL of the web frontend." + "description": "Required. Absolute URL of web frontend." } }, "location": { From ada1720a43569524d061a12d7d66b21a4a70a362 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Thu, 12 Sep 2024 17:41:39 +0200 Subject: [PATCH 14/46] feat: Added utility to re-run failed jobs for failed workflows (#2968) ## Description As it happens from time to time that multiple workflows fail due to temporary issues (e.g. PSGallery not responding, throttling, etc.), this utility is intended to easy re-running the failed jobs for these workflows. By default, the utility focuses on the `main` branch and once it ran, re-triggered all jobs, those jobs concluded, you can run the issue managing workflow to close the corresponding issues if the re-run fix the temporary issue. Example excution ```pwsh Invoke-FailedWorkflowsReRun # VERBOSE: Fetching current GitHub workflows # VERBOSE: Fetched [145] workflows # VERBOSE: Runs to re-run failed jobs for [6/145] # VERBOSE: Re-triggerung complete ``` ## Type of Change - [x] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --- .../tools/Invoke-WorkflowsFailedJobsReRun.ps1 | 415 ++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 avm/utilities/tools/Invoke-WorkflowsFailedJobsReRun.ps1 diff --git a/avm/utilities/tools/Invoke-WorkflowsFailedJobsReRun.ps1 b/avm/utilities/tools/Invoke-WorkflowsFailedJobsReRun.ps1 new file mode 100644 index 00000000000..adf77897e34 --- /dev/null +++ b/avm/utilities/tools/Invoke-WorkflowsFailedJobsReRun.ps1 @@ -0,0 +1,415 @@ +#region helper functions +<# +.SYNOPSIS +Get a list of all GitHub module workflows + +.DESCRIPTION +Get a list of all GitHub module workflows. Does not return all properties but only the relevant ones. + +.PARAMETER PersonalAccessToken +Optional. The PAT to use to interact with either GitHub / Azure DevOps. If not provided, the script will use the GitHub CLI to authenticate. + +.PARAMETER RepositoryOwner +Mandatory. The repository's organization. + +.PARAMETER RepositoryName +Mandatory. The name of the repository to fetch the workflows from. + +.PARAMETER IncludeDisabled +Optional. Set if you want to also include disabled workflows in the result. + +.PARAMETER Filter +Optional. A regex filter to apply when fetching the workflows. By default we fetch all module workflows. + +.EXAMPLE +Get-GitHubModuleWorkflowList -PersonalAccessToken '' -RepositoryOwner 'Azure' -RepositoryName 'bicep-registry-modules' + +Get all module workflows from repository 'Azure/bicep-registry-modules' +#> +function Get-GitHubModuleWorkflowList { + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [string] $PersonalAccessToken, + + [Parameter(Mandatory = $true)] + [string] $RepositoryOwner, + + [Parameter(Mandatory = $true)] + [string] $RepositoryName, + + [Parameter(Mandatory = $false)] + [switch] $IncludeDisabled, + + [Parameter(Mandatory = $false)] + [string] $Filter = 'avm\.(?:res|ptn|utl)' + ) + + $allWorkflows = @() + + $page = 1 + do { + $queryUrl = "/repos/$RepositoryOwner/$RepositoryName/actions/workflows?per_page=100&page=$page" + if ($PersonalAccessToken) { + # Using PAT + $requestInputObject = @{ + Method = 'GET' + Uri = "https://api.github.com$queryUrl" + Headers = @{ + Authorization = "Bearer $PersonalAccessToken" + } + } + $response = Invoke-RestMethod @requestInputObject + } else { + # Using GH API instead of 'gh workflow list' to get all results instead of just the first few + $requestInputObject = @( + '-H', 'Accept: application/vnd.github+json', + '-H', 'X-GitHub-Api-Version: 2022-11-28', + $queryUrl + ) + $response = (gh api @requestInputObject | ConvertFrom-Json) + } + + if (-not $response.workflows) { + Write-Error "Request failed. Reponse: [$response]" + } + + $allWorkflows += $response.workflows | Select-Object -Property @('id', 'name', 'path', 'badge_url', 'state') | Where-Object { + $_.name -match $Filter -and + ($IncludeDisabled ? $true : $_.state -eq 'active') + } + + $expectedPages = [math]::ceiling($response.total_count / 100) + $page++ + } while ($page -le $expectedPages) + + return $allWorkflows +} + + +<# +.SYNOPSIS +Invoke the re-run for a given set of workflow runs. + +.DESCRIPTION +Invoke the re-run for a given set of workflow runs. + +.PARAMETER RestInputObject +Mandatory. The REST parameters to use for the re-run. Must contain the 'RepositoryOwner' and 'RepositoryName' keys and may contain the 'PersonalAccessToken' key. + +.PARAMETER RunsToReTrigger +Manadatory. The workflow runs to re-trigger. + +.PARAMETER TotalNumberOfWorkflows +Mandatory. The total number of workflows to re-trigger. + +.EXAMPLE +Invoke-ReRun -RestInputObject @{ RepositoryOwner = 'Azure'; RepositoryName = 'bicep-registry-modules' } -RunsToReTrigger @(@{ id = 123; name = 'keyvaultworkflow'}) -TotalNumberOfWorkflows 123 + +Re-run the failed jobs for all provided runs in the repository [Azure/bicep-registry-modules]. +#> +function Invoke-ReRun { + + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory)] + [hashtable] $RestInputObject, + + [Parameter(Mandatory)] + [object[]] $RunsToReTrigger, + + [Parameter(Mandatory)] + [int] $TotalNumberOfWorkflows + ) + + $totalCount = $RunsToReTrigger.Count + $currentCount = 1 + Write-Verbose ('Runs to re-run failed jobs for [{0}/{1}]' -f $RunsToReTrigger.Count, $TotalNumberOfWorkflows) -Verbose + foreach ($run in $RunsToReTrigger) { + $percentageComplete = [math]::Round(($currentCount / $totalCount) * 100) + Write-Progress -Activity ('Re-running failed jobs for workflow [{0}]' -f $run.name) -Status "$percentageComplete% complete" -PercentComplete $percentageComplete + + if ($PSCmdlet.ShouldProcess(("Re-run of failed jobs for GitHub workflow [{0}] for branch [$TargetBranch]" -f $run.name), 'Invoke')) { + $null = Invoke-GitHubWorkflowRunFailedJobsReRun @RestInputObject -RunId $run.id + } + $currentCount++ + } +} + +<# +.SYNOPSIS +Get the latest run of a GitHub workflow for a given branch. + +.DESCRIPTION +Get the latest run of a GitHub workflow for a given branch. + +.PARAMETER PersonalAccessToken +Optional. The PAT to use to interact with either GitHub. If not provided, the script will use the GitHub CLI to authenticate. + +.PARAMETER RepositoryOwner +Optional. The GitHub organization the workfows are located in. + +.PARAMETER RepositoryName +Optional. The GitHub repository the workfows are located in. + +.PARAMETER WorkflowId +Required. The ID of the workflow to get the latest run for. + +.PARAMETER TargetBranch +Optional. The branch to get the latest run for. Defaults to 'main'. + +.EXAMPLE +Get-GitHubModuleWorkflowLatestRun -RepositoryOwner 'Azure' -RepositoryName 'bicep-registry-modules' -WorkflowId '447791597' + +Get the latest workflow run of the repository [Azure/bicep-registry-modules] for a workflow with id '447791597', filtered to the 'main' branch. +#> +function Get-GitHubModuleWorkflowLatestRun { + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [string] $PersonalAccessToken, + + [Parameter(Mandatory = $true)] + [string] $RepositoryOwner, + + [Parameter(Mandatory = $true)] + [string] $RepositoryName, + + [Parameter(Mandatory = $true)] + [string] $WorkflowId, + + [Parameter(Mandatory = $false)] + [string] $TargetBranch = 'main' + ) + + $queryUrl = "/repos/$RepositoryOwner/$RepositoryName/actions/workflows/$WorkflowId/runs?branch=$TargetBranch&per_page=1" + if ($PersonalAccessToken) { + # Using PAT + $requestInputObject = @{ + Method = 'GET' + Uri = "https://api.github.com$queryUrl" + Headers = @{ + Authorization = "Bearer $PersonalAccessToken" + } + } + $response = Invoke-RestMethod @requestInputObject + } else { + # Using GH API instead of 'gh workflow list' to get all results instead of just the first few + $requestInputObject = @( + '-H', 'Accept: application/vnd.github+json', + '-H', 'X-GitHub-Api-Version: 2022-11-28', + $queryUrl + ) + $response = (gh api @requestInputObject | ConvertFrom-Json) + } + + if (-not $response.workflow_runs) { + Write-Error "Request failed. Reponse: [$response]" + } + + return $response.workflow_runs | Select-Object -Property @('id', 'name', 'path', 'status', 'head_branch', 'created_at', 'run_number', 'run_attempt', 'conclusion') +} + +<# +.SYNOPSIS +Invoke the 'Rerun failed jobs' action for a given GitHub workflow run. + +.DESCRIPTION +Invoke the 'Rerun failed jobs' action for a given GitHub workflow run. + +.PARAMETER PersonalAccessToken +Optional. The PAT to use to interact with either GitHub. If not provided, the script will use the GitHub CLI to authenticate. + +.PARAMETER RepositoryOwner +Optional. The GitHub organization to run the workfows in. + +.PARAMETER RepositoryName +Optional. The GitHub repository to run the workfows in. + +.PARAMETER RunId +Mandatory. The ID of the run to re-run the failed jobs for. + +.EXAMPLE +Invoke-GitHubWorkflowRunFailedJobsReRun -RepositoryOwner 'Azure' -RepositoryName 'bicep-registry-modules' -RunId '447791597' + +Re-run the failed jobs for the GitHub workflow run with ID '447791597' in the repository [Azure/bicep-registry-modules]. +#> +function Invoke-GitHubWorkflowRunFailedJobsReRun { + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [string] $PersonalAccessToken, + + [Parameter(Mandatory = $true)] + [string] $RepositoryOwner, + + [Parameter(Mandatory = $true)] + [string] $RepositoryName, + + [Parameter(Mandatory = $true)] + [string] $RunId + ) + + + $queryUrl = "/repos/$RepositoryOwner/$RepositoryName/actions/runs/$RunId/rerun-failed-jobs" + if ($PersonalAccessToken) { + # Using PAT + $requestInputObject = @{ + Method = 'POST' + Uri = "https://api.github.com$queryUrl" + Headers = @{ + Authorization = "Bearer $PersonalAccessToken" + } + } + $response = Invoke-RestMethod @requestInputObject + } else { + # Using GH API instead of 'gh workflow list' to get all results instead of just the first few + $requestInputObject = @( + '--method', 'POST', + '-H', 'Accept: application/vnd.github+json', + '-H', 'X-GitHub-Api-Version: 2022-11-28', + $queryUrl + ) + $response = (gh api @requestInputObject | ConvertFrom-Json) + } + + if ("$response") { + # If successfull, the response will be an empty custom object. Must be casted to string + Write-Error "Request failed. Response: [$response]" + return $false + } + + return $true +} +#endregion + +<# +.SYNOPSIS +Re-runs all failed jobs across all workflows for a given GitHub repository. + +.DESCRIPTION +Re-runs all failed jobs across all workflows for a given GitHub repository. +By default, pipelines are filtered to AVM module pipelines & the main branch. +Currently running workflows are excluded. + +.PARAMETER PersonalAccessToken +Optional. The PAT to use to interact with either GitHub. If not provided, the script will use the GitHub CLI to authenticate. + +.PARAMETER TargetBranch +Optional. The branch to run the pipelines for (e.g. `main`). Defaults to 'main'. + +.PARAMETER PipelineFilter +Optional. The pipeline files to filter down to (regex). + +.PARAMETER RepositoryOwner +Optional. The GitHub organization to run the workfows in. + +.PARAMETER RepositoryName +Optional. The GitHub repository to run the workfows in. + +.EXAMPLE +Invoke-WorkflowsFailedJobsReRun -PersonalAccessToken '' -TargetBranch 'feature/branch' -PipelineFilter 'avm\.(?:res|ptn|utl)' + +Run the failed jobs for all GitHub workflows that match 'avm\.(?:res|ptn|utl)' using branch 'feature/branch'. + +.EXAMPLE +Invoke-WorkflowsFailedJobsReRun -PersonalAccessToken '' -TargetBranch 'feature/branch' -PipelineFilter 'avm\.(?:res|ptn|utl)' -WhatIf + +Only simulate the triggering of the failed jobs for all failed GitHub workflows that match 'avm\.(?:res|ptn|utl)' using branch 'feature/branch'. + +.EXAMPLE +Invoke-WorkflowsFailedJobsReRun -PersonalAccessToken '' -RepositoryOwner 'MyFork' + +Only simulate the triggering of the failed jobs of all GitHub workflows of project [MyFork/bicep-registry-modules] that start with'avm.res.res|ptn|utl', using the main branch & PAT. + +.EXAMPLE +Invoke-WorkflowsFailedJobsReRun -RepositoryOwner 'MyFork' + +Only simulate the triggering of the failed jobs of all GitHub workflows of project [MyFork/bicep-registry-modules] that start with'avm.res.res|ptn|utl', using the main branch & your current GH CLI login. +#> +function Invoke-WorkflowsFailedJobsReRun { + + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $false)] + [string] $PersonalAccessToken, + + [Parameter(Mandatory = $false)] + [string] $TargetBranch = 'main', + + [Parameter(Mandatory = $false)] + [string] $PipelineFilter = 'avm\.(?:res|ptn|utl)', + + [Parameter(Mandatory = $false)] + [string] $RepositoryOwner = 'Azure', + + [Parameter(Mandatory = $false)] + [string] $RepositoryName = 'bicep-registry-modules' + ) + + $baseInputObject = @{ + RepositoryOwner = $RepositoryOwner + RepositoryName = $RepositoryName + } + if ($PersonalAccessToken) { + $baseInputObject['PersonalAccessToken'] = @{ + PersonalAccessToken = $PersonalAccessToken + } + } + ##################################### + # Get all workflows for branch # + ##################################### + Write-Verbose 'Fetching current GitHub workflows' -Verbose + $workflows = Get-GitHubModuleWorkflowList @baseInputObject -Filter $PipelineFilter + Write-Verbose ('Fetched [{0}] workflows' -f $workflows.Count) -Verbose + + + ###################################################### + # Analyze latest run of each workflow for branch # + ###################################################### + $totalCount = $workflows.Count + $currentCount = 1 + $runsToReTrigger = [System.Collections.ArrayList]@() + foreach ($workflow in $workflows) { + + $percentageComplete = [math]::Round(($currentCount / $totalCount) * 100) + Write-Progress -Activity ('Analyzing workflow [{0}]' -f $workflow.name) -Status "$percentageComplete% complete" -PercentComplete $percentageComplete + # Get relevant runs + $latestBranchRun = Get-GitHubModuleWorkflowLatestRun @baseInputObject -WorkflowId $workflow.id -TargetBranch $TargetBranch + + if ($latestBranchRun.status -eq 'completed' -and $latestBranchRun.conclusion -eq 'failure') { + $runsToReTrigger += $latestBranchRun + } + $currentCount++ + } + + ############################## + # Re-trigger failed runs # + ############################## + $reRunInputObject = @{ + RestInputObject = $baseInputObject + RunsToReTrigger = $runsToReTrigger + TotalNumberOfWorkflows = $workflows.Count + } + $null = Invoke-ReRun @reRunInputObject -WhatIf:$WhatIfPreference + + # Enable the user to execute the invocation if the whatif looked good + if ($WhatIfPreference) { + do { + $userInput = Read-Host -Prompt 'Should apply (y/n)?' + } + while ($userInput -notin @('y', 'n')) + + switch ($userInput) { + 'y' { + $null = Invoke-ReRun @reRunInputObject -WhatIf:$false + } + 'n' { return } + } + } + + Write-Verbose 'Re-triggerung complete' -Verbose +} From 497ffaf537fcda577eb1704d10199487c8d9ee95 Mon Sep 17 00:00:00 2001 From: hundredacres Date: Thu, 12 Sep 2024 23:29:01 -0700 Subject: [PATCH 15/46] feat: New module `avm/ptn/network/hub-networking` (#1257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description New pattern module for hub networking. ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.network.hub-networking](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.ptn.network.hub-networking.yml/badge.svg?branch=hubspoke)](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.ptn.network.hub-networking.yml) | ## Type of Change Adding a new module - [x] A proposal has been submitted and approved. - [ ] I have included "Closes #{module_proposal_issue_number}" in the PR description. - [ ] I have run brm validate locally to verify the module files. - [X] I have run deployment tests locally to ensure the module is deployable. ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: René Hézser Co-authored-by: Kris Baranek Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> Co-authored-by: Ahmad Abdalla <28486158+ahmadabdalla@users.noreply.github.com> Co-authored-by: Alexander Sehr Co-authored-by: Clint Grove <30802291+clintgrove@users.noreply.github.com> Co-authored-by: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Co-authored-by: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> Co-authored-by: Sebastian Gräf Co-authored-by: Felix Borst <17405838+fblix@users.noreply.github.com> Co-authored-by: Felix Borst Co-authored-by: John Co-authored-by: Shenglong Li Co-authored-by: elisa anzelmo Co-authored-by: Máté Barabás Co-authored-by: Tao Yang Co-authored-by: Buddy <38195643+tsc-buddy@users.noreply.github.com> Co-authored-by: Ilhaan Rasheed Co-authored-by: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Co-authored-by: JFolberth --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .../avm.ptn.network.hub-networking.yml | 83 + .vscode/settings.json | 1 + avm/ptn/network/hub-networking/README.md | 1680 ++++ avm/ptn/network/hub-networking/main.bicep | 515 ++ avm/ptn/network/hub-networking/main.json | 6899 +++++++++++++++++ .../hub-networking/modules/getSubnet.bicep | 23 + .../hub-networking/modules/subnets.bicep | 185 + .../hub-networking/modules/vnets.bicep | 22 + .../tests/e2e/defaults/main.test.bicep | 47 + .../tests/e2e/max/main.test.bicep | 225 + .../tests/e2e/waf-aligned/main.test.bicep | 146 + avm/ptn/network/hub-networking/version.json | 7 + 14 files changed, 9835 insertions(+) create mode 100644 .github/workflows/avm.ptn.network.hub-networking.yml create mode 100644 avm/ptn/network/hub-networking/README.md create mode 100644 avm/ptn/network/hub-networking/main.bicep create mode 100644 avm/ptn/network/hub-networking/main.json create mode 100644 avm/ptn/network/hub-networking/modules/getSubnet.bicep create mode 100644 avm/ptn/network/hub-networking/modules/subnets.bicep create mode 100644 avm/ptn/network/hub-networking/modules/vnets.bicep create mode 100644 avm/ptn/network/hub-networking/tests/e2e/defaults/main.test.bicep create mode 100644 avm/ptn/network/hub-networking/tests/e2e/max/main.test.bicep create mode 100644 avm/ptn/network/hub-networking/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/ptn/network/hub-networking/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 69fd75e5c7b..2bdd9e91d3b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,6 +19,7 @@ /avm/ptn/dev-ops/cicd-agents-and-runners/ @Azure/avm-ptn-devops-cicdagentsandrunners-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/finops-toolkit/finops-hub/ @Azure/avm-ptn-finopstoolkit-finopshub-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/lz/sub-vending/ @Azure/avm-ptn-lz-subvending-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/ptn/network/hub-networking/ @Azure/avm-ptn-network-hubnetworking-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/network/private-link-private-dns-zones/ @Azure/avm-ptn-network-privatelinkprivatednszones-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/policy-insights/remediation/ @Azure/avm-ptn-policyinsights-remediation-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/security/security-center/ @Azure/avm-ptn-security-securitycenter-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index 76f4173ed13..fccb58bce5f 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -54,6 +54,7 @@ body: - "avm/ptn/dev-ops/cicd-agents-and-runners" - "avm/ptn/finops-toolkit/finops-hub" - "avm/ptn/lz/sub-vending" + - "avm/ptn/network/hub-networking" - "avm/ptn/network/private-link-private-dns-zones" - "avm/ptn/policy-insights/remediation" - "avm/ptn/security/security-center" diff --git a/.github/workflows/avm.ptn.network.hub-networking.yml b/.github/workflows/avm.ptn.network.hub-networking.yml new file mode 100644 index 00000000000..617646aa740 --- /dev/null +++ b/.github/workflows/avm.ptn.network.hub-networking.yml @@ -0,0 +1,83 @@ +name: "avm.ptn.network.hub-networking" +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" + - ".github/workflows/avm.ptn.network.hub-networking.yml" + - "avm/ptn/network/hub-networking/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" +env: + modulePath: "avm/ptn/network/hub-networking" + workflowPath: ".github/workflows/avm.ptn.network.hub-networking.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 diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e02c76f6cd..05509ca25f9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "editor.formatOnSave": true, + "editor.bracketPairColorization.enabled": true, "files.trimTrailingWhitespace": true, "files.autoSave": "onFocusChange", "files.eol": "\n", diff --git a/avm/ptn/network/hub-networking/README.md b/avm/ptn/network/hub-networking/README.md new file mode 100644 index 00000000000..46c009b8c0e --- /dev/null +++ b/avm/ptn/network/hub-networking/README.md @@ -0,0 +1,1680 @@ +# Hub Networking `[Network/HubNetworking]` + +This module is designed to simplify the creation of multi-region hub networks in Azure. It will create a number of virtual networks and subnets, and optionally peer them together in a mesh topology with routing. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.Network/azureFirewalls` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/azureFirewalls) | +| `Microsoft.Network/bastionHosts` | [2022-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-11-01/bastionHosts) | +| `Microsoft.Network/publicIPAddresses` | [2023-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-09-01/publicIPAddresses) | +| `Microsoft.Network/routeTables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/routeTables) | +| `Microsoft.Network/routeTables/routes` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/routeTables/routes) | +| `Microsoft.Network/virtualNetworks` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks) | +| `Microsoft.Network/virtualNetworks/subnets` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/subnets) | +| `Microsoft.Network/virtualNetworks/subnets` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/virtualNetworks/subnets) | +| `Microsoft.Network/virtualNetworks/virtualNetworkPeerings` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/virtualNetworkPeerings) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/ptn/network/hub-networking:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [WAF-aligned](#example-3-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module hubNetworking 'br/public:avm/ptn/network/hub-networking:' = { + name: 'hubNetworkingDeployment' + params: { + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using large parameter set_ + +This instance deploys the module with most of its features enabled. + + +

+ +via Bicep module + +```bicep +module hubNetworking 'br/public:avm/ptn/network/hub-networking:' = { + name: 'hubNetworkingDeployment' + params: { + hubVirtualNetworks: { + hub1: { + addressPrefixes: '' + azureFirewallSettings: { + azureSkuTier: 'Standard' + enableTelemetry: true + location: '' + publicIPAddressObject: { + name: 'hub1-waf-pip' + } + threatIntelMode: 'Alert' + } + bastionHost: { + disableCopyPaste: true + enableFileCopy: false + enableIpConnect: false + enableShareableLink: false + scaleUnits: 2 + skuName: 'Standard' + } + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + dnsServers: [ + '10.0.1.4' + '10.0.1.5' + ] + enableAzureFirewall: true + enableBastion: true + enablePeering: false + enableTelemetry: true + flowTimeoutInMinutes: 30 + location: '' + lock: { + kind: 'CanNotDelete' + name: 'hub1Lock' + } + peeringSettings: [ + { + allowForwardedTraffic: true + allowGatewayTransit: false + allowVirtualNetworkAccess: true + remoteVirtualNetworkName: 'hub2' + useRemoteGateways: false + } + ] + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + addressPrefix: '' + name: 'GatewaySubnet' + } + { + addressPrefix: '' + name: 'AzureFirewallSubnet' + } + { + addressPrefix: '' + name: 'AzureBastionSubnet' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + hub2: { + addressPrefixes: '' + azureFirewallSettings: { + azureSkuTier: 'Standard' + enableTelemetry: true + location: '' + publicIPAddressObject: { + name: 'hub2-waf-pip' + } + threatIntelMode: 'Alert' + zones: [ + 1 + 2 + 3 + ] + } + bastionHost: { + disableCopyPaste: true + enableFileCopy: false + enableIpConnect: false + enableShareableLink: false + scaleUnits: 2 + skuName: 'Standard' + } + enableAzureFirewall: true + enableBastion: true + enablePeering: false + enableTelemetry: false + flowTimeoutInMinutes: 10 + location: '' + lock: { + kind: 'CanNotDelete' + name: 'hub2Lock' + } + peeringSettings: [ + { + allowForwardedTraffic: true + allowGatewayTransit: false + allowVirtualNetworkAccess: true + remoteVirtualNetworkName: 'hub1' + useRemoteGateways: false + } + ] + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + addressPrefix: '' + name: 'GatewaySubnet' + } + { + addressPrefix: '' + name: 'AzureFirewallSubnet' + } + { + addressPrefix: '' + name: 'AzureBastionSubnet' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + } + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "hubVirtualNetworks": { + "value": { + "hub1": { + "addressPrefixes": "", + "azureFirewallSettings": { + "azureSkuTier": "Standard", + "enableTelemetry": true, + "location": "", + "publicIPAddressObject": { + "name": "hub1-waf-pip" + }, + "threatIntelMode": "Alert" + }, + "bastionHost": { + "disableCopyPaste": true, + "enableFileCopy": false, + "enableIpConnect": false, + "enableShareableLink": false, + "scaleUnits": 2, + "skuName": "Standard" + }, + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "dnsServers": [ + "10.0.1.4", + "10.0.1.5" + ], + "enableAzureFirewall": true, + "enableBastion": true, + "enablePeering": false, + "enableTelemetry": true, + "flowTimeoutInMinutes": 30, + "location": "", + "lock": { + "kind": "CanNotDelete", + "name": "hub1Lock" + }, + "peeringSettings": [ + { + "allowForwardedTraffic": true, + "allowGatewayTransit": false, + "allowVirtualNetworkAccess": true, + "remoteVirtualNetworkName": "hub2", + "useRemoteGateways": false + } + ], + "routes": [ + { + "name": "defaultRoute", + "properties": { + "addressPrefix": "0.0.0.0/0", + "nextHopType": "Internet" + } + } + ], + "subnets": [ + { + "addressPrefix": "", + "name": "GatewaySubnet" + }, + { + "addressPrefix": "", + "name": "AzureFirewallSubnet" + }, + { + "addressPrefix": "", + "name": "AzureBastionSubnet" + } + ], + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + }, + "vnetEncryption": false, + "vnetEncryptionEnforcement": "AllowUnencrypted" + }, + "hub2": { + "addressPrefixes": "", + "azureFirewallSettings": { + "azureSkuTier": "Standard", + "enableTelemetry": true, + "location": "", + "publicIPAddressObject": { + "name": "hub2-waf-pip" + }, + "threatIntelMode": "Alert", + "zones": [ + 1, + 2, + 3 + ] + }, + "bastionHost": { + "disableCopyPaste": true, + "enableFileCopy": false, + "enableIpConnect": false, + "enableShareableLink": false, + "scaleUnits": 2, + "skuName": "Standard" + }, + "enableAzureFirewall": true, + "enableBastion": true, + "enablePeering": false, + "enableTelemetry": false, + "flowTimeoutInMinutes": 10, + "location": "", + "lock": { + "kind": "CanNotDelete", + "name": "hub2Lock" + }, + "peeringSettings": [ + { + "allowForwardedTraffic": true, + "allowGatewayTransit": false, + "allowVirtualNetworkAccess": true, + "remoteVirtualNetworkName": "hub1", + "useRemoteGateways": false + } + ], + "routes": [ + { + "name": "defaultRoute", + "properties": { + "addressPrefix": "0.0.0.0/0", + "nextHopType": "Internet" + } + } + ], + "subnets": [ + { + "addressPrefix": "", + "name": "GatewaySubnet" + }, + { + "addressPrefix": "", + "name": "AzureFirewallSubnet" + }, + { + "addressPrefix": "", + "name": "AzureBastionSubnet" + } + ], + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + }, + "vnetEncryption": false, + "vnetEncryptionEnforcement": "AllowUnencrypted" + } + } + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 3: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module hubNetworking 'br/public:avm/ptn/network/hub-networking:' = { + name: 'hubNetworkingDeployment' + params: { + hubVirtualNetworks: { + hub1: { + addressPrefixes: '' + azureFirewallSettings: { + azureSkuTier: 'Standard' + enableTelemetry: true + location: '' + publicIPAddressObject: { + name: 'hub1PublicIp' + } + threatIntelMode: 'Alert' + zones: [ + 1 + 2 + 3 + ] + } + bastionHost: { + disableCopyPaste: true + enableFileCopy: false + enableIpConnect: false + enableShareableLink: false + scaleUnits: 2 + skuName: 'Standard' + } + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + dnsServers: [ + '10.0.1.6' + '10.0.1.7' + ] + enableAzureFirewall: true + enableBastion: true + enablePeering: false + enableTelemetry: true + flowTimeoutInMinutes: 30 + location: '' + lock: { + kind: 'CanNotDelete' + name: 'hub1Lock' + } + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + addressPrefix: '' + name: 'GatewaySubnet' + } + { + addressPrefix: '' + name: 'AzureFirewallSubnet' + } + { + addressPrefix: '' + name: 'AzureBastionSubnet' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + } + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "hubVirtualNetworks": { + "value": { + "hub1": { + "addressPrefixes": "", + "azureFirewallSettings": { + "azureSkuTier": "Standard", + "enableTelemetry": true, + "location": "", + "publicIPAddressObject": { + "name": "hub1PublicIp" + }, + "threatIntelMode": "Alert", + "zones": [ + 1, + 2, + 3 + ] + }, + "bastionHost": { + "disableCopyPaste": true, + "enableFileCopy": false, + "enableIpConnect": false, + "enableShareableLink": false, + "scaleUnits": 2, + "skuName": "Standard" + }, + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "dnsServers": [ + "10.0.1.6", + "10.0.1.7" + ], + "enableAzureFirewall": true, + "enableBastion": true, + "enablePeering": false, + "enableTelemetry": true, + "flowTimeoutInMinutes": 30, + "location": "", + "lock": { + "kind": "CanNotDelete", + "name": "hub1Lock" + }, + "routes": [ + { + "name": "defaultRoute", + "properties": { + "addressPrefix": "0.0.0.0/0", + "nextHopType": "Internet" + } + } + ], + "subnets": [ + { + "addressPrefix": "", + "name": "GatewaySubnet" + }, + { + "addressPrefix": "", + "name": "AzureFirewallSubnet" + }, + { + "addressPrefix": "", + "name": "AzureBastionSubnet" + } + ], + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + }, + "vnetEncryption": false, + "vnetEncryptionEnforcement": "AllowUnencrypted" + } + } + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +## Parameters + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`hubVirtualNetworks`](#parameter-hubvirtualnetworks) | object | A map of the hub virtual networks to create. | +| [`location`](#parameter-location) | string | Location for all Resources. | + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `hubVirtualNetworks` + +A map of the hub virtual networks to create. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`>Any_other_property<`](#parameter-hubvirtualnetworks>any_other_property<) | object | The hub virtual networks to create. | + +### Parameter: `hubVirtualNetworks.>Any_other_property<` + +The hub virtual networks to create. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`addressPrefixes`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.addressPrefixes` + +The address prefixes for the virtual network. + +- Required: Yes +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings` + +The Azure Firewall config. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`additionalPublicIpConfigurations`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.azureFirewallSettings.additionalPublicIpConfigurations` + +Additional public IP configurations. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.applicationRuleCollections` + +Application rule collections. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.azureSkuTier` + +Azure Firewall SKU. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings` + +Diagnostic settings. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.azureFirewallSettings.diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyAny_other_property<.azureFirewallSettings.diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyAny_other_property<.azureFirewallSettings.diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.firewallPolicyId` + +Firewall policy ID. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.hubIpAddresses` + +Hub IP addresses. + +- Required: No +- Type: object + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.location` + +The location of the virtual network. Defaults to the location of the resource group. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.lock` + +Lock settings. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyAny_other_property<.azureFirewallSettings.lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.managementIPAddressObject` + +Management IP address configuration. + +- Required: No +- Type: object + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.managementIPResourceID` + +Management IP resource ID. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.natRuleCollections` + +NAT rule collections. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.networkRuleCollections` + +Network rule collections. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.publicIPAddressObject` + +Public IP address object. + +- Required: No +- Type: object + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.publicIPResourceID` + +Public IP resource ID. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments` + +Role assignments. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.azureFirewallSettings.roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.condition` + +The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.name` + +The name (as GUID) of the role assignment. If not provided, a GUID will be generated. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.tags` + +Tags of the resource. + +- Required: No +- Type: object + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.threatIntelMode` + +Threat Intel mode. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.virtualHub` + +Virtual Hub ID. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.azureFirewallSettings.zones` + +Zones. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.bastionHost` + +The Azure Bastion config. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`disableCopyPaste`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.bastionHost.disableCopyPaste` + +Enable/Disable copy/paste functionality. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.bastionHost.enableFileCopy` + +Enable/Disable file copy functionality. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.bastionHost.enableIpConnect` + +Enable/Disable IP connect functionality. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.bastionHost.enableShareableLink` + +Enable/Disable shareable link functionality. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.bastionHost.scaleUnits` + +The number of scale units for the Bastion host. Defaults to 4. + +- Required: No +- Type: int + +### Parameter: `hubVirtualNetworks.>Any_other_property<.bastionHost.skuName` + +The SKU name of the Bastion host. Defaults to Standard. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.ddosProtectionPlanResourceId` + +The DDoS protection plan resource ID. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings` + +The diagnostic settings of the virtual network. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyAny_other_property<.diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyAny_other_property<.diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.dnsServers` + +The DNS servers of the virtual network. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.enableAzureFirewall` + +Enable/Disable Azure Firewall for the virtual network. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.enableBastion` + +Enable/Disable Azure Bastion for the virtual network. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.enablePeering` + +Enable/Disable peering for the virtual network. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.flowTimeoutInMinutes` + +The flow timeout in minutes. + +- Required: No +- Type: int + +### Parameter: `hubVirtualNetworks.>Any_other_property<.location` + +The location of the virtual network. Defaults to the location of the resource group. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.lock` + +The lock settings of the virtual network. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyAny_other_property<.lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.peeringSettings` + +The peerings of the virtual network. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`allowForwardedTraffic`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.peeringSettings.allowForwardedTraffic` + +Allow forwarded traffic. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.peeringSettings.allowGatewayTransit` + +Allow gateway transit. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.peeringSettings.allowVirtualNetworkAccess` + +Allow virtual network access. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.peeringSettings.remoteVirtualNetworkName` + +Remote virtual network name. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.peeringSettings.useRemoteGateways` + +Use remote gateways. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments` + +The role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-hubvirtualnetworks>any_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyany_other_propertyAny_other_property<.roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.condition` + +The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.name` + +The name (as GUID) of the role assignment. If not provided, a GUID will be generated. + +- Required: No +- Type: string + +### Parameter: `hubVirtualNetworks.>Any_other_property<.roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `hubVirtualNetworks.>Any_other_property<.routes` + +Routes to add to the virtual network route table. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.subnets` + +The subnets of the virtual network. + +- Required: No +- Type: array + +### Parameter: `hubVirtualNetworks.>Any_other_property<.tags` + +The tags of the virtual network. + +- Required: No +- Type: object + +### Parameter: `hubVirtualNetworks.>Any_other_property<.vnetEncryption` + +Enable/Disable VNet encryption. + +- Required: No +- Type: bool + +### Parameter: `hubVirtualNetworks.>Any_other_property<.vnetEncryptionEnforcement` + +The VNet encryption enforcement settings of the virtual network. + +- Required: No +- Type: string + +### Parameter: `location` + +Location for all Resources. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `hubAzureFirewalls` | array | Array of hub Azure Firewall resources. | +| `hubBastions` | array | Array of hub bastion resources. | +| `hubVirtualNetworks` | array | Array of hub virtual network resources. | +| `hubVirtualNetworkSubnets` | array | The subnets of the hub virtual network. | + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/res/network/azure-firewall:0.5.0` | Remote reference | +| `br/public:avm/res/network/bastion-host:0.4.0` | Remote reference | +| `br/public:avm/res/network/route-table:0.4.0` | Remote reference | +| `br/public:avm/res/network/virtual-network:0.4.0` | Remote reference | + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/network/hub-networking/main.bicep b/avm/ptn/network/hub-networking/main.bicep new file mode 100644 index 00000000000..8406fc1cc18 --- /dev/null +++ b/avm/ptn/network/hub-networking/main.bicep @@ -0,0 +1,515 @@ +metadata name = 'Hub Networking' +metadata description = 'This module is designed to simplify the creation of multi-region hub networks in Azure. It will create a number of virtual networks and subnets, and optionally peer them together in a mesh topology with routing.' +metadata owner = 'Azure/module-maintainers' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// +// Add your parameters here +// + +@description('Optional. A map of the hub virtual networks to create.') +param hubVirtualNetworks hubVirtualNetworkType + +// +// Add your variables here +var hubVirtualNetworkPeerings = [for (hub, index) in items(hubVirtualNetworks ?? {}): hub.value.?peeringSettings ?? []] + +// ============== // +// Resources // +// ============== // + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.ptn.network-hubnetworking.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + 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' + } + } + } + } +} + +// Create hub virtual networks +module hubVirtualNetwork 'br/public:avm/res/network/virtual-network:0.4.0' = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): { + name: '${uniqueString(deployment().name, location)}-${hub.key}-nvn' + params: { + // Required parameters + name: hub.key + addressPrefixes: hub.value.addressPrefixes + // Non-required parameters + ddosProtectionPlanResourceId: hub.value.?ddosProtectionPlanResourceId ?? '' + diagnosticSettings: hub.value.?diagnosticSettings ?? [] + dnsServers: hub.value.?dnsServers ?? [] + enableTelemetry: hub.value.?enableTelemetry ?? true + flowTimeoutInMinutes: hub.value.?flowTimeoutInMinutes ?? 0 + location: hub.value.?location ?? '' + lock: hub.value.?lock ?? {} + roleAssignments: hub.value.?roleAssignments ?? [] + subnets: hub.value.?subnets ?? [] + tags: hub.value.?tags ?? {} + vnetEncryption: hub.value.?vnetEncryption ?? false + vnetEncryptionEnforcement: hub.value.?vnetEncryptionEnforcement ?? '' + } + } +] + +// Create hub virtual network peerings +module hubVirtualNetworkPeer_remote 'modules/vnets.bicep' = [ + for (peer, index) in flatten(hubVirtualNetworkPeerings): { + name: '${uniqueString(deployment().name, location)}-${peer.remoteVirtualNetworkName}-nvnp' + params: { + name: peer.remoteVirtualNetworkName + } + dependsOn: hubVirtualNetwork + } +] + +// Create hub virtual network peerings +// resource hubVirtualNetworkPeer_remote 'Microsoft.Network/virtualNetworks@2023-11-01' existing = [ +// for (peer, index) in flatten(hubVirtualNetworkPeerings): { +// name: peer.remoteVirtualNetworkName +// } +// ] + +resource hubVirtualNetworkPeer_local 'Microsoft.Network/virtualNetworks@2024-01-01' existing = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): if (hub.value.enablePeering) { + name: hub.key + } +] + +resource hubVirtualNetworkPeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2024-01-01' = [ + for (peer, index) in (flatten(hubVirtualNetworkPeerings) ?? []): { + name: '${hubVirtualNetworkPeer_local[index].name}/${hubVirtualNetworkPeer_local[index].name}-to-${peer.remoteVirtualNetworkName}-peering' + properties: { + allowForwardedTraffic: peer.allowForwardedTraffic ?? false + allowGatewayTransit: peer.allowGatewayTransit ?? false + allowVirtualNetworkAccess: peer.allowVirtualNetworkAccess ?? true + useRemoteGateways: peer.useRemoteGateways ?? false + remoteVirtualNetwork: { + id: hubVirtualNetworkPeer_remote[index].outputs.resourceId + } + } + dependsOn: hubVirtualNetwork + } +] + +// Create hub virtual network route tables +module hubRouteTable 'br/public:avm/res/network/route-table:0.4.0' = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): { + name: '${uniqueString(deployment().name, location)}-${hub.key}-nrt' + params: { + name: hub.key + location: hub.value.?location ?? location + disableBgpRoutePropagation: true + enableTelemetry: hub.value.?enableTelemetry ?? true + roleAssignments: hub.value.?roleAssignments ?? [] + routes: hub.value.?routes ?? [] + tags: hub.value.?tags ?? {} + } + dependsOn: hubVirtualNetwork + } +] + +// Create hub virtual network route table route +resource hubRoute 'Microsoft.Network/routeTables/routes@2024-01-01' = [ + for (peer, index) in (flatten(hubVirtualNetworkPeerings) ?? []): { + name: '${hubVirtualNetworkPeer_local[index].name}/${hubVirtualNetworkPeer_local[index].name}-to-${peer.remoteVirtualNetworkName}-route' + properties: { + addressPrefix: hubVirtualNetworkPeer_remote[index].outputs.addressPrefix + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubAzureFirewall[index].outputs.privateIp + } + dependsOn: hubVirtualNetworkPeering + } +] + +// Create Bastion host if enabled +// AzureBastionSubnet is required to deploy Bastion service. This subnet must exist in the parsubnets array if you enable Bastion Service. +// There is a minimum subnet requirement of /27 prefix. +module hubBastion 'br/public:avm/res/network/bastion-host:0.4.0' = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): if (hub.value.enableBastion) { + name: '${uniqueString(deployment().name, location)}-${hub.key}-nbh' + params: { + // Required parameters + name: hub.key + virtualNetworkResourceId: hubVirtualNetwork[index].outputs.resourceId + // Non-required parameters + diagnosticSettings: hub.value.?diagnosticSettings ?? [] + disableCopyPaste: hub.value.?bastionHost.?disableCopyPaste ?? true + enableFileCopy: hub.value.?bastionHost.?enableFileCopy ?? false + enableIpConnect: hub.value.?bastionHost.?enableIpConnect ?? false + enableShareableLink: hub.value.?bastionHost.?enableShareableLink ?? false + location: hub.value.?location ?? location + enableTelemetry: hub.value.?enableTelemetry ?? true + roleAssignments: hub.value.?roleAssignments ?? [] + scaleUnits: hub.value.?bastionHost.?scaleUnits ?? 4 + skuName: hub.value.?bastionHost.?skuName ?? 'Standard' + tags: hub.value.?tags ?? {} + } + dependsOn: hubVirtualNetwork + } +] + +// Create Azure Firewall if enabled +// AzureFirewallSubnet is required to deploy Azure Firewall service. This subnet must exist in the subnets array if you enable Azure Firewall. +module hubAzureFirewall 'br/public:avm/res/network/azure-firewall:0.5.0' = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): if (hub.value.enableAzureFirewall) { + name: '${uniqueString(deployment().name, location)}-${hub.key}-naf' + params: { + // Required parameters + name: hub.key + // Conditional parameters + hubIPAddresses: hub.value.?azureFirewallSettings.?hubIpAddresses ?? {} + virtualHubId: hub.value.?azureFirewallSettings.?virtualHub ?? '' + virtualNetworkResourceId: hubVirtualNetwork[index].outputs.resourceId ?? '' + // Non-required parameters + additionalPublicIpConfigurations: hub.value.?azureFirewallSettings.?additionalPublicIpConfigurations ?? [] + applicationRuleCollections: hub.value.?azureFirewallSettings.?applicationRuleCollections ?? [] + azureSkuTier: hub.value.?azureFirewallSettings.?azureSkuTier ?? {} + diagnosticSettings: hub.value.?diagnosticSettings ?? [] + enableTelemetry: hub.value.?enableTelemetry ?? true + firewallPolicyId: hub.value.?azureFirewallSettings.?firewallPolicyId ?? '' + location: hub.value.?location ?? location + lock: hub.value.?lock ?? {} + managementIPAddressObject: hub.value.?azureFirewallSettings.?managementIPAddressObject ?? {} + managementIPResourceID: hub.value.?azureFirewallSettings.?managementIPResourceID ?? '' + natRuleCollections: hub.value.?azureFirewallSettings.?natRuleCollections ?? [] + networkRuleCollections: hub.value.?azureFirewallSettings.?networkRuleCollections ?? [] + publicIPAddressObject: hub.value.?azureFirewallSettings.?publicIPAddressObject ?? {} + publicIPResourceID: hub.value.?azureFirewallSettings.?publicIPResourceID ?? '' + roleAssignments: hub.value.?roleAssignments ?? [] + tags: hub.value.?tags ?? {} + threatIntelMode: hub.value.?azureFirewallSettings.?threatIntelMode ?? '' + zones: hub.value.?azureFirewallSettings.?zones ?? [] + } + dependsOn: hubVirtualNetwork + } +] + +module hubAzureFirewallSubnet 'modules/getSubnet.bicep' = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): if (hub.value.enableAzureFirewall) { + name: '${uniqueString(deployment().name, location)}-${hub.key}-nafs' + params: { + subnetName: 'AzureFirewallSubnet' + virtualNetworkName: hub.key + } + dependsOn: [hubVirtualNetwork] + } +] + +@batchSize(1) +module hubAzureFirewallSubnetAssociation 'modules/subnets.bicep' = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): if (hub.value.enableAzureFirewall) { + name: '${uniqueString(deployment().name, location)}-${hub.key}-nafsa' + params: { + name: 'AzureFirewallSubnet' + virtualNetworkName: hub.key + addressPrefix: hubAzureFirewallSubnet[index].outputs.addressPrefix + routeTableResourceId: hubRouteTable[index].outputs.resourceId + } + dependsOn: [hubAzureFirewallSubnet, hubAzureFirewall, hubVirtualNetwork] + } +] + +// +// Add your resources here +// + +// ============ // +// Outputs // +// ============ // + +@description('Array of hub virtual network resources.') +output hubVirtualNetworks object[] = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): { + resourceGroupName: hubVirtualNetwork[index].outputs.resourceGroupName + location: hubVirtualNetwork[index].outputs.location + name: hubVirtualNetwork[index].outputs.name + resourceId: hubVirtualNetwork[index].outputs.resourceId + } +] + +@description('Array of hub bastion resources.') +output hubBastions object[] = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): { + resourceGroupName: hubBastion[index].outputs.resourceGroupName + location: hubBastion[index].outputs.location + name: hubBastion[index].outputs.name + resourceId: hubBastion[index].outputs.resourceId + } +] + +@description('Array of hub Azure Firewall resources.') +output hubAzureFirewalls object[] = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): { + resourceGroupName: hubAzureFirewall[index].outputs.resourceGroupName + location: hubAzureFirewall[index].outputs.location + name: hubAzureFirewall[index].outputs.name + resourceId: hubAzureFirewall[index].outputs.resourceId + } +] + +@description('The subnets of the hub virtual network.') +output hubVirtualNetworkSubnets array = [ + for (hub, index) in items(hubVirtualNetworks ?? {}): hubVirtualNetwork[index].outputs.subnetNames +] + +// ================ // +// Definitions // +// ================ // +// +// Add your User-defined-types here, if any +// + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type roleAssignmentType = { + @description('Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated.') + name: string? + + @description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs.') + categoryGroup: string? + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics.') + category: string + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') + eventHubAuthorizationRuleResourceId: string? + + @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? + +type hubVirtualNetworkType = { + @description('Required. The hub virtual networks to create.') + *: { + @description('Required. The address prefixes for the virtual network.') + addressPrefixes: array + + @description('Optional. The Azure Firewall config.') + azureFirewallSettings: azureFirewallType? + + @description('Optional. The Azure Bastion config.') + bastionHost: { + @description('Optional. Enable/Disable copy/paste functionality.') + disableCopyPaste: bool? + + @description('Optional. Enable/Disable file copy functionality.') + enableFileCopy: bool? + + @description('Optional. Enable/Disable IP connect functionality.') + enableIpConnect: bool? + + @description('Optional. Enable/Disable shareable link functionality.') + enableShareableLink: bool? + + @description('Optional. The number of scale units for the Bastion host. Defaults to 4.') + scaleUnits: int? + + @description('Optional. The SKU name of the Bastion host. Defaults to Standard.') + skuName: string? + }? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? + + @description('Optional. Enable/Disable Azure Bastion for the virtual network.') + enableBastion: bool? + + @description('Optional. Enable/Disable Azure Firewall for the virtual network.') + enableAzureFirewall: bool? + + @description('Optional. The location of the virtual network. Defaults to the location of the resource group.') + location: string? + + @description('Optional. The lock settings of the virtual network.') + lock: lockType? + + @description('Optional. The diagnostic settings of the virtual network.') + diagnosticSettings: diagnosticSettingType? + + @description('Optional. The DDoS protection plan resource ID.') + ddosProtectionPlanResourceId: string? + + @description('Optional. The DNS servers of the virtual network.') + dnsServers: array? + + @description('Optional. The flow timeout in minutes.') + flowTimeoutInMinutes: int? + + @description('Optional. Enable/Disable peering for the virtual network.') + enablePeering: bool? + + @description('Optional. The peerings of the virtual network.') + peeringSettings: peeringSettingsType? + + @description('Optional. The role assignments to create.') + roleAssignments: roleAssignmentType? + + @description('Optional. Routes to add to the virtual network route table.') + routes: array? + + @description('Optional. The subnets of the virtual network.') + subnets: array? + + @description('Optional. The tags of the virtual network.') + tags: object? + + @description('Optional. Enable/Disable VNet encryption.') + vnetEncryption: bool? + + @description('Optional. The VNet encryption enforcement settings of the virtual network.') + vnetEncryptionEnforcement: string? + } +}? + +type peeringSettingsType = { + @description('Optional. Allow forwarded traffic.') + allowForwardedTraffic: bool? + + @description('Optional. Allow gateway transit.') + allowGatewayTransit: bool? + + @description('Optional. Allow virtual network access.') + allowVirtualNetworkAccess: bool? + + @description('Optional. Use remote gateways.') + useRemoteGateways: bool? + + @description('Optional. Remote virtual network name.') + remoteVirtualNetworkName: string? +}[]? + +type azureFirewallType = { + @description('Optional. Hub IP addresses.') + hubIpAddresses: object? + + @description('Optional. Virtual Hub ID.') + virtualHub: string? + + @description('Optional. Additional public IP configurations.') + additionalPublicIpConfigurations: array? + + @description('Optional. Application rule collections.') + applicationRuleCollections: array? + + @description('Optional. Azure Firewall SKU.') + azureSkuTier: string? + + @description('Optional. Diagnostic settings.') + diagnosticSettings: diagnosticSettingType? + + @description('Optional. Enable/Disable usage telemetry for module.') + enableTelemetry: bool? + + @description('Optional. Firewall policy ID.') + firewallPolicyId: string? + + @description('Optional. The location of the virtual network. Defaults to the location of the resource group.') + location: string? + + @description('Optional. Lock settings.') + lock: lockType? + + @description('Optional. Management IP address configuration.') + managementIPAddressObject: object? + + @description('Optional. Management IP resource ID.') + managementIPResourceID: string? + + @description('Optional. NAT rule collections.') + natRuleCollections: array? + + @description('Optional. Network rule collections.') + networkRuleCollections: array? + + @description('Optional. Public IP address object.') + publicIPAddressObject: object? + + @description('Optional. Public IP resource ID.') + publicIPResourceID: string? + + @description('Optional. Role assignments.') + roleAssignments: roleAssignmentType? + + @description('Optional. Tags of the resource.') + tags: object? + + @description('Optional. Threat Intel mode.') + threatIntelMode: string? + + @description('Optional. Zones.') + zones: int[]? +}? diff --git a/avm/ptn/network/hub-networking/main.json b/avm/ptn/network/hub-networking/main.json new file mode 100644 index 00000000000..b2149b348cc --- /dev/null +++ b/avm/ptn/network/hub-networking/main.json @@ -0,0 +1,6899 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "4469274513824919483" + }, + "name": "Hub Networking", + "description": "This module is designed to simplify the creation of multi-region hub networks in Azure. It will create a number of virtual networks and subnets, and optionally peer them together in a mesh topology with routing.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.value." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "hubVirtualNetworkType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "type": "object", + "properties": { + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "Required. The address prefixes for the virtual network." + } + }, + "azureFirewallSettings": { + "$ref": "#/definitions/azureFirewallType", + "nullable": true, + "metadata": { + "description": "Optional. The Azure Firewall config." + } + }, + "bastionHost": { + "type": "object", + "properties": { + "disableCopyPaste": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable copy/paste functionality." + } + }, + "enableFileCopy": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable file copy functionality." + } + }, + "enableIpConnect": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable IP connect functionality." + } + }, + "enableShareableLink": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable shareable link functionality." + } + }, + "scaleUnits": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of scale units for the Bastion host. Defaults to 4." + } + }, + "skuName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The SKU name of the Bastion host. Defaults to Standard." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The Azure Bastion config." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "enableBastion": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable Azure Bastion for the virtual network." + } + }, + "enableAzureFirewall": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable Azure Firewall for the virtual network." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location of the virtual network. Defaults to the location of the resource group." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the virtual network." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the virtual network." + } + }, + "ddosProtectionPlanResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan resource ID." + } + }, + "dnsServers": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The DNS servers of the virtual network." + } + }, + "flowTimeoutInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The flow timeout in minutes." + } + }, + "enablePeering": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable peering for the virtual network." + } + }, + "peeringSettings": { + "$ref": "#/definitions/peeringSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The peerings of the virtual network." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "nullable": true, + "metadata": { + "description": "Optional. The role assignments to create." + } + }, + "routes": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Routes to add to the virtual network route table." + } + }, + "subnets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The subnets of the virtual network." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the virtual network." + } + }, + "vnetEncryption": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable VNet encryption." + } + }, + "vnetEncryptionEnforcement": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The VNet encryption enforcement settings of the virtual network." + } + } + }, + "metadata": { + "description": "Required. The hub virtual networks to create." + } + }, + "nullable": true + }, + "peeringSettingsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "allowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Allow forwarded traffic." + } + }, + "allowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Allow gateway transit." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Allow virtual network access." + } + }, + "useRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Use remote gateways." + } + }, + "remoteVirtualNetworkName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Remote virtual network name." + } + } + } + }, + "nullable": true + }, + "azureFirewallType": { + "type": "object", + "properties": { + "hubIpAddresses": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Hub IP addresses." + } + }, + "virtualHub": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Virtual Hub ID." + } + }, + "additionalPublicIpConfigurations": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Additional public IP configurations." + } + }, + "applicationRuleCollections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application rule collections." + } + }, + "azureSkuTier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure Firewall SKU." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "nullable": true, + "metadata": { + "description": "Optional. Diagnostic settings." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "firewallPolicyId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Firewall policy ID." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location of the virtual network. Defaults to the location of the resource group." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. Lock settings." + } + }, + "managementIPAddressObject": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Management IP address configuration." + } + }, + "managementIPResourceID": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Management IP resource ID." + } + }, + "natRuleCollections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. NAT rule collections." + } + }, + "networkRuleCollections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Network rule collections." + } + }, + "publicIPAddressObject": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Public IP address object." + } + }, + "publicIPResourceID": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Public IP resource ID." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "nullable": true, + "metadata": { + "description": "Optional. Role assignments." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "threatIntelMode": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Threat Intel mode." + } + }, + "zones": { + "type": "array", + "items": { + "type": "int" + }, + "nullable": true, + "metadata": { + "description": "Optional. Zones." + } + } + }, + "nullable": true + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "hubVirtualNetworks": { + "$ref": "#/definitions/hubVirtualNetworkType", + "metadata": { + "description": "Optional. A map of the hub virtual networks to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "hubVirtualNetworkPeerings", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", + "input": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex('hubVirtualNetworkPeerings')].value, 'peeringSettings'), createArray())]" + } + ] + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.ptn.network-hubnetworking.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "hubVirtualNetworkPeer_local": { + "copy": { + "name": "hubVirtualNetworkPeer_local", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]" + }, + "condition": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enablePeering]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + }, + "hubVirtualNetworkPeering": { + "copy": { + "name": "hubVirtualNetworkPeering", + "count": "[length(coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray()))]" + }, + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}-to-{2}-peering', items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key, items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key, coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray())[copyIndex()].remoteVirtualNetworkName)]", + "properties": { + "allowForwardedTraffic": "[coalesce(coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray())[copyIndex()].allowForwardedTraffic, false())]", + "allowGatewayTransit": "[coalesce(coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray())[copyIndex()].allowGatewayTransit, false())]", + "allowVirtualNetworkAccess": "[coalesce(coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray())[copyIndex()].allowVirtualNetworkAccess, true())]", + "useRemoteGateways": "[coalesce(coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray())[copyIndex()].useRemoteGateways, false())]", + "remoteVirtualNetwork": { + "id": "[reference(format('hubVirtualNetworkPeer_remote[{0}]', copyIndex())).outputs.resourceId.value]" + } + }, + "dependsOn": [ + "hubVirtualNetwork", + "[format('hubVirtualNetworkPeer_local[{0}]', copyIndex())]", + "[format('hubVirtualNetworkPeer_local[{0}]', copyIndex())]", + "[format('hubVirtualNetworkPeer_remote[{0}]', copyIndex())]" + ] + }, + "hubRoute": { + "copy": { + "name": "hubRoute", + "count": "[length(coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray()))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}-to-{2}-route', items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key, items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key, coalesce(flatten(variables('hubVirtualNetworkPeerings')), createArray())[copyIndex()].remoteVirtualNetworkName)]", + "properties": { + "addressPrefix": "[reference(format('hubVirtualNetworkPeer_remote[{0}]', copyIndex())).outputs.addressPrefix.value]", + "nextHopType": "VirtualAppliance", + "nextHopIpAddress": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.privateIp.value]" + }, + "dependsOn": [ + "[format('hubAzureFirewall[{0}]', copyIndex())]", + "[format('hubVirtualNetworkPeer_local[{0}]', copyIndex())]", + "[format('hubVirtualNetworkPeer_local[{0}]', copyIndex())]", + "[format('hubVirtualNetworkPeer_remote[{0}]', copyIndex())]", + "hubVirtualNetworkPeering" + ] + }, + "hubVirtualNetwork": { + "copy": { + "name": "hubVirtualNetwork", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-nvn', uniqueString(deployment().name, parameters('location')), items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + }, + "addressPrefixes": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.addressPrefixes]" + }, + "ddosProtectionPlanResourceId": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'ddosProtectionPlanResourceId'), '')]" + }, + "diagnosticSettings": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'diagnosticSettings'), createArray())]" + }, + "dnsServers": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'dnsServers'), createArray())]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'enableTelemetry'), true())]" + }, + "flowTimeoutInMinutes": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'flowTimeoutInMinutes'), 0)]" + }, + "location": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'location'), '')]" + }, + "lock": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'lock'), createObject())]" + }, + "roleAssignments": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'roleAssignments'), createArray())]" + }, + "subnets": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'subnets'), createArray())]" + }, + "tags": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'tags'), createObject())]" + }, + "vnetEncryption": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'vnetEncryption'), false())]" + }, + "vnetEncryptionEnforcement": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'vnetEncryptionEnforcement'), '')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "15949466154563447171" + }, + "name": "Virtual Networks", + "description": "This module deploys a Virtual Network (vNet).", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "peeringType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be peer-localVnetName-remoteVnetName." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + }, + "remotePeeringEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Deploy the outbound and the inbound peering." + } + }, + "remotePeeringName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the VNET Peering resource in the remove Virtual Network. If not provided, default value will be peer-remoteVnetName-localVnetName." + } + }, + "remotePeeringAllowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "remotePeeringAllowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "remotePeeringAllowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "remotePeeringDoNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "remotePeeringUseRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private link service in the subnet." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "routeTableResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "serviceEndpoints": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Virtual Network (vNet)." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network." + } + }, + "virtualNetworkBgpCommunity": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The BGP community associated with the virtual network." + } + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An Array of subnets to deploy to the Virtual Network." + } + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. DNS Servers associated to the Virtual Network." + } + }, + "ddosProtectionPlanResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the DDoS protection plan to assign the VNET to. If it's left blank, DDoS protection will not be configured. If it's provided, the VNET created by this template will be attached to the referenced DDoS protection plan. The DDoS protection plan can exist in the same or in a different subscription." + } + }, + "peerings": { + "type": "array", + "items": { + "$ref": "#/definitions/peeringType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Virtual Network Peering configurations." + } + }, + "vnetEncryption": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates if encryption is enabled on virtual network and if VM without encryption is allowed in encrypted VNet. Requires the EnableVNetEncryption feature to be registered for the subscription and a supported region to use this property." + } + }, + "vnetEncryptionEnforcement": { + "type": "string", + "defaultValue": "AllowUnencrypted", + "allowedValues": [ + "AllowUnencrypted", + "DropUnencrypted" + ], + "metadata": { + "description": "Optional. If the encrypted VNet allows VM that does not support encryption. Can only be used when vnetEncryption is enabled." + } + }, + "flowTimeoutInMinutes": { + "type": "int", + "defaultValue": 0, + "maxValue": 30, + "metadata": { + "description": "Optional. The flow timeout in minutes for the Virtual Network, which is used to enable connection tracking for intra-VM flows. Possible values are between 4 and 30 minutes. Default value 0 will set the property to null." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "enableVmProtection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates if VM protection is enabled for all the subnets in the virtual network." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "virtualNetwork": { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "addressSpace": { + "addressPrefixes": "[parameters('addressPrefixes')]" + }, + "bgpCommunities": "[if(not(empty(parameters('virtualNetworkBgpCommunity'))), createObject('virtualNetworkCommunity', parameters('virtualNetworkBgpCommunity')), null())]", + "ddosProtectionPlan": "[if(not(empty(parameters('ddosProtectionPlanResourceId'))), createObject('id', parameters('ddosProtectionPlanResourceId')), null())]", + "dhcpOptions": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', array(parameters('dnsServers'))), null())]", + "enableDdosProtection": "[not(empty(parameters('ddosProtectionPlanResourceId')))]", + "encryption": "[if(equals(parameters('vnetEncryption'), true()), createObject('enabled', parameters('vnetEncryption'), 'enforcement', parameters('vnetEncryptionEnforcement')), null())]", + "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]", + "enableVmProtection": "[parameters('enableVmProtection')]" + } + }, + "virtualNetwork_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_diagnosticSettings": { + "copy": { + "name": "virtualNetwork_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_roleAssignments": { + "copy": { + "name": "virtualNetwork_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_subnets": { + "copy": { + "name": "virtualNetwork_subnets", + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-subnet-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('subnets'), createArray())[copyIndex()].name]" + }, + "addressPrefix": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefix')]" + }, + "addressPrefixes": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefixes')]" + }, + "applicationGatewayIPConfigurations": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'applicationGatewayIPConfigurations')]" + }, + "delegation": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'delegation')]" + }, + "natGatewayResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'natGatewayResourceId')]" + }, + "networkSecurityGroupResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'networkSecurityGroupResourceId')]" + }, + "privateEndpointNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateEndpointNetworkPolicies')]" + }, + "privateLinkServiceNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateLinkServiceNetworkPolicies')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "routeTableResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'routeTableResourceId')]" + }, + "serviceEndpointPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpointPolicies')]" + }, + "serviceEndpoints": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpoints')]" + }, + "defaultOutboundAccess": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'defaultOutboundAccess')]" + }, + "sharingScope": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'sharingScope')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5699372618313647761" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Requird. The Name of the subnet resource." + } + }, + "virtualNetworkName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual network. Required if the template is used in a standalone deployment." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "routeTableResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpoints": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private link service in the subnet." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." + } + }, + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "virtualNetwork": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "serviceEndpoints", + "count": "[length(parameters('serviceEndpoints'))]", + "input": { + "service": "[parameters('serviceEndpoints')[copyIndex('serviceEndpoints')]]" + } + } + ], + "addressPrefix": "[parameters('addressPrefix')]", + "addressPrefixes": "[parameters('addressPrefixes')]", + "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]", + "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]", + "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]", + "delegations": "[if(not(empty(parameters('delegation'))), createArray(createObject('name', parameters('delegation'), 'properties', createObject('serviceName', parameters('delegation')))), createArray())]", + "privateEndpointNetworkPolicies": "[if(not(empty(parameters('privateEndpointNetworkPolicies'))), parameters('privateEndpointNetworkPolicies'), null())]", + "privateLinkServiceNetworkPolicies": "[if(not(empty(parameters('privateLinkServiceNetworkPolicies'))), parameters('privateLinkServiceNetworkPolicies'), null())]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "subnet_roleAssignments": { + "copy": { + "name": "subnet_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "subnet" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]" + }, + "addressPrefix": { + "type": "string", + "metadata": { + "description": "The address prefix for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefix'), '')]" + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "List of address prefixes for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefixes'), createArray())]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_peering_local": { + "copy": { + "name": "virtualNetwork_peering_local", + "count": "[length(coalesce(parameters('peerings'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetworkPeering-local-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "localVnetName": { + "value": "[parameters('name')]" + }, + "remoteVirtualNetworkResourceId": { + "value": "[coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'name')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'doNotVerifyRemoteGateways')]" + }, + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'useRemoteGateways')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + } + }, + "localVnetName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", + "properties": { + "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", + "allowGatewayTransit": "[parameters('allowGatewayTransit')]", + "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]", + "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", + "useRemoteGateways": "[parameters('useRemoteGateways')]", + "remoteVirtualNetwork": { + "id": "[parameters('remoteVirtualNetworkResourceId')]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_peering_remote": { + "copy": { + "name": "virtualNetwork_peering_remote", + "count": "[length(coalesce(parameters('peerings'), createArray()))]" + }, + "condition": "[coalesce(tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringEnabled'), false())]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetworkPeering-remote-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[2]]", + "resourceGroup": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "localVnetName": { + "value": "[last(split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/'))]" + }, + "remoteVirtualNetworkResourceId": { + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringName')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways')]" + }, + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringUseRemoteGateways')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + } + }, + "localVnetName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", + "properties": { + "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", + "allowGatewayTransit": "[parameters('allowGatewayTransit')]", + "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]", + "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", + "useRemoteGateways": "[parameters('useRemoteGateways')]", + "remoteVirtualNetwork": { + "id": "[parameters('remoteVirtualNetworkResourceId')]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network." + }, + "value": "[parameters('name')]" + }, + "subnetNames": { + "type": "array", + "metadata": { + "description": "The names of the deployed subnets." + }, + "copy": { + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.name.value]" + } + }, + "subnetResourceIds": { + "type": "array", + "metadata": { + "description": "The resource IDs of the deployed subnets." + }, + "copy": { + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.resourceId.value]" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('virtualNetwork', '2024-01-01', 'full').location]" + } + } + } + } + }, + "hubVirtualNetworkPeer_remote": { + "copy": { + "name": "hubVirtualNetworkPeer_remote", + "count": "[length(flatten(variables('hubVirtualNetworkPeerings')))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-nvnp', uniqueString(deployment().name, parameters('location')), flatten(variables('hubVirtualNetworkPeerings'))[copyIndex()].remoteVirtualNetworkName)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[flatten(variables('hubVirtualNetworkPeerings'))[copyIndex()].remoteVirtualNetworkName]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "15958982442955537466" + }, + "name": "Virtual Networks", + "description": "This module deploys a Virtual Network.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent virtual network. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "addressPrefix": { + "type": "string", + "metadata": { + "description": "The address space of the virtual network." + }, + "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), '2024-01-01').addressSpace.addressPrefixes[0]]" + } + } + } + }, + "dependsOn": [ + "hubVirtualNetwork" + ] + }, + "hubRouteTable": { + "copy": { + "name": "hubRouteTable", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-nrt', uniqueString(deployment().name, parameters('location')), items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + }, + "location": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'location'), parameters('location'))]" + }, + "disableBgpRoutePropagation": { + "value": true + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'enableTelemetry'), true())]" + }, + "roleAssignments": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'roleAssignments'), createArray())]" + }, + "routes": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'routes'), createArray())]" + }, + "tags": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'tags'), createObject())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5827817137345359685" + }, + "name": "Route Tables", + "description": "This module deploys a User Defined Route Table (UDR).", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "routeType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the route." + } + }, + "properties": { + "type": "object", + "properties": { + "nextHopType": { + "type": "string", + "allowedValues": [ + "Internet", + "None", + "VirtualAppliance", + "VirtualNetworkGateway", + "VnetLocal" + ], + "metadata": { + "description": "Required. The type of Azure hop the packet should be sent to." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination CIDR to which the route applies." + } + }, + "hasBgpOverride": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. A value indicating whether this route overrides overlapping BGP routes regardless of LPM." + } + }, + "nextHopIpAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The IP address packets should be forwarded to. Next hop values are only allowed in routes where the next hop type is VirtualAppliance." + } + } + }, + "metadata": { + "description": "Required. Properties of the route." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name given for the hub route table." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "routes": { + "$ref": "#/definitions/routeType", + "metadata": { + "description": "Optional. An array of routes to be established within the hub route table." + } + }, + "disableBgpRoutePropagation": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Switch to disable BGP route propagation." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[take(format('46d3xbcp.res.network-routetable.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('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" + } + } + } + } + }, + "routeTable": { + "type": "Microsoft.Network/routeTables", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "routes": "[parameters('routes')]", + "disableBgpRoutePropagation": "[parameters('disableBgpRoutePropagation')]" + } + }, + "routeTable_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/routeTables/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "routeTable" + ] + }, + "routeTable_roleAssignments": { + "copy": { + "name": "routeTable_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/routeTables/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/routeTables', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "routeTable" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the route table was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the route table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the route table." + }, + "value": "[resourceId('Microsoft.Network/routeTables', parameters('name'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('routeTable', '2023-04-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "hubVirtualNetwork" + ] + }, + "hubBastion": { + "copy": { + "name": "hubBastion", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]" + }, + "condition": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enableBastion]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-nbh', uniqueString(deployment().name, parameters('location')), items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + }, + "virtualNetworkResourceId": { + "value": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.resourceId.value]" + }, + "diagnosticSettings": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'diagnosticSettings'), createArray())]" + }, + "disableCopyPaste": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'bastionHost'), 'disableCopyPaste'), true())]" + }, + "enableFileCopy": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'bastionHost'), 'enableFileCopy'), false())]" + }, + "enableIpConnect": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'bastionHost'), 'enableIpConnect'), false())]" + }, + "enableShareableLink": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'bastionHost'), 'enableShareableLink'), false())]" + }, + "location": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'location'), parameters('location'))]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'enableTelemetry'), true())]" + }, + "roleAssignments": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'roleAssignments'), createArray())]" + }, + "scaleUnits": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'bastionHost'), 'scaleUnits'), 4)]" + }, + "skuName": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'bastionHost'), 'skuName'), 'Standard')]" + }, + "tags": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'tags'), createObject())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "11368267491813619372" + }, + "name": "Bastion Hosts", + "description": "This module deploys a Bastion Host.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Bastion resource." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "virtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. Shared services Virtual Network resource Id." + } + }, + "bastionSubnetPublicIpResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Public IP resource ID to associate to the azureBastionSubnet. If empty, then the Public IP that is created as part of this module will be applied to the azureBastionSubnet." + } + }, + "publicIPAddressObject": { + "type": "object", + "defaultValue": { + "name": "[format('{0}-pip', parameters('name'))]" + }, + "metadata": { + "description": "Optional. Specifies the properties of the Public IP to create and be used by Azure Bastion, if no existing public IP was provided." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Standard" + ], + "metadata": { + "description": "Optional. The SKU of this Bastion Host." + } + }, + "disableCopyPaste": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Copy Paste." + } + }, + "enableFileCopy": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Choose to disable or enable File Copy." + } + }, + "enableIpConnect": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable IP Connect." + } + }, + "enableKerberos": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Kerberos authentication." + } + }, + "enableShareableLink": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Shareable Link." + } + }, + "scaleUnits": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. The scale units for the Bastion Host resource." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-bastionhost.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "azureBastion": { + "type": "Microsoft.Network/bastionHosts", + "apiVersion": "2022-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "properties": "[union(createObject('scaleUnits', if(equals(parameters('skuName'), 'Basic'), 2, parameters('scaleUnits')), 'ipConfigurations', createArray(createObject('name', 'IpConfAzureBastionSubnet', 'properties', union(createObject('subnet', createObject('id', format('{0}/subnets/AzureBastionSubnet', parameters('virtualNetworkResourceId')))), createObject('publicIPAddress', createObject('id', if(not(empty(parameters('bastionSubnetPublicIpResourceId'))), parameters('bastionSubnetPublicIpResourceId'), reference('publicIPAddress').outputs.resourceId.value)))))), 'enableKerberos', parameters('enableKerberos')), if(equals(parameters('skuName'), 'Standard'), createObject('enableTunneling', equals(parameters('skuName'), 'Standard'), 'disableCopyPaste', parameters('disableCopyPaste'), 'enableFileCopy', parameters('enableFileCopy'), 'enableIpConnect', parameters('enableIpConnect'), 'enableShareableLink', parameters('enableShareableLink')), createObject()))]", + "dependsOn": [ + "publicIPAddress" + ] + }, + "azureBastion_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/bastionHosts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "azureBastion" + ] + }, + "azureBastion_diagnosticSettings": { + "copy": { + "name": "azureBastion_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/bastionHosts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "azureBastion" + ] + }, + "azureBastion_roleAssignments": { + "copy": { + "name": "azureBastion_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/bastionHosts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/bastionHosts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "azureBastion" + ] + }, + "publicIPAddress": { + "condition": "[empty(parameters('bastionSubnetPublicIpResourceId'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Bastion-PIP', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('publicIPAddressObject').name]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'diagnosticSettings')]" + }, + "publicIPAddressVersion": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'publicIPAddressVersion')]" + }, + "publicIPAllocationMethod": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'publicIPAllocationMethod')]" + }, + "publicIpPrefixResourceId": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'publicIPPrefixResourceId')]" + }, + "roleAssignments": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'roleAssignments')]" + }, + "skuName": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'skuName')]" + }, + "skuTier": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'skuTier')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('publicIPAddressObject'), 'tags'), parameters('tags'))]" + }, + "zones": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'zones')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "14450344965065009842" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "dnsSettingsType": { + "type": "object", + "properties": { + "domainNameLabel": { + "type": "string", + "metadata": { + "description": "Required. The domain name label. The concatenation of the domain name label and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." + } + }, + "domainNameLabelScope": { + "type": "string", + "allowedValues": [ + "", + "NoReuse", + "ResourceGroupReuse", + "SubscriptionReuse", + "TenantReuse" + ], + "metadata": { + "description": "Required. The domain name label scope. If a domain name label and a domain name label scope are specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system with a hashed value includes in FQDN." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Fully Qualified Domain Name of the A DNS record associated with the public IP. This is the concatenation of the domainNameLabel and the regionalized DNS zone." + } + }, + "reverseFqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The reverse FQDN. A user-visible, fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN." + } + } + } + }, + "ddosSettingsType": { + "type": "object", + "properties": { + "ddosProtectionPlan": { + "type": "object", + "properties": { + "id": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the DDOS protection plan associated with the public IP address." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan associated with the public IP address." + } + }, + "protectionMode": { + "type": "string", + "allowedValues": [ + "Enabled" + ], + "metadata": { + "description": "Required. The DDoS protection policy customizations." + } + } + } + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Public IP Address." + } + }, + "publicIpPrefixResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the Public IP Prefix object. This is only needed if you want your Public IPs created in a PIP Prefix." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "defaultValue": "Static", + "allowedValues": [ + "Dynamic", + "Static" + ], + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "zones": { + "type": "array", + "items": { + "type": "int" + }, + "defaultValue": [ + 1, + 2, + 3 + ], + "allowedValues": [ + 1, + 2, + 3 + ], + "metadata": { + "description": "Optional. A list of availability zones denoting the IP allocated for the resource needs to come from." + } + }, + "publicIPAddressVersion": { + "type": "string", + "defaultValue": "IPv4", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "metadata": { + "description": "Optional. IP address version." + } + }, + "dnsSettings": { + "$ref": "#/definitions/dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Basic", + "Standard" + ], + "metadata": { + "description": "Optional. Name of a public IP address SKU." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "Regional", + "allowedValues": [ + "Global", + "Regional" + ], + "metadata": { + "description": "Optional. Tier of a public IP address SKU." + } + }, + "ddosSettings": { + "$ref": "#/definitions/ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "defaultValue": 4, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-publicipaddress.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('skuName')]", + "tier": "[parameters('skuTier')]" + }, + "zones": "[map(parameters('zones'), lambda('zone', string(lambdaVariables('zone'))))]", + "properties": { + "ddosSettings": "[parameters('ddosSettings')]", + "dnsSettings": "[parameters('dnsSettings')]", + "publicIPAddressVersion": "[parameters('publicIPAddressVersion')]", + "publicIPAllocationMethod": "[parameters('publicIPAllocationMethod')]", + "publicIPPrefix": "[if(not(empty(parameters('publicIpPrefixResourceId'))), createObject('id', parameters('publicIpPrefixResourceId')), null())]", + "idleTimeoutInMinutes": "[parameters('idleTimeoutInMinutes')]", + "ipTags": null + } + }, + "publicIpAddress_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + }, + "publicIpAddress_roleAssignments": { + "copy": { + "name": "publicIpAddress_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/publicIPAddresses', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + }, + "publicIpAddress_diagnosticSettings": { + "copy": { + "name": "publicIpAddress_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the public IP address was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the public IP address." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the public IP address." + }, + "value": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('name'))]" + }, + "ipAddress": { + "type": "string", + "metadata": { + "description": "The public IP address of the public IP address resource." + }, + "value": "[coalesce(tryGet(reference('publicIpAddress'), 'ipAddress'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('publicIpAddress', '2023-09-01', 'full').location]" + } + } + } + } + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the Azure Bastion was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name the Azure Bastion." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID the Azure Bastion." + }, + "value": "[resourceId('Microsoft.Network/bastionHosts', parameters('name'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('azureBastion', '2022-11-01', 'full').location]" + }, + "ipConfAzureBastionSubnet": { + "type": "object", + "metadata": { + "description": "The Public IPconfiguration object for the AzureBastionSubnet." + }, + "value": "[reference('azureBastion').ipConfigurations[0]]" + } + } + } + }, + "dependsOn": [ + "hubVirtualNetwork" + ] + }, + "hubAzureFirewall": { + "copy": { + "name": "hubAzureFirewall", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]" + }, + "condition": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enableAzureFirewall]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-naf', uniqueString(deployment().name, parameters('location')), items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + }, + "hubIPAddresses": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'hubIpAddresses'), createObject())]" + }, + "virtualHubId": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'virtualHub'), '')]" + }, + "virtualNetworkResourceId": { + "value": "[coalesce(reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.resourceId.value, '')]" + }, + "additionalPublicIpConfigurations": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'additionalPublicIpConfigurations'), createArray())]" + }, + "applicationRuleCollections": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'applicationRuleCollections'), createArray())]" + }, + "azureSkuTier": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'azureSkuTier'), createObject())]" + }, + "diagnosticSettings": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'diagnosticSettings'), createArray())]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'enableTelemetry'), true())]" + }, + "firewallPolicyId": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'firewallPolicyId'), '')]" + }, + "location": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'location'), parameters('location'))]" + }, + "lock": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'lock'), createObject())]" + }, + "managementIPAddressObject": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'managementIPAddressObject'), createObject())]" + }, + "managementIPResourceID": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'managementIPResourceID'), '')]" + }, + "natRuleCollections": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'natRuleCollections'), createArray())]" + }, + "networkRuleCollections": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'networkRuleCollections'), createArray())]" + }, + "publicIPAddressObject": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'publicIPAddressObject'), createObject())]" + }, + "publicIPResourceID": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'publicIPResourceID'), '')]" + }, + "roleAssignments": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'roleAssignments'), createArray())]" + }, + "tags": { + "value": "[coalesce(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'tags'), createObject())]" + }, + "threatIntelMode": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'threatIntelMode'), '')]" + }, + "zones": { + "value": "[coalesce(tryGet(tryGet(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value, 'azureFirewallSettings'), 'zones'), createArray())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "15791050653269307918" + }, + "name": "Azure Firewalls", + "description": "This module deploys an Azure Firewall.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "natRuleCollectionType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the NAT rule collection." + } + }, + "properties": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "Dnat", + "Snat" + ], + "metadata": { + "description": "Required. The type of action." + } + } + }, + "metadata": { + "description": "Required. The action type of a NAT rule collection." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 65000, + "metadata": { + "description": "Required. Priority of the NAT rule collection." + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the NAT rule." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description of the rule." + } + }, + "protocols": { + "type": "array", + "allowedValues": [ + "Any", + "ICMP", + "TCP", + "UDP" + ], + "metadata": { + "description": "Required. Array of AzureFirewallNetworkRuleProtocols applicable to this NAT rule." + } + }, + "destinationAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of destination IP addresses for this rule. Supports IP ranges, prefixes, and service tags." + } + }, + "destinationPorts": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of destination ports." + } + }, + "sourceAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of source IP addresses for this rule." + } + }, + "sourceIpGroups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of source IpGroups for this rule." + } + }, + "translatedAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The translated address for this NAT rule." + } + }, + "translatedFqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The translated FQDN for this NAT rule." + } + }, + "translatedPort": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The translated port for this NAT rule." + } + } + } + }, + "metadata": { + "description": "Required. Collection of rules used by a NAT rule collection." + } + } + }, + "metadata": { + "description": "Required. Properties of the azure firewall NAT rule collection." + } + } + } + }, + "nullable": true + }, + "applicationRuleCollectionType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the application rule collection." + } + }, + "properties": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. The type of action." + } + } + }, + "metadata": { + "description": "Required. The action type of a rule collection." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 65000, + "metadata": { + "description": "Required. Priority of the application rule collection." + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the application rule." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description of the rule." + } + }, + "protocols": { + "type": "array", + "items": { + "type": "object", + "properties": { + "port": { + "type": "int", + "nullable": true, + "maxValue": 64000, + "metadata": { + "description": "Optional. Port number for the protocol." + } + }, + "protocolType": { + "type": "string", + "allowedValues": [ + "Http", + "Https", + "Mssql" + ], + "metadata": { + "description": "Required. Protocol type." + } + } + } + }, + "metadata": { + "description": "Required. Array of ApplicationRuleProtocols." + } + }, + "fqdnTags": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of FQDN Tags for this rule." + } + }, + "targetFqdns": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of FQDNs for this rule." + } + }, + "sourceAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of source IP addresses for this rule." + } + }, + "sourceIpGroups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of source IpGroups for this rule." + } + } + } + }, + "metadata": { + "description": "Required. Collection of rules used by a application rule collection." + } + } + }, + "metadata": { + "description": "Required. Properties of the azure firewall application rule collection." + } + } + } + }, + "nullable": true + }, + "networkRuleCollectionType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the network rule collection." + } + }, + "properties": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. The type of action." + } + } + }, + "metadata": { + "description": "Required. The action type of a rule collection." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 65000, + "metadata": { + "description": "Required. Priority of the network rule collection." + } + }, + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the network rule." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description of the rule." + } + }, + "protocols": { + "type": "array", + "allowedValues": [ + "Any", + "ICMP", + "TCP", + "UDP" + ], + "metadata": { + "description": "Required. Array of AzureFirewallNetworkRuleProtocols." + } + }, + "destinationAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of destination IP addresses." + } + }, + "destinationFqdns": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of destination FQDNs." + } + }, + "destinationIpGroups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of destination IP groups for this rule." + } + }, + "destinationPorts": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of destination ports." + } + }, + "sourceAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of source IP addresses for this rule." + } + }, + "sourceIpGroups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of source IpGroups for this rule." + } + } + } + }, + "metadata": { + "description": "Required. Collection of rules used by a network rule collection." + } + } + }, + "metadata": { + "description": "Required. Properties of the azure firewall network rule collection." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Firewall." + } + }, + "azureSkuTier": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Basic", + "Standard", + "Premium" + ], + "metadata": { + "description": "Optional. Tier of an Azure Firewall." + } + }, + "virtualNetworkResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. Shared services Virtual Network resource ID. The virtual network ID containing AzureFirewallSubnet. If a Public IP is not provided, then the Public IP that is created as part of this module will be applied with the subnet provided in this variable. Required if `virtualHubId` is empty." + } + }, + "publicIPResourceID": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Public IP resource ID to associate to the AzureFirewallSubnet. If empty, then the Public IP that is created as part of this module will be applied to the AzureFirewallSubnet." + } + }, + "additionalPublicIpConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. This is to add any additional Public IP configurations on top of the Public IP with subnet IP configuration." + } + }, + "publicIPAddressObject": { + "type": "object", + "defaultValue": { + "name": "[format('{0}-pip', parameters('name'))]" + }, + "metadata": { + "description": "Optional. Specifies the properties of the Public IP to create and be used by the Firewall, if no existing public IP was provided." + } + }, + "managementIPResourceID": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Management Public IP resource ID to associate to the AzureFirewallManagementSubnet. If empty, then the Management Public IP that is created as part of this module will be applied to the AzureFirewallManagementSubnet." + } + }, + "managementIPAddressObject": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Specifies the properties of the Management Public IP to create and be used by Azure Firewall. If it's not provided and managementIPResourceID is empty, a '-mip' suffix will be appended to the Firewall's name." + } + }, + "applicationRuleCollections": { + "$ref": "#/definitions/applicationRuleCollectionType", + "metadata": { + "description": "Optional. Collection of application rule collections used by Azure Firewall." + } + }, + "networkRuleCollections": { + "$ref": "#/definitions/networkRuleCollectionType", + "metadata": { + "description": "Optional. Collection of network rule collections used by Azure Firewall." + } + }, + "natRuleCollections": { + "$ref": "#/definitions/natRuleCollectionType", + "metadata": { + "description": "Optional. Collection of NAT rule collections used by Azure Firewall." + } + }, + "firewallPolicyId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the Firewall Policy that should be attached." + } + }, + "hubIPAddresses": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Conditional. IP addresses associated with AzureFirewall. Required if `virtualHubId` is supplied." + } + }, + "virtualHubId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Conditional. The virtualHub resource ID to which the firewall belongs. Required if `virtualNetworkId` is empty." + } + }, + "threatIntelMode": { + "type": "string", + "defaultValue": "Deny", + "allowedValues": [ + "Alert", + "Deny", + "Off" + ], + "metadata": { + "description": "Optional. The operation mode for Threat Intel." + } + }, + "zones": { + "type": "array", + "defaultValue": [ + 1, + 2, + 3 + ], + "metadata": { + "description": "Optional. Zone numbers e.g. 1,2,3." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the Azure Firewall resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "additionalPublicIpConfigurationsVar", + "count": "[length(parameters('additionalPublicIpConfigurations'))]", + "input": { + "name": "[parameters('additionalPublicIpConfigurations')[copyIndex('additionalPublicIpConfigurationsVar')].name]", + "properties": { + "publicIPAddress": "[if(contains(parameters('additionalPublicIpConfigurations')[copyIndex('additionalPublicIpConfigurationsVar')], 'publicIPAddressResourceId'), createObject('id', parameters('additionalPublicIpConfigurations')[copyIndex('additionalPublicIpConfigurationsVar')].publicIPAddressResourceId), null())]" + } + } + }, + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "azureSkuName": "[if(empty(parameters('virtualNetworkResourceId')), 'AZFW_Hub', 'AZFW_VNet')]", + "requiresManagementIp": "[if(equals(parameters('azureSkuTier'), 'Basic'), true(), false())]", + "isCreateDefaultManagementIP": "[and(empty(parameters('managementIPResourceID')), variables('requiresManagementIp'))]", + "builtInRoleNames": { + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-azurefirewall.{0}.{1}', replace('0.5.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "azureFirewall": { + "type": "Microsoft.Network/azureFirewalls", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "zones": "[if(equals(length(parameters('zones')), 0), null(), parameters('zones'))]", + "tags": "[parameters('tags')]", + "properties": "[if(equals(variables('azureSkuName'), 'AZFW_VNet'), createObject('threatIntelMode', parameters('threatIntelMode'), 'firewallPolicy', if(not(empty(parameters('firewallPolicyId'))), createObject('id', parameters('firewallPolicyId')), null()), 'ipConfigurations', concat(createArray(createObject('name', if(not(empty(parameters('publicIPResourceID'))), last(split(parameters('publicIPResourceID'), '/')), reference('publicIPAddress').outputs.name.value), 'properties', union(createObject('subnet', createObject('id', format('{0}/subnets/AzureFirewallSubnet', parameters('virtualNetworkResourceId')))), if(or(not(empty(parameters('publicIPResourceID'))), not(empty(parameters('publicIPAddressObject')))), createObject('publicIPAddress', createObject('id', if(not(empty(parameters('publicIPResourceID'))), parameters('publicIPResourceID'), reference('publicIPAddress').outputs.resourceId.value))), createObject())))), variables('additionalPublicIpConfigurationsVar')), 'managementIpConfiguration', if(variables('requiresManagementIp'), createObject('name', if(not(empty(parameters('managementIPResourceID'))), last(split(parameters('managementIPResourceID'), '/')), reference('managementIPAddress').outputs.name.value), 'properties', createObject('subnet', createObject('id', format('{0}/subnets/AzureFirewallManagementSubnet', parameters('virtualNetworkResourceId'))), 'publicIPAddress', createObject('id', if(not(empty(parameters('managementIPResourceID'))), parameters('managementIPResourceID'), reference('managementIPAddress').outputs.resourceId.value)))), null()), 'sku', createObject('name', variables('azureSkuName'), 'tier', parameters('azureSkuTier')), 'applicationRuleCollections', coalesce(parameters('applicationRuleCollections'), createArray()), 'natRuleCollections', coalesce(parameters('natRuleCollections'), createArray()), 'networkRuleCollections', coalesce(parameters('networkRuleCollections'), createArray())), createObject('firewallPolicy', if(not(empty(parameters('firewallPolicyId'))), createObject('id', parameters('firewallPolicyId')), null()), 'sku', createObject('name', variables('azureSkuName'), 'tier', parameters('azureSkuTier')), 'hubIPAddresses', if(not(empty(parameters('hubIPAddresses'))), parameters('hubIPAddresses'), null()), 'virtualHub', if(not(empty(parameters('virtualHubId'))), createObject('id', parameters('virtualHubId')), null())))]", + "dependsOn": [ + "managementIPAddress", + "publicIPAddress" + ] + }, + "azureFirewall_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/azureFirewalls/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "azureFirewall" + ] + }, + "azureFirewall_diagnosticSettings": { + "copy": { + "name": "azureFirewall_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/azureFirewalls/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "azureFirewall" + ] + }, + "azureFirewall_roleAssignments": { + "copy": { + "name": "azureFirewall_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/azureFirewalls/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/azureFirewalls', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "azureFirewall" + ] + }, + "publicIPAddress": { + "condition": "[and(empty(parameters('publicIPResourceID')), equals(variables('azureSkuName'), 'AZFW_VNet'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Firewall-PIP', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('publicIPAddressObject').name]" + }, + "publicIpPrefixResourceId": "[if(contains(parameters('publicIPAddressObject'), 'publicIPPrefixResourceId'), if(not(empty(parameters('publicIPAddressObject').publicIPPrefixResourceId)), createObject('value', parameters('publicIPAddressObject').publicIPPrefixResourceId), createObject('value', '')), createObject('value', ''))]", + "publicIPAllocationMethod": "[if(contains(parameters('publicIPAddressObject'), 'publicIPAllocationMethod'), if(not(empty(parameters('publicIPAddressObject').publicIPAllocationMethod)), createObject('value', parameters('publicIPAddressObject').publicIPAllocationMethod), createObject('value', 'Static')), createObject('value', 'Static'))]", + "skuName": "[if(contains(parameters('publicIPAddressObject'), 'skuName'), if(not(empty(parameters('publicIPAddressObject').skuName)), createObject('value', parameters('publicIPAddressObject').skuName), createObject('value', 'Standard')), createObject('value', 'Standard'))]", + "skuTier": "[if(contains(parameters('publicIPAddressObject'), 'skuTier'), if(not(empty(parameters('publicIPAddressObject').skuTier)), createObject('value', parameters('publicIPAddressObject').skuTier), createObject('value', 'Regional')), createObject('value', 'Regional'))]", + "roleAssignments": "[if(contains(parameters('publicIPAddressObject'), 'roleAssignments'), if(not(empty(parameters('publicIPAddressObject').roleAssignments)), createObject('value', parameters('publicIPAddressObject').roleAssignments), createObject('value', createArray())), createObject('value', createArray()))]", + "diagnosticSettings": { + "value": "[tryGet(parameters('publicIPAddressObject'), 'diagnosticSettings')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('publicIPAddressObject'), 'tags'), parameters('tags'))]" + }, + "zones": { + "value": "[parameters('zones')]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(parameters('publicIPAddressObject'), 'enableTelemetry'), parameters('enableTelemetry'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "14450344965065009842" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "dnsSettingsType": { + "type": "object", + "properties": { + "domainNameLabel": { + "type": "string", + "metadata": { + "description": "Required. The domain name label. The concatenation of the domain name label and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." + } + }, + "domainNameLabelScope": { + "type": "string", + "allowedValues": [ + "", + "NoReuse", + "ResourceGroupReuse", + "SubscriptionReuse", + "TenantReuse" + ], + "metadata": { + "description": "Required. The domain name label scope. If a domain name label and a domain name label scope are specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system with a hashed value includes in FQDN." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Fully Qualified Domain Name of the A DNS record associated with the public IP. This is the concatenation of the domainNameLabel and the regionalized DNS zone." + } + }, + "reverseFqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The reverse FQDN. A user-visible, fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN." + } + } + } + }, + "ddosSettingsType": { + "type": "object", + "properties": { + "ddosProtectionPlan": { + "type": "object", + "properties": { + "id": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the DDOS protection plan associated with the public IP address." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan associated with the public IP address." + } + }, + "protectionMode": { + "type": "string", + "allowedValues": [ + "Enabled" + ], + "metadata": { + "description": "Required. The DDoS protection policy customizations." + } + } + } + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Public IP Address." + } + }, + "publicIpPrefixResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the Public IP Prefix object. This is only needed if you want your Public IPs created in a PIP Prefix." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "defaultValue": "Static", + "allowedValues": [ + "Dynamic", + "Static" + ], + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "zones": { + "type": "array", + "items": { + "type": "int" + }, + "defaultValue": [ + 1, + 2, + 3 + ], + "allowedValues": [ + 1, + 2, + 3 + ], + "metadata": { + "description": "Optional. A list of availability zones denoting the IP allocated for the resource needs to come from." + } + }, + "publicIPAddressVersion": { + "type": "string", + "defaultValue": "IPv4", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "metadata": { + "description": "Optional. IP address version." + } + }, + "dnsSettings": { + "$ref": "#/definitions/dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Basic", + "Standard" + ], + "metadata": { + "description": "Optional. Name of a public IP address SKU." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "Regional", + "allowedValues": [ + "Global", + "Regional" + ], + "metadata": { + "description": "Optional. Tier of a public IP address SKU." + } + }, + "ddosSettings": { + "$ref": "#/definitions/ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "defaultValue": 4, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-publicipaddress.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('skuName')]", + "tier": "[parameters('skuTier')]" + }, + "zones": "[map(parameters('zones'), lambda('zone', string(lambdaVariables('zone'))))]", + "properties": { + "ddosSettings": "[parameters('ddosSettings')]", + "dnsSettings": "[parameters('dnsSettings')]", + "publicIPAddressVersion": "[parameters('publicIPAddressVersion')]", + "publicIPAllocationMethod": "[parameters('publicIPAllocationMethod')]", + "publicIPPrefix": "[if(not(empty(parameters('publicIpPrefixResourceId'))), createObject('id', parameters('publicIpPrefixResourceId')), null())]", + "idleTimeoutInMinutes": "[parameters('idleTimeoutInMinutes')]", + "ipTags": null + } + }, + "publicIpAddress_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + }, + "publicIpAddress_roleAssignments": { + "copy": { + "name": "publicIpAddress_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/publicIPAddresses', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + }, + "publicIpAddress_diagnosticSettings": { + "copy": { + "name": "publicIpAddress_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the public IP address was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the public IP address." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the public IP address." + }, + "value": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('name'))]" + }, + "ipAddress": { + "type": "string", + "metadata": { + "description": "The public IP address of the public IP address resource." + }, + "value": "[coalesce(tryGet(reference('publicIpAddress'), 'ipAddress'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('publicIpAddress', '2023-09-01', 'full').location]" + } + } + } + } + }, + "managementIPAddress": { + "condition": "[and(variables('isCreateDefaultManagementIP'), equals(variables('azureSkuName'), 'AZFW_VNet'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Firewall-MIP', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(contains(parameters('managementIPAddressObject'), 'name'), if(not(empty(parameters('managementIPAddressObject').name)), createObject('value', parameters('managementIPAddressObject').name), createObject('value', format('{0}-mip', parameters('name')))), createObject('value', format('{0}-mip', parameters('name'))))]", + "publicIpPrefixResourceId": "[if(contains(parameters('managementIPAddressObject'), 'managementIPPrefixResourceId'), if(not(empty(parameters('managementIPAddressObject').managementIPPrefixResourceId)), createObject('value', parameters('managementIPAddressObject').managementIPPrefixResourceId), createObject('value', '')), createObject('value', ''))]", + "publicIPAllocationMethod": "[if(contains(parameters('managementIPAddressObject'), 'managementIPAllocationMethod'), if(not(empty(parameters('managementIPAddressObject').managementIPAllocationMethod)), createObject('value', parameters('managementIPAddressObject').managementIPAllocationMethod), createObject('value', 'Static')), createObject('value', 'Static'))]", + "skuName": "[if(contains(parameters('managementIPAddressObject'), 'skuName'), if(not(empty(parameters('managementIPAddressObject').skuName)), createObject('value', parameters('managementIPAddressObject').skuName), createObject('value', 'Standard')), createObject('value', 'Standard'))]", + "skuTier": "[if(contains(parameters('managementIPAddressObject'), 'skuTier'), if(not(empty(parameters('managementIPAddressObject').skuTier)), createObject('value', parameters('managementIPAddressObject').skuTier), createObject('value', 'Regional')), createObject('value', 'Regional'))]", + "roleAssignments": "[if(contains(parameters('managementIPAddressObject'), 'roleAssignments'), if(not(empty(parameters('managementIPAddressObject').roleAssignments)), createObject('value', parameters('managementIPAddressObject').roleAssignments), createObject('value', createArray())), createObject('value', createArray()))]", + "diagnosticSettings": { + "value": "[tryGet(parameters('managementIPAddressObject'), 'diagnosticSettings')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('managementIPAddressObject'), 'tags'), parameters('tags'))]" + }, + "zones": { + "value": "[parameters('zones')]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(parameters('managementIPAddressObject'), 'enableTelemetry'), parameters('enableTelemetry'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "14450344965065009842" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "dnsSettingsType": { + "type": "object", + "properties": { + "domainNameLabel": { + "type": "string", + "metadata": { + "description": "Required. The domain name label. The concatenation of the domain name label and the regionalized DNS zone make up the fully qualified domain name associated with the public IP address. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." + } + }, + "domainNameLabelScope": { + "type": "string", + "allowedValues": [ + "", + "NoReuse", + "ResourceGroupReuse", + "SubscriptionReuse", + "TenantReuse" + ], + "metadata": { + "description": "Required. The domain name label scope. If a domain name label and a domain name label scope are specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system with a hashed value includes in FQDN." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Fully Qualified Domain Name of the A DNS record associated with the public IP. This is the concatenation of the domainNameLabel and the regionalized DNS zone." + } + }, + "reverseFqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The reverse FQDN. A user-visible, fully qualified domain name that resolves to this public IP address. If the reverseFqdn is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa domain to the reverse FQDN." + } + } + } + }, + "ddosSettingsType": { + "type": "object", + "properties": { + "ddosProtectionPlan": { + "type": "object", + "properties": { + "id": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the DDOS protection plan associated with the public IP address." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan associated with the public IP address." + } + }, + "protectionMode": { + "type": "string", + "allowedValues": [ + "Enabled" + ], + "metadata": { + "description": "Required. The DDoS protection policy customizations." + } + } + } + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Public IP Address." + } + }, + "publicIpPrefixResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the Public IP Prefix object. This is only needed if you want your Public IPs created in a PIP Prefix." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "defaultValue": "Static", + "allowedValues": [ + "Dynamic", + "Static" + ], + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "zones": { + "type": "array", + "items": { + "type": "int" + }, + "defaultValue": [ + 1, + 2, + 3 + ], + "allowedValues": [ + 1, + 2, + 3 + ], + "metadata": { + "description": "Optional. A list of availability zones denoting the IP allocated for the resource needs to come from." + } + }, + "publicIPAddressVersion": { + "type": "string", + "defaultValue": "IPv4", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "metadata": { + "description": "Optional. IP address version." + } + }, + "dnsSettings": { + "$ref": "#/definitions/dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Basic", + "Standard" + ], + "metadata": { + "description": "Optional. Name of a public IP address SKU." + } + }, + "skuTier": { + "type": "string", + "defaultValue": "Regional", + "allowedValues": [ + "Global", + "Regional" + ], + "metadata": { + "description": "Optional. Tier of a public IP address SKU." + } + }, + "ddosSettings": { + "$ref": "#/definitions/ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "defaultValue": 4, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-publicipaddress.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('skuName')]", + "tier": "[parameters('skuTier')]" + }, + "zones": "[map(parameters('zones'), lambda('zone', string(lambdaVariables('zone'))))]", + "properties": { + "ddosSettings": "[parameters('ddosSettings')]", + "dnsSettings": "[parameters('dnsSettings')]", + "publicIPAddressVersion": "[parameters('publicIPAddressVersion')]", + "publicIPAllocationMethod": "[parameters('publicIPAllocationMethod')]", + "publicIPPrefix": "[if(not(empty(parameters('publicIpPrefixResourceId'))), createObject('id', parameters('publicIpPrefixResourceId')), null())]", + "idleTimeoutInMinutes": "[parameters('idleTimeoutInMinutes')]", + "ipTags": null + } + }, + "publicIpAddress_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + }, + "publicIpAddress_roleAssignments": { + "copy": { + "name": "publicIpAddress_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/publicIPAddresses', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + }, + "publicIpAddress_diagnosticSettings": { + "copy": { + "name": "publicIpAddress_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/publicIPAddresses/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "publicIpAddress" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the public IP address was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the public IP address." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the public IP address." + }, + "value": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('name'))]" + }, + "ipAddress": { + "type": "string", + "metadata": { + "description": "The public IP address of the public IP address resource." + }, + "value": "[coalesce(tryGet(reference('publicIpAddress'), 'ipAddress'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('publicIpAddress', '2023-09-01', 'full').location]" + } + } + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Azure Firewall." + }, + "value": "[resourceId('Microsoft.Network/azureFirewalls', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the Azure Firewall." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the Azure firewall was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "privateIp": { + "type": "string", + "metadata": { + "description": "The private IP of the Azure firewall." + }, + "value": "[if(contains(reference('azureFirewall'), 'ipConfigurations'), reference('azureFirewall').ipConfigurations[0].properties.privateIPAddress, '')]" + }, + "ipConfAzureFirewallSubnet": { + "type": "object", + "metadata": { + "description": "The Public IP configuration object for the Azure Firewall Subnet." + }, + "value": "[if(contains(reference('azureFirewall'), 'ipConfigurations'), reference('azureFirewall').ipConfigurations[0], createObject())]" + }, + "applicationRuleCollections": { + "type": "array", + "metadata": { + "description": "List of Application Rule Collections used by Azure Firewall." + }, + "value": "[coalesce(parameters('applicationRuleCollections'), createArray())]" + }, + "networkRuleCollections": { + "type": "array", + "metadata": { + "description": "List of Network Rule Collections used by Azure Firewall." + }, + "value": "[coalesce(parameters('networkRuleCollections'), createArray())]" + }, + "natRuleCollections": { + "type": "array", + "metadata": { + "description": "List of NAT rule collections used by Azure Firewall." + }, + "value": "[coalesce(parameters('natRuleCollections'), createArray())]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('azureFirewall', '2023-04-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "hubVirtualNetwork" + ] + }, + "hubAzureFirewallSubnet": { + "copy": { + "name": "hubAzureFirewallSubnet", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]" + }, + "condition": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enableAzureFirewall]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-nafs', uniqueString(deployment().name, parameters('location')), items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "subnetName": { + "value": "AzureFirewallSubnet" + }, + "virtualNetworkName": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "13190798974838698070" + }, + "name": "Existing Virtual Network Subnets", + "description": "This module retrieves an existing Virtual Network Subnet.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the subnet." + } + }, + "virtualNetworkName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the virtual network." + } + } + }, + "resources": [], + "outputs": { + "subnetId": { + "type": "string", + "metadata": { + "description": "Subnet ID" + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('subnetName'))]" + }, + "addressPrefix": { + "type": "string", + "metadata": { + "description": "Subnet address prefix" + }, + "value": "[reference(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('subnetName')), '2024-01-01').addressPrefix]" + } + } + } + }, + "dependsOn": [ + "hubVirtualNetwork" + ] + }, + "hubAzureFirewallSubnetAssociation": { + "copy": { + "name": "hubAzureFirewallSubnetAssociation", + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enableAzureFirewall]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}-nafsa', uniqueString(deployment().name, parameters('location')), items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "AzureFirewallSubnet" + }, + "virtualNetworkName": { + "value": "[items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].key]" + }, + "addressPrefix": { + "value": "[reference(format('hubAzureFirewallSubnet[{0}]', copyIndex())).outputs.addressPrefix.value]" + }, + "routeTableResourceId": { + "value": "[reference(format('hubRouteTable[{0}]', copyIndex())).outputs.resourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "11735652948112662202" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "virtualNetworkName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent virtual network. Required if the template is used in a standalone deployment." + } + }, + "addressPrefix": { + "type": "string", + "metadata": { + "description": "Required. The address prefix for the subnet." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "routeTableResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpoints": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "delegations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The delegations to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private link service in the subnet." + } + }, + "addressPrefixes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of address prefixes for the subnet." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "ipAllocations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of IpAllocation which reference this subnet." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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')]" + } + }, + "resources": { + "virtualNetwork": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-11-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "properties": { + "addressPrefix": "[parameters('addressPrefix')]", + "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]", + "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]", + "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]", + "serviceEndpoints": "[parameters('serviceEndpoints')]", + "delegations": "[parameters('delegations')]", + "privateEndpointNetworkPolicies": "[if(not(empty(parameters('privateEndpointNetworkPolicies'))), parameters('privateEndpointNetworkPolicies'), null())]", + "privateLinkServiceNetworkPolicies": "[if(not(empty(parameters('privateLinkServiceNetworkPolicies'))), parameters('privateLinkServiceNetworkPolicies'), null())]", + "addressPrefixes": "[parameters('addressPrefixes')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "ipAllocations": "[parameters('ipAllocations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "subnet_roleAssignments": { + "copy": { + "name": "subnet_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "subnet" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the subnet was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "subnetName": { + "type": "string", + "metadata": { + "description": "The name of the subnet." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the subnet." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]" + }, + "subnetAddressPrefix": { + "type": "string", + "metadata": { + "description": "The address prefix for the subnet." + }, + "value": "[reference('subnet').addressPrefix]" + }, + "subnetAddressPrefixes": { + "type": "array", + "metadata": { + "description": "List of address prefixes for the subnet." + }, + "value": "[if(not(empty(parameters('addressPrefixes'))), reference('subnet').addressPrefixes, createArray())]" + } + } + } + }, + "dependsOn": [ + "hubAzureFirewall", + "hubAzureFirewallSubnet", + "[format('hubRouteTable[{0}]', copyIndex())]", + "hubVirtualNetwork" + ] + } + }, + "outputs": { + "hubVirtualNetworks": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Array of hub virtual network resources." + }, + "copy": { + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", + "input": { + "resourceGroupName": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.resourceGroupName.value]", + "location": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.location.value]", + "name": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.resourceId.value]" + } + } + }, + "hubBastions": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Array of hub bastion resources." + }, + "copy": { + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", + "input": { + "resourceGroupName": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.resourceGroupName.value]", + "location": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.location.value]", + "name": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.resourceId.value]" + } + } + }, + "hubAzureFirewalls": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Array of hub Azure Firewall resources." + }, + "copy": { + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", + "input": { + "resourceGroupName": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.resourceGroupName.value]", + "location": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.location.value]", + "name": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.resourceId.value]" + } + } + }, + "hubVirtualNetworkSubnets": { + "type": "array", + "metadata": { + "description": "The subnets of the hub virtual network." + }, + "copy": { + "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", + "input": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.subnetNames.value]" + } + } + } +} \ No newline at end of file diff --git a/avm/ptn/network/hub-networking/modules/getSubnet.bicep b/avm/ptn/network/hub-networking/modules/getSubnet.bicep new file mode 100644 index 00000000000..02a646a91c3 --- /dev/null +++ b/avm/ptn/network/hub-networking/modules/getSubnet.bicep @@ -0,0 +1,23 @@ +metadata name = 'Existing Virtual Network Subnets' +metadata description = 'This module retrieves an existing Virtual Network Subnet.' +metadata owner = 'Azure/module-maintainers' + +@description('Optional. The name of the subnet.') +param subnetName string = '' + +@description('Optional. The name of the virtual network.') +param virtualNetworkName string = '' + +resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' existing = { + name: virtualNetworkName + + resource subnet 'subnets@2024-01-01' existing = { + name: subnetName + } +} + +@description('Subnet ID') +output subnetId string = vnet::subnet.id + +@description('Subnet address prefix') +output addressPrefix string = vnet::subnet.properties.addressPrefix diff --git a/avm/ptn/network/hub-networking/modules/subnets.bicep b/avm/ptn/network/hub-networking/modules/subnets.bicep new file mode 100644 index 00000000000..49e9a4c21d1 --- /dev/null +++ b/avm/ptn/network/hub-networking/modules/subnets.bicep @@ -0,0 +1,185 @@ +metadata name = 'Virtual Network Subnets' +metadata description = 'This module deploys a Virtual Network Subnet.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The Name of the subnet resource.') +param name string + +@description('Required. The name of the parent virtual network. Required if the template is used in a standalone deployment.') +param virtualNetworkName string + +@description('Required. The address prefix for the subnet.') +param addressPrefix string + +@description('Optional. The resource ID of the network security group to assign to the subnet.') +param networkSecurityGroupResourceId string = '' + +@description('Optional. The resource ID of the route table to assign to the subnet.') +param routeTableResourceId string = '' + +@description('Optional. The service endpoints to enable on the subnet.') +param serviceEndpoints array = [] + +@description('Optional. The delegations to enable on the subnet.') +param delegations array = [] + +@description('Optional. The resource ID of the NAT Gateway to use for the subnet.') +param natGatewayResourceId string = '' + +@description('Optional. Enable or disable apply network policies on private endpoint in the subnet.') +@allowed([ + 'Disabled' + 'Enabled' + '' +]) +param privateEndpointNetworkPolicies string = '' + +@description('Optional. Enable or disable apply network policies on private link service in the subnet.') +@allowed([ + 'Disabled' + 'Enabled' + '' +]) +param privateLinkServiceNetworkPolicies string = '' + +@description('Optional. List of address prefixes for the subnet.') +param addressPrefixes array = [] + +@description('Optional. Application gateway IP configurations of virtual network resource.') +param applicationGatewayIPConfigurations array = [] + +@description('Optional. Array of IpAllocation which reference this subnet.') +param ipAllocations array = [] + +@description('Optional. An array of service endpoint policies.') +param serviceEndpointPolicies array = [] + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + 'Network Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '4d97b98b-1d4f-4787-a291-c67834d212e7' + ) + 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)) + }) +] + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' existing = { + name: virtualNetworkName +} + +resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { + name: name + parent: virtualNetwork + properties: { + addressPrefix: addressPrefix + networkSecurityGroup: !empty(networkSecurityGroupResourceId) + ? { + id: networkSecurityGroupResourceId + } + : null + routeTable: !empty(routeTableResourceId) + ? { + id: routeTableResourceId + } + : null + natGateway: !empty(natGatewayResourceId) + ? { + id: natGatewayResourceId + } + : null + serviceEndpoints: serviceEndpoints + delegations: delegations + privateEndpointNetworkPolicies: !empty(privateEndpointNetworkPolicies) ? any(privateEndpointNetworkPolicies) : null + privateLinkServiceNetworkPolicies: !empty(privateLinkServiceNetworkPolicies) + ? any(privateLinkServiceNetworkPolicies) + : null + addressPrefixes: addressPrefixes + applicationGatewayIPConfigurations: applicationGatewayIPConfigurations + ipAllocations: ipAllocations + serviceEndpointPolicies: serviceEndpointPolicies + } +} + +resource subnet_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (formattedRoleAssignments ?? []): { + name: roleAssignment.?name ?? guid(subnet.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: subnet + } +] + +@description('The resource group the subnet was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the subnet.') +output subnetName string = subnet.name + +@description('The resource ID of the subnet.') +output resourceId string = subnet.id + +@description('The address prefix for the subnet.') +output subnetAddressPrefix string = subnet.properties.addressPrefix + +@description('List of address prefixes for the subnet.') +output subnetAddressPrefixes array = !empty(addressPrefixes) ? subnet.properties.addressPrefixes : [] + +// =============== // +// Definitions // +// =============== // + +type roleAssignmentType = { + @description('Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated.') + name: string? + + @description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? diff --git a/avm/ptn/network/hub-networking/modules/vnets.bicep b/avm/ptn/network/hub-networking/modules/vnets.bicep new file mode 100644 index 00000000000..78d6048a3f0 --- /dev/null +++ b/avm/ptn/network/hub-networking/modules/vnets.bicep @@ -0,0 +1,22 @@ +metadata name = 'Virtual Networks' +metadata description = 'This module deploys a Virtual Network.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. The name of the parent virtual network. Required if the template is used in a standalone deployment.') +param name string + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-01-01' existing = { + name: name +} + +@description('The resource group the virtual network peering was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The name of the virtual network peering.') +output name string = virtualNetwork.name + +@description('The resource ID of the virtual network peering.') +output resourceId string = virtualNetwork.id + +@description('The address space of the virtual network.') +output addressPrefix string = virtualNetwork.properties.addressSpace.addressPrefixes[0] diff --git a/avm/ptn/network/hub-networking/tests/e2e/defaults/main.test.bicep b/avm/ptn/network/hub-networking/tests/e2e/defaults/main.test.bicep new file mode 100644 index 00000000000..86181df2214 --- /dev/null +++ b/avm/ptn/network/hub-networking/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,47 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.hub-networking-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nhnmin' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + } + } +] diff --git a/avm/ptn/network/hub-networking/tests/e2e/max/main.test.bicep b/avm/ptn/network/hub-networking/tests/e2e/max/main.test.bicep new file mode 100644 index 00000000000..5ff56e9d23b --- /dev/null +++ b/avm/ptn/network/hub-networking/tests/e2e/max/main.test.bicep @@ -0,0 +1,225 @@ +targetScope = 'subscription' + +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module with most of its features enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.hub-networking-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nhnmax' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: resourceLocation +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +var addressPrefix = '10.0.0.0/16' +var addressPrefix2 = '10.1.0.0/16' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + location: resourceLocation + hubVirtualNetworks: { + hub1: { + addressPrefixes: array(addressPrefix) + azureFirewallSettings: { + azureSkuTier: 'Standard' + enableTelemetry: true + location: resourceLocation + publicIPAddressObject: { + name: 'hub1-waf-pip' + } + threatIntelMode: 'Alert' + } + bastionHost: { + disableCopyPaste: true + enableFileCopy: false + enableIpConnect: false + enableShareableLink: false + scaleUnits: 2 + skuName: 'Standard' + } + dnsServers: ['10.0.1.4', '10.0.1.5'] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + enableAzureFirewall: true + enableBastion: true + enablePeering: false + enableTelemetry: true + flowTimeoutInMinutes: 30 + location: resourceLocation + lock: { + kind: 'CanNotDelete' + name: 'hub1Lock' + } + peeringSettings: [ + { + allowForwardedTraffic: true + allowGatewayTransit: false + allowVirtualNetworkAccess: true + useRemoteGateways: false + remoteVirtualNetworkName: 'hub2' + } + ] + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + name: 'GatewaySubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 0) + } + { + name: 'AzureFirewallSubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 1) + } + { + name: 'AzureBastionSubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 2) + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + hub2: { + addressPrefixes: array(addressPrefix2) + azureFirewallSettings: { + azureSkuTier: 'Standard' + enableTelemetry: true + location: resourceLocation + publicIPAddressObject: { + name: 'hub2-waf-pip' + } + threatIntelMode: 'Alert' + zones: [ + 1 + 2 + 3 + ] + } + bastionHost: { + disableCopyPaste: true + enableFileCopy: false + enableIpConnect: false + enableShareableLink: false + scaleUnits: 2 + skuName: 'Standard' + } + enableAzureFirewall: true + enableBastion: true + enablePeering: false + enableTelemetry: false + flowTimeoutInMinutes: 10 + location: resourceLocation + lock: { + kind: 'CanNotDelete' + name: 'hub2Lock' + } + peeringSettings: [ + { + allowForwardedTraffic: true + allowGatewayTransit: false + allowVirtualNetworkAccess: true + useRemoteGateways: false + remoteVirtualNetworkName: 'hub1' + } + ] + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + name: 'GatewaySubnet' + addressPrefix: cidrSubnet(addressPrefix2, 26, 0) + } + { + name: 'AzureFirewallSubnet' + addressPrefix: cidrSubnet(addressPrefix2, 26, 1) + } + { + name: 'AzureBastionSubnet' + addressPrefix: cidrSubnet(addressPrefix2, 26, 2) + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + } + } + } +] diff --git a/avm/ptn/network/hub-networking/tests/e2e/waf-aligned/main.test.bicep b/avm/ptn/network/hub-networking/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 00000000000..693e68fe093 --- /dev/null +++ b/avm/ptn/network/hub-networking/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,146 @@ +targetScope = 'subscription' + +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.hub-networking-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nhnwaf' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: resourceLocation +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +var addressPrefix = '10.0.0.0/16' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + location: resourceLocation + hubVirtualNetworks: { + hub1: { + addressPrefixes: array(addressPrefix) + azureFirewallSettings: { + azureSkuTier: 'Standard' + enableTelemetry: true + location: resourceLocation + publicIPAddressObject: { + name: 'hub1PublicIp' + } + threatIntelMode: 'Alert' + zones: [ + 1 + 2 + 3 + ] + } + bastionHost: { + disableCopyPaste: true + enableFileCopy: false + enableIpConnect: false + enableShareableLink: false + scaleUnits: 2 + skuName: 'Standard' + } + enableAzureFirewall: true + enableBastion: true + enablePeering: false + enableTelemetry: true + flowTimeoutInMinutes: 30 + dnsServers: ['10.0.1.6', '10.0.1.7'] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + location: resourceLocation + lock: { + kind: 'CanNotDelete' + name: 'hub1Lock' + } + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + name: 'GatewaySubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 0) + } + { + name: 'AzureFirewallSubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 1) + } + { + name: 'AzureBastionSubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 2) + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + } + } + } +] diff --git a/avm/ptn/network/hub-networking/version.json b/avm/ptn/network/hub-networking/version.json new file mode 100644 index 00000000000..8def869edeb --- /dev/null +++ b/avm/ptn/network/hub-networking/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 897149284afb4e39d7e2f928f650e8635d8e4366 Mon Sep 17 00:00:00 2001 From: hundredacres Date: Thu, 12 Sep 2024 23:33:32 -0700 Subject: [PATCH 16/46] feat: Add param for disabling access key (#3162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Add support for disabling access key authorization. Added support to control updateChannel. Fixes #3127 Closes #3127 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.cache.redis](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml/badge.svg?branch=feat%2Fissues%2F3127)](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [X] Azure Verified Module updates: - [X] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [X] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Máté Barabás Co-authored-by: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Co-authored-by: JFolberth --- avm/res/cache/redis/README.md | 9 +++++++++ avm/res/cache/redis/linked-servers/main.json | 2 +- avm/res/cache/redis/main.bicep | 4 ++++ avm/res/cache/redis/main.json | 12 ++++++++++-- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/avm/res/cache/redis/README.md b/avm/res/cache/redis/README.md index 53940a6aa55..fb766dff67d 100644 --- a/avm/res/cache/redis/README.md +++ b/avm/res/cache/redis/README.md @@ -734,6 +734,7 @@ module redis 'br/public:avm/res/cache/redis:' = { | :-- | :-- | :-- | | [`capacity`](#parameter-capacity) | int | The size of the Redis cache to deploy. Valid values: for C (Basic/Standard) family (0, 1, 2, 3, 4, 5, 6), for P (Premium) family (1, 2, 3, 4). | | [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`disableAccessKeyAuthentication`](#parameter-disableaccesskeyauthentication) | bool | Disable authentication via access keys. | | [`enableNonSslPort`](#parameter-enablenonsslport) | bool | Specifies whether the non-ssl Redis server port (6379) is enabled. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`geoReplicationObject`](#parameter-georeplicationobject) | object | The geo-replication settings of the service. Requires a Premium SKU. Geo-replication is not supported on a cache with multiple replicas per primary. Secondary cache VM Size must be same or higher as compared to the primary cache VM Size. Geo-replication between a vnet and non vnet cache (and vice-a-versa) not supported. | @@ -930,6 +931,14 @@ Resource ID of the diagnostic log analytics workspace. For security reasons, it - Required: No - Type: string +### Parameter: `disableAccessKeyAuthentication` + +Disable authentication via access keys. + +- Required: No +- Type: bool +- Default: `False` + ### Parameter: `enableNonSslPort` Specifies whether the non-ssl Redis server port (6379) is enabled. diff --git a/avm/res/cache/redis/linked-servers/main.json b/avm/res/cache/redis/linked-servers/main.json index 67532e2795a..1352156fefc 100644 --- a/avm/res/cache/redis/linked-servers/main.json +++ b/avm/res/cache/redis/linked-servers/main.json @@ -98,4 +98,4 @@ "value": "[resourceGroup().name]" } } -} \ No newline at end of file +} diff --git a/avm/res/cache/redis/main.bicep b/avm/res/cache/redis/main.bicep index 09212df80b0..9e866bb89ee 100644 --- a/avm/res/cache/redis/main.bicep +++ b/avm/res/cache/redis/main.bicep @@ -20,6 +20,9 @@ param tags object? @description('Optional. The managed identity definition for this resource.') param managedIdentities managedIdentitiesType +@description('Optional. Disable authentication via access keys.') +param disableAccessKeyAuthentication bool = false + @description('Optional. Specifies whether the non-ssl Redis server port (6379) is enabled.') param enableNonSslPort bool = false @@ -181,6 +184,7 @@ resource redis 'Microsoft.Cache/redis@2024-03-01' = { tags: tags identity: identity properties: { + disableAccessKeyAuthentication: disableAccessKeyAuthentication enableNonSslPort: enableNonSslPort minimumTlsVersion: minimumTlsVersion publicNetworkAccess: !empty(publicNetworkAccess) diff --git a/avm/res/cache/redis/main.json b/avm/res/cache/redis/main.json index f9b3301dcb2..222f86491e2 100644 --- a/avm/res/cache/redis/main.json +++ b/avm/res/cache/redis/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "2147401251365362685" + "templateHash": "8427702382536251551" }, "name": "Redis Cache", "description": "This module deploys a Redis Cache.", @@ -514,6 +514,13 @@ "description": "Optional. The managed identity definition for this resource." } }, + "disableAccessKeyAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Disable authentication via access keys." + } + }, "enableNonSslPort": { "type": "bool", "defaultValue": false, @@ -733,6 +740,7 @@ "tags": "[parameters('tags')]", "identity": "[variables('identity')]", "properties": { + "disableAccessKeyAuthentication": "[parameters('disableAccessKeyAuthentication')]", "enableNonSslPort": "[parameters('enableNonSslPort')]", "minimumTlsVersion": "[parameters('minimumTlsVersion')]", "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(not(empty(parameters('privateEndpoints'))), 'Disabled', null()))]", @@ -1801,4 +1809,4 @@ } } } -} \ No newline at end of file +} From 34c5bf10c4cb75fbd51db3fd4a030a7cb6d6bd7b Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Fri, 13 Sep 2024 09:23:52 +0200 Subject: [PATCH 17/46] fix: Updated hub-network output (#3259) ## Description Added missing resource group output to hub-network pattern module ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.network.hub-networking](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.ptn.network.hub-networking.yml/badge.svg?branch=users%2Falsehr%2FoutputTest&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.ptn.network.hub-networking.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --- avm/ptn/network/hub-networking/README.md | 1 + avm/ptn/network/hub-networking/main.bicep | 4 ++++ avm/ptn/network/hub-networking/main.json | 9 ++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/avm/ptn/network/hub-networking/README.md b/avm/ptn/network/hub-networking/README.md index 46c009b8c0e..9dd17dbb8d6 100644 --- a/avm/ptn/network/hub-networking/README.md +++ b/avm/ptn/network/hub-networking/README.md @@ -1663,6 +1663,7 @@ Location for all Resources. | `hubBastions` | array | Array of hub bastion resources. | | `hubVirtualNetworks` | array | Array of hub virtual network resources. | | `hubVirtualNetworkSubnets` | array | The subnets of the hub virtual network. | +| `resourceGroupName` | string | The resource group the resources were deployed into. | ## Cross-referenced modules diff --git a/avm/ptn/network/hub-networking/main.bicep b/avm/ptn/network/hub-networking/main.bicep index 8406fc1cc18..981cdd4effd 100644 --- a/avm/ptn/network/hub-networking/main.bicep +++ b/avm/ptn/network/hub-networking/main.bicep @@ -23,6 +23,7 @@ var hubVirtualNetworkPeerings = [for (hub, index) in items(hubVirtualNetworks ?? // Resources // // ============== // +#disable-next-line no-deployments-resources resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { name: '46d3xbcp.ptn.network-hubnetworking.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' properties: { @@ -267,6 +268,9 @@ output hubVirtualNetworkSubnets array = [ for (hub, index) in items(hubVirtualNetworks ?? {}): hubVirtualNetwork[index].outputs.subnetNames ] +@description('The resource group the resources were deployed into.') +output resourceGroupName string = resourceGroup().name + // ================ // // Definitions // // ================ // diff --git a/avm/ptn/network/hub-networking/main.json b/avm/ptn/network/hub-networking/main.json index b2149b348cc..8fb5cb887bc 100644 --- a/avm/ptn/network/hub-networking/main.json +++ b/avm/ptn/network/hub-networking/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "4469274513824919483" + "templateHash": "9188161100861636713" }, "name": "Hub Networking", "description": "This module is designed to simplify the creation of multi-region hub networks in Azure. It will create a number of virtual networks and subnets, and optionally peer them together in a mesh topology with routing.", @@ -6894,6 +6894,13 @@ "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", "input": "[reference(format('hubVirtualNetwork[{0}]', copyIndex())).outputs.subnetNames.value]" } + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the resources were deployed into." + }, + "value": "[resourceGroup().name]" } } } \ No newline at end of file From 9dacc4c55724e2ab4551a99eab0b4e772cf8c2c9 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Fri, 13 Sep 2024 16:40:08 +0200 Subject: [PATCH 18/46] feat: Added logic to add available role names to readme (#706) ## Description Added logic to add available role names to readme At this time, this works only for modules that use the Language Server Version 2 @eriqua, @jtracey93 & @ChrisSidebotham this PR is fundamentally ready for review - but I want to get your approval first before generating all readme's E.g. ![image](https://github.com/Azure/bicep-registry-modules/assets/5365358/90e8a99f-0b24-4521-8fb8-1d17f8b8b0ed) | Pipeline | | - | [![avm.res.aad.domain-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.aad.domain-service.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.aad.domain-service.yml) [![avm.res.alerts-management.action-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.alerts-management.action-rule.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.alerts-management.action-rule.yml) [![avm.res.analysis-services.server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.analysis-services.server.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.analysis-services.server.yml) [![avm.res.api-management.service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml) [![avm.res.app-configuration.configuration-store](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app-configuration.configuration-store.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app-configuration.configuration-store.yml) [![avm.res.app.container-app](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.container-app.yml) [![avm.res.app.job](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.job.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.job.yml) [![avm.res.app.managed-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.managed-environment.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.app.managed-environment.yml) [![avm.res.automation.automation-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.automation.automation-account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.automation.automation-account.yml) [![avm.res.batch.batch-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.batch.batch-account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.batch.batch-account.yml) [![avm.res.cache.redis](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cache.redis.yml) [![avm.res.cdn.profile](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cdn.profile.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cdn.profile.yml) [![avm.res.cognitive-services.account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml) [![avm.res.communication.communication-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.communication-service.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.communication-service.yml) [![avm.res.communication.email-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.email-service.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.communication.email-service.yml) [![avm.res.compute.availability-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.availability-set.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.availability-set.yml) [![avm.res.compute.disk-encryption-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk-encryption-set.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk-encryption-set.yml) [![avm.res.compute.disk](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.disk.yml) [![avm.res.compute.gallery](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.gallery.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.gallery.yml) [![avm.res.compute.image](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.image.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.image.yml) [![avm.res.compute.proximity-placement-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.proximity-placement-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.proximity-placement-group.yml) [![avm.res.compute.ssh-public-key](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.ssh-public-key.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.ssh-public-key.yml) [![avm.res.compute.virtual-machine-scale-set](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine-scale-set.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine-scale-set.yml) [![avm.res.compute.virtual-machine](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.compute.virtual-machine.yml) [![avm.res.consumption.budget](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.consumption.budget.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.consumption.budget.yml) [![avm.res.container-instance.container-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-instance.container-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-instance.container-group.yml) [![avm.res.container-registry.registry](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-registry.registry.yml) [![avm.res.container-service.managed-cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.container-service.managed-cluster.yml) (unrelated, WAF) [![avm.res.data-factory.factory](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-factory.factory.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-factory.factory.yml) [![avm.res.data-protection.backup-vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-protection.backup-vault.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.data-protection.backup-vault.yml) [![avm.res.databricks.access-connector](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.access-connector.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.access-connector.yml) [![avm.res.databricks.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.databricks.workspace.yml) [![avm.res.db-for-my-sql.flexible-server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-my-sql.flexible-server.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-my-sql.flexible-server.yml) [![avm.res.db-for-postgre-sql.flexible-server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml) [![avm.res.desktop-virtualization.application-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.application-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.application-group.yml) [![avm.res.desktop-virtualization.host-pool](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.host-pool.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.host-pool.yml) [![avm.res.desktop-virtualization.scaling-plan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.scaling-plan.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.scaling-plan.yml) [![avm.res.desktop-virtualization.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.workspace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.desktop-virtualization.workspace.yml) [![avm.res.dev-test-lab.lab](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.dev-test-lab.lab.yml) [![avm.res.digital-twins.digital-twins-instance](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.digital-twins.digital-twins-instance.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.digital-twins.digital-twins-instance.yml) [![avm.res.document-db.database-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.document-db.database-account.yml) [![avm.res.event-grid.domain](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.domain.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.domain.yml) [![avm.res.event-grid.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.namespace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.namespace.yml) [![avm.res.event-grid.system-topic](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.system-topic.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.system-topic.yml) [![avm.res.event-grid.topic](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.topic.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-grid.topic.yml) [![avm.res.event-hub.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-hub.namespace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.event-hub.namespace.yml) [![avm.res.health-bot.health-bot](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.health-bot.health-bot.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.health-bot.health-bot.yml) [![avm.res.healthcare-apis.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.healthcare-apis.workspace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.healthcare-apis.workspace.yml) [![avm.res.hybrid-compute.machine](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.hybrid-compute.machine.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.hybrid-compute.machine.yml) [![avm.res.insights.action-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.action-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.action-group.yml) [![avm.res.insights.activity-log-alert](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.activity-log-alert.yml) [![avm.res.insights.component](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.component.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.component.yml) [![avm.res.insights.data-collection-endpoint](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-endpoint.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-endpoint.yml) [![avm.res.insights.data-collection-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.data-collection-rule.yml) [![avm.res.insights.diagnostic-setting](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.diagnostic-setting.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.diagnostic-setting.yml) [![avm.res.insights.metric-alert](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.metric-alert.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.metric-alert.yml) [![avm.res.insights.private-link-scope](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml) [![avm.res.insights.scheduled-query-rule](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.scheduled-query-rule.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.scheduled-query-rule.yml) [![avm.res.insights.webtest](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.webtest.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.insights.webtest.yml) [![avm.res.key-vault.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) [![avm.res.kubernetes-configuration.extension](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.extension.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.extension.yml) [![avm.res.kubernetes-configuration.flux-configuration](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.flux-configuration.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kubernetes-configuration.flux-configuration.yml) [![avm.res.kusto.cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kusto.cluster.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.kusto.cluster.yml) [![avm.res.load-test-service.load-test](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.load-test-service.load-test.yml) [![avm.res.logic.workflow](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.logic.workflow.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.logic.workflow.yml) [![avm.res.machine-learning-services.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.machine-learning-services.workspace.yml) [![avm.res.maintenance.maintenance-configuration](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.maintenance.maintenance-configuration.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.maintenance.maintenance-configuration.yml) [![avm.res.managed-identity.user-assigned-identity](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-identity.user-assigned-identity.yml) [![avm.res.managed-services.registration-definition](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-services.registration-definition.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.managed-services.registration-definition.yml) [![avm.res.management.management-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.management.management-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.management.management-group.yml) [![avm.res.net-app.net-app-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.net-app.net-app-account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.net-app.net-app-account.yml) [![avm.res.network.application-gateway-web-application-firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway-web-application-firewall-policy.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway-web-application-firewall-policy.yml) [![avm.res.network.application-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-gateway.yml) [![avm.res.network.application-security-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-security-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.application-security-group.yml) [![avm.res.network.azure-firewall](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.azure-firewall.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.azure-firewall.yml) [![avm.res.network.bastion-host](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.bastion-host.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.bastion-host.yml) [![avm.res.network.connection](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.connection.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.connection.yml) [![avm.res.network.ddos-protection-plan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ddos-protection-plan.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ddos-protection-plan.yml) [![avm.res.network.dns-forwarding-ruleset](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-forwarding-ruleset.yml) [![avm.res.network.dns-resolver](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-resolver.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-resolver.yml) [![avm.res.network.dns-zone](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-zone.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.dns-zone.yml) [![avm.res.network.express-route-circuit](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-circuit.yml) [![avm.res.network.express-route-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.express-route-gateway.yml) [![avm.res.network.firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.firewall-policy.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.firewall-policy.yml) [![avm.res.network.front-door-web-application-firewall-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door-web-application-firewall-policy.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door-web-application-firewall-policy.yml) [![avm.res.network.front-door](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml) [![avm.res.network.ip-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ip-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.ip-group.yml) [![avm.res.network.load-balancer](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.load-balancer.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.load-balancer.yml) [![avm.res.network.local-network-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.local-network-gateway.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.local-network-gateway.yml) [![avm.res.network.nat-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml) [![avm.res.network.network-interface](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-interface.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-interface.yml) [![avm.res.network.network-manager](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-manager.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-manager.yml) [![avm.res.network.network-security-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-security-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-security-group.yml) [![avm.res.network.network-watcher](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-watcher.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.network-watcher.yml) [![avm.res.network.private-dns-zone](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-dns-zone.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-dns-zone.yml) [![avm.res.network.private-endpoint](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-endpoint.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-endpoint.yml) [![avm.res.network.private-link-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-link-service.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.private-link-service.yml) [![avm.res.network.public-ip-address](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-address.yml) [![avm.res.network.public-ip-prefix](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-prefix.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.public-ip-prefix.yml) [![avm.res.network.route-table](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.route-table.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.route-table.yml) [![avm.res.network.service-endpoint-policy](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml) [![avm.res.network.trafficmanagerprofile](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml) [![avm.res.network.virtual-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-hub.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-hub.yml) [![avm.res.network.virtual-network-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml) [![avm.res.network.virtual-network](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network.yml) [![avm.res.network.virtual-wan](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-wan.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.virtual-wan.yml) [![avm.res.network.vpn-gateway](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-gateway.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-gateway.yml) [![avm.res.network.vpn-site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-site.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.network.vpn-site.yml) [![avm.res.operational-insights.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operational-insights.workspace.yml) [![avm.res.operations-management.solution](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml) [![avm.res.portal.dashboard](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.portal.dashboard.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.portal.dashboard.yml) [![avm.res.power-bi-dedicated.capacity](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.power-bi-dedicated.capacity.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.power-bi-dedicated.capacity.yml) [![avm.res.purview.account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.purview.account.yml) [![avm.res.recovery-services.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.recovery-services.vault.yml) [![avm.res.relay.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.relay.namespace.yml) [![avm.res.resource-graph.query](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resource-graph.query.yml) [![avm.res.resources.deployment-script](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.deployment-script.yml) [![avm.res.resources.resource-group](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.resources.resource-group.yml) [![avm.res.search.search-service](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.search.search-service.yml) [![avm.res.service-bus.namespace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-bus.namespace.yml) [![avm.res.service-fabric.cluster](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.service-fabric.cluster.yml) [![avm.res.signal-r-service.signal-r](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.signal-r.yml) [![avm.res.signal-r-service.web-pub-sub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.signal-r-service.web-pub-sub.yml) [![avm.res.sql.instance-pool](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.instance-pool.yml) [![avm.res.sql.managed-instance](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.managed-instance.yml) (unrelated, WAF) [![avm.res.sql.server](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.sql.server.yml) [![avm.res.storage.storage-account](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) [![avm.res.synapse.private-link-hub](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.private-link-hub.yml) [![avm.res.synapse.workspace](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.synapse.workspace.yml) [![avm.res.virtual-machine-images.image-template](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.virtual-machine-images.image-template.yml) [![avm.res.web.connection](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.connection.yml) [![avm.res.web.hosting-environment](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.hosting-environment.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.hosting-environment.yml) (unrelated, WAF) [![avm.res.web.serverfarm](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml) [![avm.res.web.site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.site.yml) [![avm.res.web.static-site](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml/badge.svg?branch=users%2Falsehr%2Freadme_Roles&event=workflow_dispatch)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.web.static-site.yml) --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> Co-authored-by: ChrisSidebotham-MSFT <48600046+ChrisSidebotham@users.noreply.github.com> --- avm/res/aad/domain-service/README.md | 6 + .../alerts-management/action-rule/README.md | 6 + avm/res/analysis-services/server/README.md | 6 + avm/res/api-management/service/README.md | 10 ++ .../configuration-store/README.md | 21 +++ avm/res/app/container-app/README.md | 7 + avm/res/app/job/README.md | 7 + avm/res/app/managed-environment/README.md | 6 + .../automation/automation-account/README.md | 21 +++ avm/res/batch/batch-account/README.md | 17 +++ avm/res/cache/redis/README.md | 18 +++ avm/res/cdn/profile/README.md | 10 ++ avm/res/cognitive-services/account/README.md | 41 ++++++ .../communication-service/README.md | 6 + avm/res/communication/email-service/README.md | 6 + .../email-service/domain/README.md | 6 + avm/res/compute/availability-set/README.md | 10 ++ avm/res/compute/disk-encryption-set/README.md | 11 ++ avm/res/compute/disk/README.md | 11 ++ avm/res/compute/gallery/README.md | 7 + avm/res/compute/gallery/application/README.md | 7 + avm/res/compute/gallery/image/README.md | 7 + avm/res/compute/image/README.md | 6 + .../proximity-placement-group/README.md | 6 + avm/res/compute/ssh-public-key/README.md | 6 + .../virtual-machine-scale-set/README.md | 19 +++ avm/res/compute/virtual-machine/README.md | 19 +++ avm/res/container-registry/registry/README.md | 23 +++ .../managed-cluster/README.md | 20 +++ avm/res/data-factory/factory/README.md | 18 +++ .../data-protection/backup-vault/README.md | 9 ++ avm/res/databricks/access-connector/README.md | 6 + avm/res/databricks/workspace/README.md | 12 ++ .../db-for-my-sql/flexible-server/README.md | 7 + .../flexible-server/README.md | 6 + .../application-group/README.md | 23 +++ .../host-pool/README.md | 34 +++++ .../scaling-plan/README.md | 20 +++ .../workspace/README.md | 31 ++++ avm/res/dev-test-lab/lab/README.md | 9 ++ .../digital-twins-instance/README.md | 19 +++ .../document-db/database-account/README.md | 22 +++ avm/res/event-grid/domain/README.md | 21 +++ avm/res/event-grid/namespace/README.md | 25 ++++ .../namespace/topic-space/README.md | 14 ++ avm/res/event-grid/namespace/topic/README.md | 14 ++ .../topic/event-subscription/README.md | 14 ++ avm/res/event-grid/system-topic/README.md | 10 ++ avm/res/event-grid/topic/README.md | 21 +++ avm/res/event-hub/namespace/README.md | 20 +++ .../event-hub/namespace/eventhub/README.md | 9 ++ avm/res/health-bot/health-bot/README.md | 6 + avm/res/healthcare-apis/workspace/README.md | 15 ++ .../workspace/fhirservice/README.md | 15 ++ avm/res/hybrid-compute/machine/README.md | 10 ++ avm/res/insights/action-group/README.md | 6 + avm/res/insights/activity-log-alert/README.md | 6 + avm/res/insights/component/README.md | 10 ++ .../data-collection-endpoint/README.md | 6 + .../insights/data-collection-rule/README.md | 6 + avm/res/insights/metric-alert/README.md | 6 + avm/res/insights/private-link-scope/README.md | 26 ++++ .../insights/scheduled-query-rule/README.md | 6 + avm/res/insights/webtest/README.md | 6 + avm/res/key-vault/vault/README.md | 50 +++++++ avm/res/key-vault/vault/key/README.md | 12 ++ avm/res/key-vault/vault/secret/README.md | 11 ++ avm/res/kusto/cluster/README.md | 15 ++ avm/res/load-test-service/load-test/README.md | 6 + avm/res/logic/workflow/README.md | 8 ++ .../workspace/README.md | 21 +++ .../maintenance-configuration/README.md | 7 + .../user-assigned-identity/README.md | 8 ++ avm/res/net-app/net-app-account/README.md | 6 + .../net-app-account/capacity-pool/README.md | 6 + .../capacity-pool/volume/README.md | 6 + avm/res/network/application-gateway/README.md | 17 +++ .../application-security-group/README.md | 6 + avm/res/network/azure-firewall/README.md | 6 + avm/res/network/bastion-host/README.md | 6 + .../network/ddos-protection-plan/README.md | 6 + .../network/dns-forwarding-ruleset/README.md | 9 ++ avm/res/network/dns-resolver/README.md | 11 ++ avm/res/network/dns-zone/README.md | 132 ++++++++++++++++++ avm/res/network/dns-zone/a/README.md | 12 ++ avm/res/network/dns-zone/aaaa/README.md | 12 ++ avm/res/network/dns-zone/caa/README.md | 12 ++ avm/res/network/dns-zone/cname/README.md | 12 ++ avm/res/network/dns-zone/mx/README.md | 12 ++ avm/res/network/dns-zone/ns/README.md | 12 ++ avm/res/network/dns-zone/ptr/README.md | 12 ++ avm/res/network/dns-zone/soa/README.md | 12 ++ avm/res/network/dns-zone/srv/README.md | 12 ++ avm/res/network/dns-zone/txt/README.md | 12 ++ .../network/express-route-circuit/README.md | 7 + .../network/express-route-gateway/README.md | 7 + .../README.md | 6 + avm/res/network/front-door/README.md | 7 + avm/res/network/ip-group/README.md | 7 + avm/res/network/load-balancer/README.md | 7 + .../network/local-network-gateway/README.md | 7 + avm/res/network/nat-gateway/README.md | 7 + avm/res/network/network-interface/README.md | 9 ++ avm/res/network/network-manager/README.md | 10 ++ .../network/network-security-group/README.md | 7 + avm/res/network/network-watcher/README.md | 7 + avm/res/network/private-dns-zone/README.md | 71 ++++++++++ avm/res/network/private-dns-zone/a/README.md | 8 ++ .../network/private-dns-zone/aaaa/README.md | 8 ++ .../network/private-dns-zone/cname/README.md | 8 ++ avm/res/network/private-dns-zone/mx/README.md | 8 ++ .../network/private-dns-zone/ptr/README.md | 8 ++ .../network/private-dns-zone/soa/README.md | 8 ++ .../network/private-dns-zone/srv/README.md | 8 ++ .../network/private-dns-zone/txt/README.md | 8 ++ avm/res/network/private-endpoint/README.md | 11 ++ .../network/private-link-service/README.md | 8 ++ avm/res/network/public-ip-address/README.md | 11 ++ avm/res/network/public-ip-prefix/README.md | 7 + avm/res/network/route-table/README.md | 7 + .../network/service-endpoint-policy/README.md | 7 + .../network/trafficmanagerprofile/README.md | 8 ++ .../network/virtual-network-gateway/README.md | 7 + avm/res/network/virtual-network/README.md | 14 ++ .../network/virtual-network/subnet/README.md | 7 + avm/res/network/virtual-wan/README.md | 7 + avm/res/network/vpn-site/README.md | 7 + .../operational-insights/workspace/README.md | 12 ++ .../workspace/table/README.md | 10 ++ avm/res/portal/dashboard/README.md | 6 + avm/res/power-bi-dedicated/capacity/README.md | 8 ++ avm/res/purview/account/README.md | 18 +++ avm/res/recovery-services/vault/README.md | 23 +++ avm/res/relay/namespace/README.md | 20 +++ .../namespace/hybrid-connection/README.md | 9 ++ avm/res/relay/namespace/wcf-relay/README.md | 9 ++ avm/res/resource-graph/query/README.md | 6 + avm/res/resources/deployment-script/README.md | 6 + avm/res/search/search-service/README.md | 20 +++ avm/res/service-bus/namespace/README.md | 38 +++++ avm/res/service-bus/namespace/queue/README.md | 9 ++ avm/res/service-bus/namespace/topic/README.md | 9 ++ avm/res/service-fabric/cluster/README.md | 6 + avm/res/signal-r-service/signal-r/README.md | 25 ++++ .../signal-r-service/web-pub-sub/README.md | 25 ++++ avm/res/sql/managed-instance/README.md | 13 ++ avm/res/sql/server/README.md | 24 ++++ avm/res/storage/storage-account/README.md | 36 +++++ .../blob-service/container/README.md | 14 ++ .../queue-service/queue/README.md | 14 ++ .../table-service/table/README.md | 12 ++ avm/res/synapse/private-link-hub/README.md | 17 +++ avm/res/synapse/workspace/README.md | 18 +++ .../image-template/README.md | 6 + avm/res/web/connection/README.md | 6 + avm/res/web/hosting-environment/README.md | 6 + avm/res/web/serverfarm/README.md | 8 ++ avm/res/web/site/README.md | 9 ++ avm/res/web/site/slot/README.md | 20 +++ avm/res/web/static-site/README.md | 19 +++ .../sharedScripts/Get-NestedResourceList.ps1 | 11 +- .../sharedScripts/Set-ModuleReadMe.ps1 | 31 +++- 162 files changed, 2145 insertions(+), 5 deletions(-) diff --git a/avm/res/aad/domain-service/README.md b/avm/res/aad/domain-service/README.md index 9aa32399c5d..1a8c5627a80 100644 --- a/avm/res/aad/domain-service/README.md +++ b/avm/res/aad/domain-service/README.md @@ -645,6 +645,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/alerts-management/action-rule/README.md b/avm/res/alerts-management/action-rule/README.md index 72c9e58af6f..658f8557698 100644 --- a/avm/res/alerts-management/action-rule/README.md +++ b/avm/res/alerts-management/action-rule/README.md @@ -603,6 +603,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/analysis-services/server/README.md b/avm/res/analysis-services/server/README.md index 18ad72dda3b..32cb2ff12e7 100644 --- a/avm/res/analysis-services/server/README.md +++ b/avm/res/analysis-services/server/README.md @@ -669,6 +669,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/api-management/service/README.md b/avm/res/api-management/service/README.md index fa41e811407..c219b2de19f 100644 --- a/avm/res/api-management/service/README.md +++ b/avm/res/api-management/service/README.md @@ -1742,6 +1742,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'API Management Developer Portal Content Editor'` + - `'API Management Service Contributor'` + - `'API Management Service Operator Role'` + - `'API Management Service Reader Role'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/app-configuration/configuration-store/README.md b/avm/res/app-configuration/configuration-store/README.md index 06d8a2d62da..62362ac7a1d 100644 --- a/avm/res/app-configuration/configuration-store/README.md +++ b/avm/res/app-configuration/configuration-store/README.md @@ -1303,6 +1303,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1435,6 +1446,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'App Compliance Automation Administrator'` + - `'App Compliance Automation Reader'` + - `'App Configuration Data Owner'` + - `'App Configuration Data Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/app/container-app/README.md b/avm/res/app/container-app/README.md index 9ac975e1222..a032f3411f5 100644 --- a/avm/res/app/container-app/README.md +++ b/avm/res/app/container-app/README.md @@ -1409,6 +1409,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'ContainerApp Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/app/job/README.md b/avm/res/app/job/README.md index a0d091b7808..792068064b4 100644 --- a/avm/res/app/job/README.md +++ b/avm/res/app/job/README.md @@ -1741,6 +1741,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'ContainerApp Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/app/managed-environment/README.md b/avm/res/app/managed-environment/README.md index 66e8fe23641..75b860ddd75 100644 --- a/avm/res/app/managed-environment/README.md +++ b/avm/res/app/managed-environment/README.md @@ -778,6 +778,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/automation/automation-account/README.md b/avm/res/automation/automation-account/README.md index 8aa19e6b652..d6b50e257bc 100644 --- a/avm/res/automation/automation-account/README.md +++ b/avm/res/automation/automation-account/README.md @@ -1853,6 +1853,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1973,6 +1984,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Automation Contributor'` + - `'Automation Job Operator'` + - `'Automation Operator'` + - `'Automation Runbook Operator'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/batch/batch-account/README.md b/avm/res/batch/batch-account/README.md index 7b725694d8a..788df949576 100644 --- a/avm/res/batch/batch-account/README.md +++ b/avm/res/batch/batch-account/README.md @@ -1407,6 +1407,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1527,6 +1538,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/cache/redis/README.md b/avm/res/cache/redis/README.md index fb766dff67d..addd537447b 100644 --- a/avm/res/cache/redis/README.md +++ b/avm/res/cache/redis/README.md @@ -1341,6 +1341,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1507,6 +1518,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Redis Cache Contributor'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/cdn/profile/README.md b/avm/res/cdn/profile/README.md index ccaf732ba3e..369cadf61be 100644 --- a/avm/res/cdn/profile/README.md +++ b/avm/res/cdn/profile/README.md @@ -740,6 +740,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'CDN Endpoint Contributor'` + - `'CDN Endpoint Reader'` + - `'CDN Profile Contributor'` + - `'CDN Profile Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/cognitive-services/account/README.md b/avm/res/cognitive-services/account/README.md index 9838b6a3e63..32e22ce46f5 100644 --- a/avm/res/cognitive-services/account/README.md +++ b/avm/res/cognitive-services/account/README.md @@ -1869,6 +1869,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2010,6 +2021,36 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Cognitive Services Contributor'` + - `'Cognitive Services Custom Vision Contributor'` + - `'Cognitive Services Custom Vision Deployment'` + - `'Cognitive Services Custom Vision Labeler'` + - `'Cognitive Services Custom Vision Reader'` + - `'Cognitive Services Custom Vision Trainer'` + - `'Cognitive Services Data Reader (Preview)'` + - `'Cognitive Services Face Recognizer'` + - `'Cognitive Services Immersive Reader User'` + - `'Cognitive Services Language Owner'` + - `'Cognitive Services Language Reader'` + - `'Cognitive Services Language Writer'` + - `'Cognitive Services LUIS Owner'` + - `'Cognitive Services LUIS Reader'` + - `'Cognitive Services LUIS Writer'` + - `'Cognitive Services Metrics Advisor Administrator'` + - `'Cognitive Services Metrics Advisor User'` + - `'Cognitive Services OpenAI Contributor'` + - `'Cognitive Services OpenAI User'` + - `'Cognitive Services QnA Maker Editor'` + - `'Cognitive Services QnA Maker Reader'` + - `'Cognitive Services Speech Contributor'` + - `'Cognitive Services Speech User'` + - `'Cognitive Services User'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/communication/communication-service/README.md b/avm/res/communication/communication-service/README.md index def24cd5654..e1d2bdd0464 100644 --- a/avm/res/communication/communication-service/README.md +++ b/avm/res/communication/communication-service/README.md @@ -606,6 +606,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/communication/email-service/README.md b/avm/res/communication/email-service/README.md index 756b60281b6..0087caf5ff6 100644 --- a/avm/res/communication/email-service/README.md +++ b/avm/res/communication/email-service/README.md @@ -451,6 +451,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/communication/email-service/domain/README.md b/avm/res/communication/email-service/domain/README.md index 20316013ee5..e649034df23 100644 --- a/avm/res/communication/email-service/domain/README.md +++ b/avm/res/communication/email-service/domain/README.md @@ -123,6 +123,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/availability-set/README.md b/avm/res/compute/availability-set/README.md index 57229ca6f34..b0587664135 100644 --- a/avm/res/compute/availability-set/README.md +++ b/avm/res/compute/availability-set/README.md @@ -377,6 +377,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Virtual Machine Administrator Login'` + - `'Virtual Machine Contributor'` + - `'Virtual Machine Data Access Administrator (preview)'` + - `'Virtual Machine User Login'` **Required parameters** diff --git a/avm/res/compute/disk-encryption-set/README.md b/avm/res/compute/disk-encryption-set/README.md index eb3e9b657ca..85900913fe3 100644 --- a/avm/res/compute/disk-encryption-set/README.md +++ b/avm/res/compute/disk-encryption-set/README.md @@ -585,6 +585,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Data Operator for Managed Disks'` + - `'Disk Backup Reader'` + - `'Disk Pool Operator'` + - `'Disk Restore Operator'` + - `'Disk Snapshot Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/disk/README.md b/avm/res/compute/disk/README.md index be1624c4f6b..3a95ddb2adc 100644 --- a/avm/res/compute/disk/README.md +++ b/avm/res/compute/disk/README.md @@ -818,6 +818,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Data Operator for Managed Disks'` + - `'Disk Backup Reader'` + - `'Disk Pool Operator'` + - `'Disk Restore Operator'` + - `'Disk Snapshot Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/gallery/README.md b/avm/res/compute/gallery/README.md index 926c7e1b35c..62c4b8041fb 100644 --- a/avm/res/compute/gallery/README.md +++ b/avm/res/compute/gallery/README.md @@ -1048,6 +1048,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Compute Gallery Sharing Admin'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/gallery/application/README.md b/avm/res/compute/gallery/application/README.md index be702370056..879fb1c1db5 100644 --- a/avm/res/compute/gallery/application/README.md +++ b/avm/res/compute/gallery/application/README.md @@ -129,6 +129,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Compute Gallery Sharing Admin'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/gallery/image/README.md b/avm/res/compute/gallery/image/README.md index 98d3d3ec682..484444ae230 100644 --- a/avm/res/compute/gallery/image/README.md +++ b/avm/res/compute/gallery/image/README.md @@ -321,6 +321,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Compute Gallery Sharing Admin'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/image/README.md b/avm/res/compute/image/README.md index 786bf30b97e..58545668c5d 100644 --- a/avm/res/compute/image/README.md +++ b/avm/res/compute/image/README.md @@ -476,6 +476,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/proximity-placement-group/README.md b/avm/res/compute/proximity-placement-group/README.md index 849182b03d6..7bb3c4a8573 100644 --- a/avm/res/compute/proximity-placement-group/README.md +++ b/avm/res/compute/proximity-placement-group/README.md @@ -429,6 +429,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/ssh-public-key/README.md b/avm/res/compute/ssh-public-key/README.md index 6db199cbfd4..a4ebea41858 100644 --- a/avm/res/compute/ssh-public-key/README.md +++ b/avm/res/compute/ssh-public-key/README.md @@ -403,6 +403,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/compute/virtual-machine-scale-set/README.md b/avm/res/compute/virtual-machine-scale-set/README.md index 730b23f033c..febb8bfea72 100644 --- a/avm/res/compute/virtual-machine-scale-set/README.md +++ b/avm/res/compute/virtual-machine-scale-set/README.md @@ -2450,6 +2450,25 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Data Operator for Managed Disks'` + - `'Desktop Virtualization Power On Contributor'` + - `'Desktop Virtualization Power On Off Contributor'` + - `'Desktop Virtualization Virtual Machine Contributor'` + - `'DevTest Labs User'` + - `'Disk Backup Reader'` + - `'Disk Pool Operator'` + - `'Disk Restore Operator'` + - `'Disk Snapshot Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Virtual Machine Administrator Login'` + - `'Virtual Machine Contributor'` + - `'Virtual Machine User Login'` + - `'VM Scanner Operator'` **Required parameters** diff --git a/avm/res/compute/virtual-machine/README.md b/avm/res/compute/virtual-machine/README.md index f34080a1bcf..855f72b4a87 100644 --- a/avm/res/compute/virtual-machine/README.md +++ b/avm/res/compute/virtual-machine/README.md @@ -4210,6 +4210,25 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Data Operator for Managed Disks'` + - `'Desktop Virtualization Power On Contributor'` + - `'Desktop Virtualization Power On Off Contributor'` + - `'Desktop Virtualization Virtual Machine Contributor'` + - `'DevTest Labs User'` + - `'Disk Backup Reader'` + - `'Disk Pool Operator'` + - `'Disk Restore Operator'` + - `'Disk Snapshot Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Virtual Machine Administrator Login'` + - `'Virtual Machine Contributor'` + - `'Virtual Machine User Login'` + - `'VM Scanner Operator'` **Required parameters** diff --git a/avm/res/container-registry/registry/README.md b/avm/res/container-registry/registry/README.md index f81a37d43a3..02594a96ff7 100644 --- a/avm/res/container-registry/registry/README.md +++ b/avm/res/container-registry/registry/README.md @@ -1526,6 +1526,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1696,6 +1707,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'AcrDelete'` + - `'AcrImageSigner'` + - `'AcrPull'` + - `'AcrPush'` + - `'AcrQuarantineReader'` + - `'AcrQuarantineWriter'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/container-service/managed-cluster/README.md b/avm/res/container-service/managed-cluster/README.md index 44a7b26a1e2..2e988e1ca58 100644 --- a/avm/res/container-service/managed-cluster/README.md +++ b/avm/res/container-service/managed-cluster/README.md @@ -3194,6 +3194,26 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Kubernetes Fleet Manager Contributor Role'` + - `'Azure Kubernetes Fleet Manager RBAC Admin'` + - `'Azure Kubernetes Fleet Manager RBAC Cluster Admin'` + - `'Azure Kubernetes Fleet Manager RBAC Reader'` + - `'Azure Kubernetes Fleet Manager RBAC Writer'` + - `'Azure Kubernetes Service Cluster Admin Role'` + - `'Azure Kubernetes Service Cluster Monitoring User'` + - `'Azure Kubernetes Service Cluster User Role'` + - `'Azure Kubernetes Service Contributor Role'` + - `'Azure Kubernetes Service RBAC Admin'` + - `'Azure Kubernetes Service RBAC Cluster Admin'` + - `'Azure Kubernetes Service RBAC Reader'` + - `'Azure Kubernetes Service RBAC Writer'` + - `'Contributor'` + - `'Kubernetes Agentless Operator'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/data-factory/factory/README.md b/avm/res/data-factory/factory/README.md index d4ee7144b01..bd9ec6e1c65 100644 --- a/avm/res/data-factory/factory/README.md +++ b/avm/res/data-factory/factory/README.md @@ -1270,6 +1270,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1397,6 +1408,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Data Factory Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/data-protection/backup-vault/README.md b/avm/res/data-protection/backup-vault/README.md index 5ef4757a5b8..77172ba563c 100644 --- a/avm/res/data-protection/backup-vault/README.md +++ b/avm/res/data-protection/backup-vault/README.md @@ -693,6 +693,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Backup Contributor'` + - `'Backup Operator'` + - `'Backup Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/databricks/access-connector/README.md b/avm/res/databricks/access-connector/README.md index 5086b63cd6e..e825762f759 100644 --- a/avm/res/databricks/access-connector/README.md +++ b/avm/res/databricks/access-connector/README.md @@ -392,6 +392,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/databricks/workspace/README.md b/avm/res/databricks/workspace/README.md index b3084f34a59..5b877cd4ceb 100644 --- a/avm/res/databricks/workspace/README.md +++ b/avm/res/databricks/workspace/README.md @@ -1565,6 +1565,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1977,6 +1983,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/db-for-my-sql/flexible-server/README.md b/avm/res/db-for-my-sql/flexible-server/README.md index f915bedd9d6..dbe135fbc55 100644 --- a/avm/res/db-for-my-sql/flexible-server/README.md +++ b/avm/res/db-for-my-sql/flexible-server/README.md @@ -1223,6 +1223,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'MySQL Backup And Export Operator'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/db-for-postgre-sql/flexible-server/README.md b/avm/res/db-for-postgre-sql/flexible-server/README.md index 8e84071338e..c60b6026b4f 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/README.md +++ b/avm/res/db-for-postgre-sql/flexible-server/README.md @@ -1381,6 +1381,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/desktop-virtualization/application-group/README.md b/avm/res/desktop-virtualization/application-group/README.md index 96040b80183..da9a322e545 100644 --- a/avm/res/desktop-virtualization/application-group/README.md +++ b/avm/res/desktop-virtualization/application-group/README.md @@ -608,6 +608,29 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Owner'` + - `'Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Application Group Contributor'` + - `'Desktop Virtualization Application Group Contributor'` + - `'Desktop Virtualization Application Group Reader'` + - `'Desktop Virtualization Contributor'` + - `'Desktop Virtualization Host Pool Contributor'` + - `'Desktop Virtualization Host Pool Reader'` + - `'Desktop Virtualization Power On Off Contributor'` + - `'Desktop Virtualization Reader'` + - `'Desktop Virtualization Session Host Operator'` + - `'Desktop Virtualization User'` + - `'Desktop Virtualization User Session Operator'` + - `'Desktop Virtualization Virtual Machine Contributor'` + - `'Desktop Virtualization Workspace Contributor'` + - `'Desktop Virtualization Workspace Reader'` + - `'Managed Application Contributor Role'` + - `'Managed Application Operator Role'` + - `'Managed Applications Reader'` **Required parameters** diff --git a/avm/res/desktop-virtualization/host-pool/README.md b/avm/res/desktop-virtualization/host-pool/README.md index 2d0ab9b9573..886a58e81aa 100644 --- a/avm/res/desktop-virtualization/host-pool/README.md +++ b/avm/res/desktop-virtualization/host-pool/README.md @@ -1071,6 +1071,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1207,6 +1218,29 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Owner'` + - `'Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Application Group Contributor'` + - `'Desktop Virtualization Application Group Contributor'` + - `'Desktop Virtualization Application Group Reader'` + - `'Desktop Virtualization Contributor'` + - `'Desktop Virtualization Host Pool Contributor'` + - `'Desktop Virtualization Host Pool Reader'` + - `'Desktop Virtualization Power On Off Contributor'` + - `'Desktop Virtualization Reader'` + - `'Desktop Virtualization Session Host Operator'` + - `'Desktop Virtualization User'` + - `'Desktop Virtualization User Session Operator'` + - `'Desktop Virtualization Virtual Machine Contributor'` + - `'Desktop Virtualization Workspace Contributor'` + - `'Desktop Virtualization Workspace Reader'` + - `'Managed Application Contributor Role'` + - `'Managed Application Operator Role'` + - `'Managed Applications Reader'` **Required parameters** diff --git a/avm/res/desktop-virtualization/scaling-plan/README.md b/avm/res/desktop-virtualization/scaling-plan/README.md index a3860a32133..97207ef73cb 100644 --- a/avm/res/desktop-virtualization/scaling-plan/README.md +++ b/avm/res/desktop-virtualization/scaling-plan/README.md @@ -795,6 +795,26 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Owner'` + - `'Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Application Group Contributor'` + - `'Desktop Virtualization Application Group Contributor'` + - `'Desktop Virtualization Application Group Reader'` + - `'Desktop Virtualization Contributor'` + - `'Desktop Virtualization Host Pool Contributor'` + - `'Desktop Virtualization Host Pool Reader'` + - `'Desktop Virtualization Power On Off Contributor'` + - `'Desktop Virtualization Reader'` + - `'Desktop Virtualization Session Host Operator'` + - `'Desktop Virtualization User'` + - `'Desktop Virtualization User Session Operator'` + - `'Desktop Virtualization Virtual Machine Contributor'` + - `'Desktop Virtualization Workspace Contributor'` + - `'Desktop Virtualization Workspace Reader'` **Required parameters** diff --git a/avm/res/desktop-virtualization/workspace/README.md b/avm/res/desktop-virtualization/workspace/README.md index f37ac6ade8d..c71da4bcbb3 100644 --- a/avm/res/desktop-virtualization/workspace/README.md +++ b/avm/res/desktop-virtualization/workspace/README.md @@ -971,6 +971,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1090,6 +1101,26 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Owner'` + - `'Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Application Group Contributor'` + - `'Desktop Virtualization Application Group Contributor'` + - `'Desktop Virtualization Application Group Reader'` + - `'Desktop Virtualization Contributor'` + - `'Desktop Virtualization Host Pool Contributor'` + - `'Desktop Virtualization Host Pool Reader'` + - `'Desktop Virtualization Power On Off Contributor'` + - `'Desktop Virtualization Reader'` + - `'Desktop Virtualization Session Host Operator'` + - `'Desktop Virtualization User'` + - `'Desktop Virtualization User Session Operator'` + - `'Desktop Virtualization Virtual Machine Contributor'` + - `'Desktop Virtualization Workspace Contributor'` + - `'Desktop Virtualization Workspace Reader'` **Required parameters** diff --git a/avm/res/dev-test-lab/lab/README.md b/avm/res/dev-test-lab/lab/README.md index 22a277da2ac..4c3db963d8e 100644 --- a/avm/res/dev-test-lab/lab/README.md +++ b/avm/res/dev-test-lab/lab/README.md @@ -1590,6 +1590,15 @@ Array of role assignment objects that contain the 'roleDefinitionIdOrName' and ' - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DevTest Labs User'` + - `'Owner'` + - `'Reader'` + - `'Resource Policy Contributor'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Virtual Machine Contributor'` **Required parameters** diff --git a/avm/res/digital-twins/digital-twins-instance/README.md b/avm/res/digital-twins/digital-twins-instance/README.md index 8dc8fc0cd81..17f002c7747 100644 --- a/avm/res/digital-twins/digital-twins-instance/README.md +++ b/avm/res/digital-twins/digital-twins-instance/README.md @@ -996,6 +996,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1115,6 +1126,14 @@ Array of role assignment objects that contain the 'roleDefinitionIdOrName' and ' - Required: No - Type: array +- Roles configurable by name: + - `'Azure Digital Twins Data Owner'` + - `'Azure Digital Twins Data Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/document-db/database-account/README.md b/avm/res/document-db/database-account/README.md index 24654fcc7fe..e3b1d30fb64 100644 --- a/avm/res/document-db/database-account/README.md +++ b/avm/res/document-db/database-account/README.md @@ -3189,6 +3189,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -3293,6 +3304,17 @@ Array of role assignment objects that contain the 'roleDefinitionIdOrName' and ' - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Cosmos DB Account Reader Role'` + - `'Cosmos DB Operator'` + - `'CosmosBackupOperator'` + - `'CosmosRestoreOperator'` + - `'DocumentDB Account Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/domain/README.md b/avm/res/event-grid/domain/README.md index 7514b32c3d7..a9833d90255 100644 --- a/avm/res/event-grid/domain/README.md +++ b/avm/res/event-grid/domain/README.md @@ -1033,6 +1033,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1160,6 +1171,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/namespace/README.md b/avm/res/event-grid/namespace/README.md index 290a4d6631c..7ac321334d7 100644 --- a/avm/res/event-grid/namespace/README.md +++ b/avm/res/event-grid/namespace/README.md @@ -2006,6 +2006,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2132,6 +2143,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Resource Notifications System Topics Subscriber'` + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Contributor'` + - `'EventGrid Data Receiver'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'EventGrid TopicSpaces Publisher'` + - `'EventGrid TopicSpaces Subscriber'` + - `'Owner'` + - `'Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/namespace/topic-space/README.md b/avm/res/event-grid/namespace/topic-space/README.md index a931f6ad62f..442efe3335f 100644 --- a/avm/res/event-grid/namespace/topic-space/README.md +++ b/avm/res/event-grid/namespace/topic-space/README.md @@ -71,6 +71,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Resource Notifications System Topics Subscriber'` + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Contributor'` + - `'EventGrid Data Receiver'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'EventGrid TopicSpaces Publisher'` + - `'EventGrid TopicSpaces Subscriber'` + - `'Owner'` + - `'Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/namespace/topic/README.md b/avm/res/event-grid/namespace/topic/README.md index 8b0920ea45e..9d831de41c1 100644 --- a/avm/res/event-grid/namespace/topic/README.md +++ b/avm/res/event-grid/namespace/topic/README.md @@ -129,6 +129,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Resource Notifications System Topics Subscriber'` + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Contributor'` + - `'EventGrid Data Receiver'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'EventGrid TopicSpaces Publisher'` + - `'EventGrid TopicSpaces Subscriber'` + - `'Owner'` + - `'Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/namespace/topic/event-subscription/README.md b/avm/res/event-grid/namespace/topic/event-subscription/README.md index 8b2fcd5527e..3efe652422e 100644 --- a/avm/res/event-grid/namespace/topic/event-subscription/README.md +++ b/avm/res/event-grid/namespace/topic/event-subscription/README.md @@ -88,6 +88,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Resource Notifications System Topics Subscriber'` + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Contributor'` + - `'EventGrid Data Receiver'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'EventGrid TopicSpaces Publisher'` + - `'EventGrid TopicSpaces Subscriber'` + - `'Owner'` + - `'Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/system-topic/README.md b/avm/res/event-grid/system-topic/README.md index eb74e203200..98c9a44742e 100644 --- a/avm/res/event-grid/system-topic/README.md +++ b/avm/res/event-grid/system-topic/README.md @@ -732,6 +732,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-grid/topic/README.md b/avm/res/event-grid/topic/README.md index 56864ec7107..bf85377cfe7 100644 --- a/avm/res/event-grid/topic/README.md +++ b/avm/res/event-grid/topic/README.md @@ -1127,6 +1127,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1254,6 +1265,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'EventGrid Contributor'` + - `'EventGrid Data Sender'` + - `'EventGrid EventSubscription Contributor'` + - `'EventGrid EventSubscription Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-hub/namespace/README.md b/avm/res/event-hub/namespace/README.md index aae733f8da5..c32df1eeb07 100644 --- a/avm/res/event-hub/namespace/README.md +++ b/avm/res/event-hub/namespace/README.md @@ -1631,6 +1631,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1767,6 +1778,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Event Hubs Data Owner'` + - `'Azure Event Hubs Data Receiver'` + - `'Azure Event Hubs Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/event-hub/namespace/eventhub/README.md b/avm/res/event-hub/namespace/eventhub/README.md index 8964602f1bf..b10aa5e66f7 100644 --- a/avm/res/event-hub/namespace/eventhub/README.md +++ b/avm/res/event-hub/namespace/eventhub/README.md @@ -273,6 +273,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Event Hubs Data Owner'` + - `'Azure Event Hubs Data Receiver'` + - `'Azure Event Hubs Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/health-bot/health-bot/README.md b/avm/res/health-bot/health-bot/README.md index 032ebd2d903..b23485fd1cf 100644 --- a/avm/res/health-bot/health-bot/README.md +++ b/avm/res/health-bot/health-bot/README.md @@ -407,6 +407,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/healthcare-apis/workspace/README.md b/avm/res/healthcare-apis/workspace/README.md index 3347555bd99..992cee3c232 100644 --- a/avm/res/healthcare-apis/workspace/README.md +++ b/avm/res/healthcare-apis/workspace/README.md @@ -590,6 +590,21 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DICOM Data Owner'` + - `'DICOM Data Reader'` + - `'FHIR Data Contributor'` + - `'FHIR Data Converter'` + - `'FHIR Data Exporter'` + - `'FHIR Data Importer'` + - `'FHIR Data Reader'` + - `'FHIR Data Writer'` + - `'FHIR SMART User'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/healthcare-apis/workspace/fhirservice/README.md b/avm/res/healthcare-apis/workspace/fhirservice/README.md index 0835f815843..ce0990cf469 100644 --- a/avm/res/healthcare-apis/workspace/fhirservice/README.md +++ b/avm/res/healthcare-apis/workspace/fhirservice/README.md @@ -468,6 +468,21 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DICOM Data Owner'` + - `'DICOM Data Reader'` + - `'FHIR Data Contributor'` + - `'FHIR Data Converter'` + - `'FHIR Data Exporter'` + - `'FHIR Data Importer'` + - `'FHIR Data Reader'` + - `'FHIR Data Writer'` + - `'FHIR SMART User'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/hybrid-compute/machine/README.md b/avm/res/hybrid-compute/machine/README.md index 5221e3578be..b7f4fd21bad 100644 --- a/avm/res/hybrid-compute/machine/README.md +++ b/avm/res/hybrid-compute/machine/README.md @@ -509,6 +509,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Arc machine Administrator Login'` + - `'Arc machine Contributor'` + - `'Arc machine User Login'` + - `'Windows Admin Center Administrator Login'` **Required parameters** diff --git a/avm/res/insights/action-group/README.md b/avm/res/insights/action-group/README.md index 5ca2f832b82..becc4cda4df 100644 --- a/avm/res/insights/action-group/README.md +++ b/avm/res/insights/action-group/README.md @@ -413,6 +413,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/activity-log-alert/README.md b/avm/res/insights/activity-log-alert/README.md index ab8e82e3057..3dc67e6cf2b 100644 --- a/avm/res/insights/activity-log-alert/README.md +++ b/avm/res/insights/activity-log-alert/README.md @@ -553,6 +553,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/component/README.md b/avm/res/insights/component/README.md index 458098b208b..e4f7b78ac5f 100644 --- a/avm/res/insights/component/README.md +++ b/avm/res/insights/component/README.md @@ -645,6 +645,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Monitoring Metrics Publisher'` + - `'Application Insights Component Contributor'` + - `'Application Insights Snapshot Debugger'` + - `'Monitoring Contributor'` **Required parameters** diff --git a/avm/res/insights/data-collection-endpoint/README.md b/avm/res/insights/data-collection-endpoint/README.md index a1cd20427a1..a2f09497c19 100644 --- a/avm/res/insights/data-collection-endpoint/README.md +++ b/avm/res/insights/data-collection-endpoint/README.md @@ -391,6 +391,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/data-collection-rule/README.md b/avm/res/insights/data-collection-rule/README.md index 6134656d6d7..300de97955c 100644 --- a/avm/res/insights/data-collection-rule/README.md +++ b/avm/res/insights/data-collection-rule/README.md @@ -2052,6 +2052,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/metric-alert/README.md b/avm/res/insights/metric-alert/README.md index a28a379fae4..121b127c85c 100644 --- a/avm/res/insights/metric-alert/README.md +++ b/avm/res/insights/metric-alert/README.md @@ -498,6 +498,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/private-link-scope/README.md b/avm/res/insights/private-link-scope/README.md index 9c6f5d57194..4dda77b2098 100644 --- a/avm/res/insights/private-link-scope/README.md +++ b/avm/res/insights/private-link-scope/README.md @@ -1221,6 +1221,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1332,6 +1343,21 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Log Analytics Contributor'` + - `'Log Analytics Reader'` + - `'Logic App Contributor'` + - `'Monitoring Contributor'` + - `'Monitoring Metrics Publisher'` + - `'Monitoring Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'Tag Contributor'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/scheduled-query-rule/README.md b/avm/res/insights/scheduled-query-rule/README.md index c0f4c7a9795..7497372fa6b 100644 --- a/avm/res/insights/scheduled-query-rule/README.md +++ b/avm/res/insights/scheduled-query-rule/README.md @@ -658,6 +658,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/insights/webtest/README.md b/avm/res/insights/webtest/README.md index 2b0ca9def05..ef3bac62534 100644 --- a/avm/res/insights/webtest/README.md +++ b/avm/res/insights/webtest/README.md @@ -529,6 +529,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/key-vault/vault/README.md b/avm/res/key-vault/vault/README.md index 10326e55cc4..9997df4ed0d 100644 --- a/avm/res/key-vault/vault/README.md +++ b/avm/res/key-vault/vault/README.md @@ -1681,6 +1681,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Key Vault Administrator'` + - `'Key Vault Contributor'` + - `'Key Vault Crypto Officer'` + - `'Key Vault Crypto Service Encryption User'` + - `'Key Vault Crypto User'` + - `'Key Vault Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2223,6 +2235,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2350,6 +2373,22 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Key Vault Administrator'` + - `'Key Vault Certificates Officer'` + - `'Key Vault Certificate User'` + - `'Key Vault Contributor'` + - `'Key Vault Crypto Officer'` + - `'Key Vault Crypto Service Encryption User'` + - `'Key Vault Crypto User'` + - `'Key Vault Reader'` + - `'Key Vault Secrets Officer'` + - `'Key Vault Secrets User'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2527,6 +2566,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Key Vault Administrator'` + - `'Key Vault Contributor'` + - `'Key Vault Reader'` + - `'Key Vault Secrets Officer'` + - `'Key Vault Secrets User'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/key-vault/vault/key/README.md b/avm/res/key-vault/vault/key/README.md index 4531fa3eaba..a5dfe17fe2e 100644 --- a/avm/res/key-vault/vault/key/README.md +++ b/avm/res/key-vault/vault/key/README.md @@ -154,6 +154,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Key Vault Administrator'` + - `'Key Vault Contributor'` + - `'Key Vault Crypto Officer'` + - `'Key Vault Crypto Service Encryption User'` + - `'Key Vault Crypto User'` + - `'Key Vault Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/key-vault/vault/secret/README.md b/avm/res/key-vault/vault/secret/README.md index d62aac35dd7..b05c38071f5 100644 --- a/avm/res/key-vault/vault/secret/README.md +++ b/avm/res/key-vault/vault/secret/README.md @@ -97,6 +97,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Key Vault Administrator'` + - `'Key Vault Contributor'` + - `'Key Vault Reader'` + - `'Key Vault Secrets Officer'` + - `'Key Vault Secrets User'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/kusto/cluster/README.md b/avm/res/kusto/cluster/README.md index 8d94f1f46fb..40d7a0e1153 100644 --- a/avm/res/kusto/cluster/README.md +++ b/avm/res/kusto/cluster/README.md @@ -1390,6 +1390,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1510,6 +1521,10 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` **Required parameters** diff --git a/avm/res/load-test-service/load-test/README.md b/avm/res/load-test-service/load-test/README.md index 16936f9cdfb..687a4e16a11 100644 --- a/avm/res/load-test-service/load-test/README.md +++ b/avm/res/load-test-service/load-test/README.md @@ -509,6 +509,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/logic/workflow/README.md b/avm/res/logic/workflow/README.md index fc08456b5cd..a3e33ad4801 100644 --- a/avm/res/logic/workflow/README.md +++ b/avm/res/logic/workflow/README.md @@ -805,6 +805,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Logic App Contributor'` + - `'Logic App Operator'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/machine-learning-services/workspace/README.md b/avm/res/machine-learning-services/workspace/README.md index 860f066e50e..e8b2aa23b55 100644 --- a/avm/res/machine-learning-services/workspace/README.md +++ b/avm/res/machine-learning-services/workspace/README.md @@ -2035,6 +2035,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2161,6 +2172,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'AzureML Compute Operator'` + - `'AzureML Data Scientist'` + - `'AzureML Metrics Writer (preview)'` + - `'AzureML Registry User'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/maintenance/maintenance-configuration/README.md b/avm/res/maintenance/maintenance-configuration/README.md index b2fe74ff0cd..d8187c2a1fd 100644 --- a/avm/res/maintenance/maintenance-configuration/README.md +++ b/avm/res/maintenance/maintenance-configuration/README.md @@ -528,6 +528,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'Scheduled Patching Contributor'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/managed-identity/user-assigned-identity/README.md b/avm/res/managed-identity/user-assigned-identity/README.md index 6ab0ca20821..43908d19fea 100644 --- a/avm/res/managed-identity/user-assigned-identity/README.md +++ b/avm/res/managed-identity/user-assigned-identity/README.md @@ -463,6 +463,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Managed Identity Contributor'` + - `'Managed Identity Operator'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/net-app/net-app-account/README.md b/avm/res/net-app/net-app-account/README.md index 7ee4003cbec..13b76bd3089 100644 --- a/avm/res/net-app/net-app-account/README.md +++ b/avm/res/net-app/net-app-account/README.md @@ -951,6 +951,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/net-app/net-app-account/capacity-pool/README.md b/avm/res/net-app/net-app-account/capacity-pool/README.md index e8d582aa36a..4c95a9ca0b4 100644 --- a/avm/res/net-app/net-app-account/capacity-pool/README.md +++ b/avm/res/net-app/net-app-account/capacity-pool/README.md @@ -130,6 +130,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/net-app/net-app-account/capacity-pool/volume/README.md b/avm/res/net-app/net-app-account/capacity-pool/volume/README.md index f7d5472e355..9361b0045e5 100644 --- a/avm/res/net-app/net-app-account/capacity-pool/volume/README.md +++ b/avm/res/net-app/net-app-account/capacity-pool/volume/README.md @@ -399,6 +399,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/application-gateway/README.md b/avm/res/network/application-gateway/README.md index b5d206d002c..ad061f01d1c 100644 --- a/avm/res/network/application-gateway/README.md +++ b/avm/res/network/application-gateway/README.md @@ -2840,6 +2840,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2984,6 +2995,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/application-security-group/README.md b/avm/res/network/application-security-group/README.md index 227c71bc930..6ef90cc95a7 100644 --- a/avm/res/network/application-security-group/README.md +++ b/avm/res/network/application-security-group/README.md @@ -341,6 +341,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/azure-firewall/README.md b/avm/res/network/azure-firewall/README.md index cb38f1c5053..ce6a6114efc 100644 --- a/avm/res/network/azure-firewall/README.md +++ b/avm/res/network/azure-firewall/README.md @@ -2079,6 +2079,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/bastion-host/README.md b/avm/res/network/bastion-host/README.md index a7c2f5dd910..fc93544fc5d 100644 --- a/avm/res/network/bastion-host/README.md +++ b/avm/res/network/bastion-host/README.md @@ -764,6 +764,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/ddos-protection-plan/README.md b/avm/res/network/ddos-protection-plan/README.md index 5d9187c4d36..6bdd5c6b3c9 100644 --- a/avm/res/network/ddos-protection-plan/README.md +++ b/avm/res/network/ddos-protection-plan/README.md @@ -341,6 +341,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-forwarding-ruleset/README.md b/avm/res/network/dns-forwarding-ruleset/README.md index d126a5c2a51..e0d4cf14aea 100644 --- a/avm/res/network/dns-forwarding-ruleset/README.md +++ b/avm/res/network/dns-forwarding-ruleset/README.md @@ -504,6 +504,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** diff --git a/avm/res/network/dns-resolver/README.md b/avm/res/network/dns-resolver/README.md index 8437207f1fb..4642dd8e316 100644 --- a/avm/res/network/dns-resolver/README.md +++ b/avm/res/network/dns-resolver/README.md @@ -542,6 +542,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/README.md b/avm/res/network/dns-zone/README.md index 8a4e661ec14..e7a9c463a37 100644 --- a/avm/res/network/dns-zone/README.md +++ b/avm/res/network/dns-zone/README.md @@ -940,6 +940,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1108,6 +1120,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1291,6 +1315,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1452,6 +1488,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1679,6 +1727,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1839,6 +1899,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1999,6 +2071,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2103,6 +2187,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2236,6 +2332,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2444,6 +2552,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2635,6 +2755,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/a/README.md b/avm/res/network/dns-zone/a/README.md index 8d42ef07477..e93a99ac4a4 100644 --- a/avm/res/network/dns-zone/a/README.md +++ b/avm/res/network/dns-zone/a/README.md @@ -73,6 +73,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/aaaa/README.md b/avm/res/network/dns-zone/aaaa/README.md index 93755b92fcb..99120461e1b 100644 --- a/avm/res/network/dns-zone/aaaa/README.md +++ b/avm/res/network/dns-zone/aaaa/README.md @@ -73,6 +73,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/caa/README.md b/avm/res/network/dns-zone/caa/README.md index 8b433084d98..7d1b1ef225c 100644 --- a/avm/res/network/dns-zone/caa/README.md +++ b/avm/res/network/dns-zone/caa/README.md @@ -72,6 +72,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/cname/README.md b/avm/res/network/dns-zone/cname/README.md index 197a33a7fdd..7107700e874 100644 --- a/avm/res/network/dns-zone/cname/README.md +++ b/avm/res/network/dns-zone/cname/README.md @@ -73,6 +73,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/mx/README.md b/avm/res/network/dns-zone/mx/README.md index 19577e558ee..68615af23c4 100644 --- a/avm/res/network/dns-zone/mx/README.md +++ b/avm/res/network/dns-zone/mx/README.md @@ -72,6 +72,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/ns/README.md b/avm/res/network/dns-zone/ns/README.md index bf63641c451..43fd817fe2a 100644 --- a/avm/res/network/dns-zone/ns/README.md +++ b/avm/res/network/dns-zone/ns/README.md @@ -72,6 +72,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/ptr/README.md b/avm/res/network/dns-zone/ptr/README.md index d5fffa162be..2dda8e4f59d 100644 --- a/avm/res/network/dns-zone/ptr/README.md +++ b/avm/res/network/dns-zone/ptr/README.md @@ -72,6 +72,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/soa/README.md b/avm/res/network/dns-zone/soa/README.md index 3c0227fea8e..22832138fbf 100644 --- a/avm/res/network/dns-zone/soa/README.md +++ b/avm/res/network/dns-zone/soa/README.md @@ -65,6 +65,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/srv/README.md b/avm/res/network/dns-zone/srv/README.md index 309a7861018..c733d695718 100644 --- a/avm/res/network/dns-zone/srv/README.md +++ b/avm/res/network/dns-zone/srv/README.md @@ -65,6 +65,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/dns-zone/txt/README.md b/avm/res/network/dns-zone/txt/README.md index dbc21c21cd3..35f4be5c49f 100644 --- a/avm/res/network/dns-zone/txt/README.md +++ b/avm/res/network/dns-zone/txt/README.md @@ -65,6 +65,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/express-route-circuit/README.md b/avm/res/network/express-route-circuit/README.md index 578169e9ff7..50e00e3bd53 100644 --- a/avm/res/network/express-route-circuit/README.md +++ b/avm/res/network/express-route-circuit/README.md @@ -717,6 +717,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/express-route-gateway/README.md b/avm/res/network/express-route-gateway/README.md index cba6d19ddfc..d66a1ef172d 100644 --- a/avm/res/network/express-route-gateway/README.md +++ b/avm/res/network/express-route-gateway/README.md @@ -409,6 +409,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/front-door-web-application-firewall-policy/README.md b/avm/res/network/front-door-web-application-firewall-policy/README.md index 02c147d79ec..6b4b2dd0dfa 100644 --- a/avm/res/network/front-door-web-application-firewall-policy/README.md +++ b/avm/res/network/front-door-web-application-firewall-policy/README.md @@ -675,6 +675,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/front-door/README.md b/avm/res/network/front-door/README.md index 1fc4d437219..50e01165f62 100644 --- a/avm/res/network/front-door/README.md +++ b/avm/res/network/front-door/README.md @@ -1157,6 +1157,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/ip-group/README.md b/avm/res/network/ip-group/README.md index 211d8de421d..48dcd81c36f 100644 --- a/avm/res/network/ip-group/README.md +++ b/avm/res/network/ip-group/README.md @@ -360,6 +360,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'IPAM Pool Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** diff --git a/avm/res/network/load-balancer/README.md b/avm/res/network/load-balancer/README.md index 2ca817beb0b..4b5103926d4 100644 --- a/avm/res/network/load-balancer/README.md +++ b/avm/res/network/load-balancer/README.md @@ -1459,6 +1459,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/local-network-gateway/README.md b/avm/res/network/local-network-gateway/README.md index 0e702b10edd..aa324dae8a1 100644 --- a/avm/res/network/local-network-gateway/README.md +++ b/avm/res/network/local-network-gateway/README.md @@ -445,6 +445,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/nat-gateway/README.md b/avm/res/network/nat-gateway/README.md index ed01e1d4525..8c6358ff0c0 100644 --- a/avm/res/network/nat-gateway/README.md +++ b/avm/res/network/nat-gateway/README.md @@ -628,6 +628,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/network-interface/README.md b/avm/res/network/network-interface/README.md index 3e9767ba448..6b6f123af85 100644 --- a/avm/res/network/network-interface/README.md +++ b/avm/res/network/network-interface/README.md @@ -722,6 +722,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** diff --git a/avm/res/network/network-manager/README.md b/avm/res/network/network-manager/README.md index 99f4273940c..ea6a3831c03 100644 --- a/avm/res/network/network-manager/README.md +++ b/avm/res/network/network-manager/README.md @@ -1075,6 +1075,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'IPAM Pool Contributor'` + - `'LocalNGFirewallAdministrator role'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Resource Policy Contributor'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/network-security-group/README.md b/avm/res/network/network-security-group/README.md index 15b057b8ce3..3de82ead53e 100644 --- a/avm/res/network/network-security-group/README.md +++ b/avm/res/network/network-security-group/README.md @@ -693,6 +693,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/network-watcher/README.md b/avm/res/network/network-watcher/README.md index a13c4ae45b8..d26f9648ba8 100644 --- a/avm/res/network/network-watcher/README.md +++ b/avm/res/network/network-watcher/README.md @@ -605,6 +605,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/README.md b/avm/res/network/private-dns-zone/README.md index b9bcac8c5ed..bcc558ab558 100644 --- a/avm/res/network/private-dns-zone/README.md +++ b/avm/res/network/private-dns-zone/README.md @@ -823,6 +823,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -983,6 +991,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1143,6 +1159,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1363,6 +1387,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1523,6 +1555,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1627,6 +1667,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** @@ -1760,6 +1807,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1968,6 +2023,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2159,6 +2222,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/a/README.md b/avm/res/network/private-dns-zone/a/README.md index 1d776a8b076..6584b4966ca 100644 --- a/avm/res/network/private-dns-zone/a/README.md +++ b/avm/res/network/private-dns-zone/a/README.md @@ -72,6 +72,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/aaaa/README.md b/avm/res/network/private-dns-zone/aaaa/README.md index 8eb2f75a000..01b9ca1fbe3 100644 --- a/avm/res/network/private-dns-zone/aaaa/README.md +++ b/avm/res/network/private-dns-zone/aaaa/README.md @@ -72,6 +72,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/cname/README.md b/avm/res/network/private-dns-zone/cname/README.md index 0e3cba5ca81..bcabc2f0a8c 100644 --- a/avm/res/network/private-dns-zone/cname/README.md +++ b/avm/res/network/private-dns-zone/cname/README.md @@ -72,6 +72,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/mx/README.md b/avm/res/network/private-dns-zone/mx/README.md index 3284c3e1e7b..721b698ccd5 100644 --- a/avm/res/network/private-dns-zone/mx/README.md +++ b/avm/res/network/private-dns-zone/mx/README.md @@ -72,6 +72,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/ptr/README.md b/avm/res/network/private-dns-zone/ptr/README.md index 23549d6a1f8..0c8d412a531 100644 --- a/avm/res/network/private-dns-zone/ptr/README.md +++ b/avm/res/network/private-dns-zone/ptr/README.md @@ -72,6 +72,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/soa/README.md b/avm/res/network/private-dns-zone/soa/README.md index 5936d5e83c9..fd6c40a7fb2 100644 --- a/avm/res/network/private-dns-zone/soa/README.md +++ b/avm/res/network/private-dns-zone/soa/README.md @@ -65,6 +65,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/srv/README.md b/avm/res/network/private-dns-zone/srv/README.md index 01a7f680376..a6047c6bda5 100644 --- a/avm/res/network/private-dns-zone/srv/README.md +++ b/avm/res/network/private-dns-zone/srv/README.md @@ -65,6 +65,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-dns-zone/txt/README.md b/avm/res/network/private-dns-zone/txt/README.md index 5439d372f22..2121b15b16f 100644 --- a/avm/res/network/private-dns-zone/txt/README.md +++ b/avm/res/network/private-dns-zone/txt/README.md @@ -65,6 +65,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Private DNS Zone Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/private-endpoint/README.md b/avm/res/network/private-endpoint/README.md index 9aa9d1085ef..c0637427963 100644 --- a/avm/res/network/private-endpoint/README.md +++ b/avm/res/network/private-endpoint/README.md @@ -942,6 +942,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** diff --git a/avm/res/network/private-link-service/README.md b/avm/res/network/private-link-service/README.md index 1cf77598261..3df921a3c3b 100644 --- a/avm/res/network/private-link-service/README.md +++ b/avm/res/network/private-link-service/README.md @@ -570,6 +570,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/public-ip-address/README.md b/avm/res/network/public-ip-address/README.md index 9f4526d01e0..6c71fc4c87d 100644 --- a/avm/res/network/public-ip-address/README.md +++ b/avm/res/network/public-ip-address/README.md @@ -810,6 +810,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator'` **Required parameters** diff --git a/avm/res/network/public-ip-prefix/README.md b/avm/res/network/public-ip-prefix/README.md index 8b70c24c455..22a3790bf45 100644 --- a/avm/res/network/public-ip-prefix/README.md +++ b/avm/res/network/public-ip-prefix/README.md @@ -371,6 +371,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/route-table/README.md b/avm/res/network/route-table/README.md index c93c63b58a2..a0a83836fb0 100644 --- a/avm/res/network/route-table/README.md +++ b/avm/res/network/route-table/README.md @@ -395,6 +395,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/service-endpoint-policy/README.md b/avm/res/network/service-endpoint-policy/README.md index bc95b35f176..7d1097c4b2b 100644 --- a/avm/res/network/service-endpoint-policy/README.md +++ b/avm/res/network/service-endpoint-policy/README.md @@ -240,6 +240,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/trafficmanagerprofile/README.md b/avm/res/network/trafficmanagerprofile/README.md index d7f8192264a..c7dfa7d65c1 100644 --- a/avm/res/network/trafficmanagerprofile/README.md +++ b/avm/res/network/trafficmanagerprofile/README.md @@ -675,6 +675,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'Traffic Manager Contributor'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/virtual-network-gateway/README.md b/avm/res/network/virtual-network-gateway/README.md index 36648bf55a2..83cde965b03 100644 --- a/avm/res/network/virtual-network-gateway/README.md +++ b/avm/res/network/virtual-network-gateway/README.md @@ -1582,6 +1582,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/virtual-network/README.md b/avm/res/network/virtual-network/README.md index 4b6fa95d306..b3f52a10bb9 100644 --- a/avm/res/network/virtual-network/README.md +++ b/avm/res/network/virtual-network/README.md @@ -1195,6 +1195,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -1415,6 +1422,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/virtual-network/subnet/README.md b/avm/res/network/virtual-network/subnet/README.md index e6269f80464..9a975d39da0 100644 --- a/avm/res/network/virtual-network/subnet/README.md +++ b/avm/res/network/virtual-network/subnet/README.md @@ -144,6 +144,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/virtual-wan/README.md b/avm/res/network/virtual-wan/README.md index 18b3f54a3b9..418464fd848 100644 --- a/avm/res/network/virtual-wan/README.md +++ b/avm/res/network/virtual-wan/README.md @@ -391,6 +391,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/network/vpn-site/README.md b/avm/res/network/vpn-site/README.md index 07e306d08b5..63156245bbf 100644 --- a/avm/res/network/vpn-site/README.md +++ b/avm/res/network/vpn-site/README.md @@ -606,6 +606,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Network Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/operational-insights/workspace/README.md b/avm/res/operational-insights/workspace/README.md index 2290266bfbf..59a1b53a9ac 100644 --- a/avm/res/operational-insights/workspace/README.md +++ b/avm/res/operational-insights/workspace/README.md @@ -2081,6 +2081,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Log Analytics Contributor'` + - `'Log Analytics Reader'` + - `'Monitoring Contributor'` + - `'Monitoring Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'Security Admin'` + - `'Security Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/operational-insights/workspace/table/README.md b/avm/res/operational-insights/workspace/table/README.md index 7313d497eed..dc20f643c0c 100644 --- a/avm/res/operational-insights/workspace/table/README.md +++ b/avm/res/operational-insights/workspace/table/README.md @@ -92,6 +92,16 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Log Analytics Contributor'` + - `'Log Analytics Reader'` + - `'Monitoring Contributor'` + - `'Monitoring Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/portal/dashboard/README.md b/avm/res/portal/dashboard/README.md index 0a33b644ffb..07b61aa2619 100644 --- a/avm/res/portal/dashboard/README.md +++ b/avm/res/portal/dashboard/README.md @@ -796,6 +796,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/power-bi-dedicated/capacity/README.md b/avm/res/power-bi-dedicated/capacity/README.md index 0ef1e8e8f40..b01901dabae 100644 --- a/avm/res/power-bi-dedicated/capacity/README.md +++ b/avm/res/power-bi-dedicated/capacity/README.md @@ -478,6 +478,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Log Analytics Contributor'` + - `'Log Analytics Reader'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/purview/account/README.md b/avm/res/purview/account/README.md index 26ccfdb7d29..cc71dad4f66 100644 --- a/avm/res/purview/account/README.md +++ b/avm/res/purview/account/README.md @@ -2165,6 +2165,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2546,6 +2552,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2941,6 +2953,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/recovery-services/vault/README.md b/avm/res/recovery-services/vault/README.md index b6ca61f7e59..06c21b10e95 100644 --- a/avm/res/recovery-services/vault/README.md +++ b/avm/res/recovery-services/vault/README.md @@ -2445,6 +2445,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2603,6 +2614,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Backup Contributor'` + - `'Backup Operator'` + - `'Backup Reader'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'Site Recovery Contributor'` + - `'Site Recovery Operator'` + - `'Site Recovery Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/relay/namespace/README.md b/avm/res/relay/namespace/README.md index 837fa1fa439..06c2c905d18 100644 --- a/avm/res/relay/namespace/README.md +++ b/avm/res/relay/namespace/README.md @@ -1215,6 +1215,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1326,6 +1337,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Relay Listener'` + - `'Azure Relay Owner'` + - `'Azure Relay Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/relay/namespace/hybrid-connection/README.md b/avm/res/relay/namespace/hybrid-connection/README.md index 96212178afd..8df88f210df 100644 --- a/avm/res/relay/namespace/hybrid-connection/README.md +++ b/avm/res/relay/namespace/hybrid-connection/README.md @@ -144,6 +144,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Relay Listener'` + - `'Azure Relay Owner'` + - `'Azure Relay Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/relay/namespace/wcf-relay/README.md b/avm/res/relay/namespace/wcf-relay/README.md index abc0f0c610d..80caef4d778 100644 --- a/avm/res/relay/namespace/wcf-relay/README.md +++ b/avm/res/relay/namespace/wcf-relay/README.md @@ -161,6 +161,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Relay Listener'` + - `'Azure Relay Owner'` + - `'Azure Relay Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/resource-graph/query/README.md b/avm/res/resource-graph/query/README.md index fd64204c24f..316e8d7090e 100644 --- a/avm/res/resource-graph/query/README.md +++ b/avm/res/resource-graph/query/README.md @@ -374,6 +374,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/resources/deployment-script/README.md b/avm/res/resources/deployment-script/README.md index 9dac2f308cb..a922e8496cd 100644 --- a/avm/res/resources/deployment-script/README.md +++ b/avm/res/resources/deployment-script/README.md @@ -1012,6 +1012,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/search/search-service/README.md b/avm/res/search/search-service/README.md index a0de84e7401..9001f0359cd 100644 --- a/avm/res/search/search-service/README.md +++ b/avm/res/search/search-service/README.md @@ -1236,6 +1236,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1370,6 +1381,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'Search Index Data Contributor'` + - `'Search Index Data Reader'` + - `'Search Service Contributor'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/service-bus/namespace/README.md b/avm/res/service-bus/namespace/README.md index 0e05ec12f12..178f3dd6654 100644 --- a/avm/res/service-bus/namespace/README.md +++ b/avm/res/service-bus/namespace/README.md @@ -2000,6 +2000,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -2354,6 +2365,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Service Bus Data Owner'` + - `'Azure Service Bus Data Receiver'` + - `'Azure Service Bus Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2480,6 +2500,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Service Bus Data Owner'` + - `'Azure Service Bus Data Receiver'` + - `'Azure Service Bus Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** @@ -2764,6 +2793,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Service Bus Data Owner'` + - `'Azure Service Bus Data Receiver'` + - `'Azure Service Bus Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/service-bus/namespace/queue/README.md b/avm/res/service-bus/namespace/queue/README.md index cc4a9b560c9..0c581d4e73e 100644 --- a/avm/res/service-bus/namespace/queue/README.md +++ b/avm/res/service-bus/namespace/queue/README.md @@ -236,6 +236,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Service Bus Data Owner'` + - `'Azure Service Bus Data Receiver'` + - `'Azure Service Bus Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/service-bus/namespace/topic/README.md b/avm/res/service-bus/namespace/topic/README.md index 59c7b43e97e..7362055d9e0 100644 --- a/avm/res/service-bus/namespace/topic/README.md +++ b/avm/res/service-bus/namespace/topic/README.md @@ -201,6 +201,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Azure Service Bus Data Owner'` + - `'Azure Service Bus Data Receiver'` + - `'Azure Service Bus Data Sender'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/service-fabric/cluster/README.md b/avm/res/service-fabric/cluster/README.md index bdeeb3417cd..08c89d43fa0 100644 --- a/avm/res/service-fabric/cluster/README.md +++ b/avm/res/service-fabric/cluster/README.md @@ -1339,6 +1339,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/signal-r-service/signal-r/README.md b/avm/res/signal-r-service/signal-r/README.md index 35f25de4aaf..9d3688a0d38 100644 --- a/avm/res/signal-r-service/signal-r/README.md +++ b/avm/res/signal-r-service/signal-r/README.md @@ -983,6 +983,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1129,6 +1140,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'SignalR AccessKey Reader'` + - `'SignalR App Server'` + - `'SignalR REST API Owner'` + - `'SignalR REST API Reader'` + - `'SignalR Service Owner'` + - `'SignalR/Web PubSub Contributor'` + - `'User Access Administrator'` + - `'Web PubSub Service Owner (Preview)'` + - `'Web PubSub Service Reader (Preview)'` **Required parameters** diff --git a/avm/res/signal-r-service/web-pub-sub/README.md b/avm/res/signal-r-service/web-pub-sub/README.md index 46fb8b2483c..e255f3e70c1 100644 --- a/avm/res/signal-r-service/web-pub-sub/README.md +++ b/avm/res/signal-r-service/web-pub-sub/README.md @@ -952,6 +952,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1098,6 +1109,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'SignalR AccessKey Reader'` + - `'SignalR App Server'` + - `'SignalR REST API Owner'` + - `'SignalR REST API Reader'` + - `'SignalR Service Owner'` + - `'SignalR/Web PubSub Contributor'` + - `'User Access Administrator'` + - `'Web PubSub Service Owner (Preview)'` + - `'Web PubSub Service Reader (Preview)'` **Required parameters** diff --git a/avm/res/sql/managed-instance/README.md b/avm/res/sql/managed-instance/README.md index a9394f7cf0d..4efc05d72d1 100644 --- a/avm/res/sql/managed-instance/README.md +++ b/avm/res/sql/managed-instance/README.md @@ -1286,6 +1286,19 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Reservation Purchaser'` + - `'Role Based Access Control Administrator (Preview)'` + - `'SQL DB Contributor'` + - `'SQL Managed Instance Contributor'` + - `'SQL Security Manager'` + - `'SQL Server Contributor'` + - `'SqlDb Migration Role'` + - `'SqlMI Migration Role'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/sql/server/README.md b/avm/res/sql/server/README.md index e4306982f29..4f73df2fa8e 100644 --- a/avm/res/sql/server/README.md +++ b/avm/res/sql/server/README.md @@ -1722,6 +1722,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1865,6 +1876,19 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Reservation Purchaser'` + - `'Role Based Access Control Administrator'` + - `'SQL DB Contributor'` + - `'SQL Managed Instance Contributor'` + - `'SQL Security Manager'` + - `'SQL Server Contributor'` + - `'SqlDb Migration Role'` + - `'SqlMI Migration Role'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/storage/storage-account/README.md b/avm/res/storage/storage-account/README.md index 557a0ff537b..bafe70194ff 100644 --- a/avm/res/storage/storage-account/README.md +++ b/avm/res/storage/storage-account/README.md @@ -3134,6 +3134,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -3270,6 +3281,31 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Reader and Data Access'` + - `'Role Based Access Control Administrator'` + - `'Storage Account Backup Contributor'` + - `'Storage Account Contributor'` + - `'Storage Account Key Operator Service Role'` + - `'Storage Blob Data Contributor'` + - `'Storage Blob Data Owner'` + - `'Storage Blob Data Reader'` + - `'Storage Blob Delegator'` + - `'Storage File Data Privileged Contributor'` + - `'Storage File Data Privileged Reader'` + - `'Storage File Data SMB Share Contributor'` + - `'Storage File Data SMB Share Elevated Contributor'` + - `'Storage File Data SMB Share Reader'` + - `'Storage Queue Data Contributor'` + - `'Storage Queue Data Message Processor'` + - `'Storage Queue Data Message Sender'` + - `'Storage Queue Data Reader'` + - `'Storage Table Data Contributor'` + - `'Storage Table Data Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/storage/storage-account/blob-service/container/README.md b/avm/res/storage/storage-account/blob-service/container/README.md index 14d00393ef2..c66f943899b 100644 --- a/avm/res/storage/storage-account/blob-service/container/README.md +++ b/avm/res/storage/storage-account/blob-service/container/README.md @@ -144,6 +144,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Reader and Data Access'` + - `'Role Based Access Control Administrator'` + - `'Storage Account Backup Contributor'` + - `'Storage Account Contributor'` + - `'Storage Account Key Operator Service Role'` + - `'Storage Blob Data Contributor'` + - `'Storage Blob Data Owner'` + - `'Storage Blob Data Reader'` + - `'Storage Blob Delegator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/storage/storage-account/queue-service/queue/README.md b/avm/res/storage/storage-account/queue-service/queue/README.md index 508062614a6..ccfbd4b6352 100644 --- a/avm/res/storage/storage-account/queue-service/queue/README.md +++ b/avm/res/storage/storage-account/queue-service/queue/README.md @@ -64,6 +64,20 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Reader and Data Access'` + - `'Role Based Access Control Administrator'` + - `'Storage Account Backup Contributor'` + - `'Storage Account Contributor'` + - `'Storage Account Key Operator Service Role'` + - `'Storage Queue Data Contributor'` + - `'Storage Queue Data Message Processor'` + - `'Storage Queue Data Message Sender'` + - `'Storage Queue Data Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/storage/storage-account/table-service/table/README.md b/avm/res/storage/storage-account/table-service/table/README.md index dfe48226f8d..4009c666b33 100644 --- a/avm/res/storage/storage-account/table-service/table/README.md +++ b/avm/res/storage/storage-account/table-service/table/README.md @@ -55,6 +55,18 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Reader and Data Access'` + - `'Role Based Access Control Administrator'` + - `'Storage Account Backup Contributor'` + - `'Storage Account Contributor'` + - `'Storage Account Key Operator Service Role'` + - `'Storage Table Data Contributor'` + - `'Storage Table Data Reader'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/synapse/private-link-hub/README.md b/avm/res/synapse/private-link-hub/README.md index 6b406c9bd6b..9a2123c05ec 100644 --- a/avm/res/synapse/private-link-hub/README.md +++ b/avm/res/synapse/private-link-hub/README.md @@ -708,6 +708,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -819,6 +830,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/synapse/workspace/README.md b/avm/res/synapse/workspace/README.md index d9b6a461a34..45cccee622d 100644 --- a/avm/res/synapse/workspace/README.md +++ b/avm/res/synapse/workspace/README.md @@ -1651,6 +1651,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1778,6 +1789,13 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Resource Policy Contributor'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/virtual-machine-images/image-template/README.md b/avm/res/virtual-machine-images/image-template/README.md index 022c56722c0..b40afc22f9f 100644 --- a/avm/res/virtual-machine-images/image-template/README.md +++ b/avm/res/virtual-machine-images/image-template/README.md @@ -691,6 +691,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/web/connection/README.md b/avm/res/web/connection/README.md index 8c29f78a193..be084c468fb 100644 --- a/avm/res/web/connection/README.md +++ b/avm/res/web/connection/README.md @@ -460,6 +460,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/web/hosting-environment/README.md b/avm/res/web/hosting-environment/README.md index c6aa44fb425..568d1fbb7b2 100644 --- a/avm/res/web/hosting-environment/README.md +++ b/avm/res/web/hosting-environment/README.md @@ -797,6 +797,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** diff --git a/avm/res/web/serverfarm/README.md b/avm/res/web/serverfarm/README.md index 96833164175..0df60a5510e 100644 --- a/avm/res/web/serverfarm/README.md +++ b/avm/res/web/serverfarm/README.md @@ -648,6 +648,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Web Plan Contributor'` + - `'Website Contributor'` **Required parameters** diff --git a/avm/res/web/site/README.md b/avm/res/web/site/README.md index 5b72d460f57..bc9c390cb6a 100644 --- a/avm/res/web/site/README.md +++ b/avm/res/web/site/README.md @@ -3001,6 +3001,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'App Compliance Automation Administrator'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Web Plan Contributor'` + - `'Website Contributor'` **Required parameters** diff --git a/avm/res/web/site/slot/README.md b/avm/res/web/site/slot/README.md index 063962c6307..135fa084b8d 100644 --- a/avm/res/web/site/slot/README.md +++ b/avm/res/web/site/slot/README.md @@ -793,6 +793,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -936,6 +947,15 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'App Compliance Automation Administrator'` + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Web Plan Contributor'` + - `'Website Contributor'` **Required parameters** diff --git a/avm/res/web/static-site/README.md b/avm/res/web/static-site/README.md index c0b10e3875f..e77e8fe0c6e 100644 --- a/avm/res/web/static-site/README.md +++ b/avm/res/web/static-site/README.md @@ -940,6 +940,17 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'DNS Resolver Contributor'` + - `'DNS Zone Contributor'` + - `'Domain Services Contributor'` + - `'Domain Services Reader'` + - `'Network Contributor'` + - `'Owner'` + - `'Private DNS Zone Contributor'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` **Required parameters** @@ -1073,6 +1084,14 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator'` + - `'User Access Administrator'` + - `'Web Plan Contributor'` + - `'Website Contributor'` **Required parameters** diff --git a/avm/utilities/pipelines/sharedScripts/Get-NestedResourceList.ps1 b/avm/utilities/pipelines/sharedScripts/Get-NestedResourceList.ps1 index 1f5e5389f2a..df38f9eb2f0 100644 --- a/avm/utilities/pipelines/sharedScripts/Get-NestedResourceList.ps1 +++ b/avm/utilities/pipelines/sharedScripts/Get-NestedResourceList.ps1 @@ -29,13 +29,20 @@ function Get-NestedResourceList { if ($TemplateFileContent.resources -is [System.Collections.Hashtable]) { # With the introduction of user defined types, a compiled template's resources are not part of an ordered hashtable instead of an array. $currLevelResources += $TemplateFileContent.resources.Keys | ForEach-Object { - $TemplateFileContent.resources[$_] + $elem = $TemplateFileContent.resources[$_] + $elem['identifier'] = $_ + $elem } | Where-Object { $_.existing -ne $true } } else { # Default array - $currLevelResources += $TemplateFileContent.resources + $currLevelResources += $TemplateFileContent.resources | ForEach-Object { + $_['identifier'] = $_.name + $_ + } | Where-Object { + $_.existing -ne $true + } } } foreach ($resource in $currLevelResources) { diff --git a/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 b/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 index 3669c1f75aa..09355c6f9da 100644 --- a/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 +++ b/avm/utilities/pipelines/sharedScripts/Set-ModuleReadMe.ps1 @@ -390,7 +390,7 @@ function Set-DefinitionSection { ) } } else { - $formattedDefaultValue = $null + $formattedDefaultValue = $null # Reset value for future iterations } # Format allowed values @@ -420,7 +420,31 @@ function Set-DefinitionSection { ) } } else { - $formattedAllowedValues = $null + $formattedAllowedValues = $null # Reset value for future iterations + } + + # Special case for 'roleAssignments' parameter + if (($parameter.name -eq 'roleAssignments') -and ($TemplateFileContent.variables.keys -contains 'builtInRoleNames')) { + if ([String]::IsNullOrEmpty($ParentName)) { + # Top-level invocation + $roles = $TemplateFileContent.variables.builtInRoleNames.Keys + } else { + # Nested-invocation (requires e.g., roles for of nested private endpoint template) + $flattendResources = Get-NestedResourceList -TemplateFileContent $TemplateFileContent + if ($resourceIdentifier = $flattendResources.identifier | Where-Object { $_ -match "^.*_$ParentName`$" }) { + $roles = ($flattendResources | Where-Object { + $_.identifier -eq $resourceIdentifier + }).properties.template.variables.builtInRoleNames.Keys + } else { + Write-Warning ('Failed to identify roles for parameter [{0}] of type [{1}] as resource with identifier [{2}] was not found in the corresponding linked template.' -f $parameter.name, $ParentName, "*_$ParentName") + } + } + $formattedRoleNames = $roles.count -gt 0 ? @( + '- Roles configurable by name:', + ($roles | ForEach-Object { " - ``'$_'``" } | Out-String).TrimEnd() + ) : $null + } else { + $formattedRoleNames = $null # Reset value for future iterations } # Format example @@ -457,7 +481,8 @@ function Set-DefinitionSection { ('- Type: {0}' -f $type), ((-not [String]::IsNullOrEmpty($formattedDefaultValue)) ? $formattedDefaultValue : $null), ((-not [String]::IsNullOrEmpty($formattedAllowedValues)) ? $formattedAllowedValues : $null), - ((-not [String]::IsNullOrEmpty($formattedExample)) ? $formattedExample : $null) + ((-not [String]::IsNullOrEmpty($formattedRoleNames)) ? $formattedRoleNames : $null), + ((-not [String]::IsNullOrEmpty($formattedExample)) ? $formattedExample : $null), '' ) | Where-Object { $null -ne $_ } From f21032a311672f5f417348870977e63d452cd6ca Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Fri, 13 Sep 2024 17:28:44 +0200 Subject: [PATCH 19/46] feat: Pattern - Azure Image Builder - `avm/ptn/virtual-machine-images/azure-image-builder` (#3005) ## Description This PR introduces the Azure Image Builder pattern to AVM Depends on - #3006 - #3007 - #2992 - #2756 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.virtual-machine-images.azure-image-builder](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml/badge.svg?branch=users%2Falsehr%2FaibPattern)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --------- Co-authored-by: Maher Aldineh --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + ...ual-machine-images.azure-image-builder.yml | 88 + .../azure-image-builder/README.md | 1159 ++ .../azure-image-builder/main.bicep | 625 + .../azure-image-builder/main.json | 15487 ++++++++++++++++ .../src/icons/AzureComputeGalleries.svg | 181 + .../src/icons/Deployment-Script.png | Bin 0 -> 22186 bytes .../src/icons/ImageTemplates.svg | 227 + .../src/icons/Managed-identities.svg | 31 + .../src/icons/Network-Security-Groups.svg | 9 + .../src/icons/Resource-Groups.svg | 1 + .../src/icons/Storage-Accounts.svg | 22 + .../src/icons/VMImageDefinitions.svg | 196 + .../src/icons/VMImageVersions.svg | 277 + .../src/icons/Virtual-Machine-Scale-Sets.svg | 9 + .../src/icons/Virtual-Networks.svg | 1 + .../src/image/imageBuilderimage.png | Bin 0 -> 211739 bytes .../tests/e2e/defaults/main.test.bicep | 58 + .../tests/e2e/deployAll/main.test.bicep | 136 + .../scripts/Initialize-LinuxSoftware.ps1 | 538 + .../scripts/Initialize-WindowsSoftware.ps1 | 792 + .../scripts/Install-LinuxPowerShell.sh | 44 + .../scripts/Install-WindowsPowerShell.ps1 | 43 + .../dependencies.bicep | 259 + .../deployOnlyAssetsAndImage/main.test.bicep | 90 + .../scripts/exampleScript.sh | 4 + .../tests/e2e/deployOnlyBase/main.test.bicep | 59 + .../e2e/deployOnlyImage/dependencies.bicep | 303 + .../tests/e2e/deployOnlyImage/main.test.bicep | 87 + .../deployOnlyImage/scripts/exampleScript.sh | 4 + .../azure-image-builder/version.json | 7 + .../scripts/Wait-ForImageBuild.ps1 | 97 + .../helper/Invoke-ResourceRemoval.ps1 | 58 + 34 files changed, 20894 insertions(+) create mode 100644 .github/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/README.md create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/main.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/main.json create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/AzureComputeGalleries.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Deployment-Script.png create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/ImageTemplates.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Managed-identities.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Network-Security-Groups.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Resource-Groups.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Storage-Accounts.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageDefinitions.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageVersions.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Machine-Scale-Sets.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Networks.svg create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/src/image/imageBuilderimage.png create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/defaults/main.test.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/main.test.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-LinuxSoftware.ps1 create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-WindowsSoftware.ps1 create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-LinuxPowerShell.sh create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-WindowsPowerShell.ps1 create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/dependencies.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/main.test.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/scripts/exampleScript.sh create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyBase/main.test.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/dependencies.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/main.test.bicep create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/scripts/exampleScript.sh create mode 100644 avm/ptn/virtual-machine-images/azure-image-builder/version.json create mode 100644 avm/utilities/e2e-template-assets/scripts/Wait-ForImageBuild.ps1 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2bdd9e91d3b..f67f52ec145 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,6 +23,7 @@ /avm/ptn/network/private-link-private-dns-zones/ @Azure/avm-ptn-network-privatelinkprivatednszones-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/policy-insights/remediation/ @Azure/avm-ptn-policyinsights-remediation-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/ptn/security/security-center/ @Azure/avm-ptn-security-securitycenter-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/ptn/virtual-machine-images/azure-image-builder/ @Azure/avm-ptn-virtualmachineimages-azureimagebuilder-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/aad/domain-service/ @Azure/avm-res-aad-domainservice-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/alerts-management/action-rule/ @Azure/avm-res-alertsmanagement-actionrule-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/analysis-services/server/ @Azure/avm-res-analysisservices-server-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index fccb58bce5f..d30ee744be1 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -58,6 +58,7 @@ body: - "avm/ptn/network/private-link-private-dns-zones" - "avm/ptn/policy-insights/remediation" - "avm/ptn/security/security-center" + - "avm/ptn/virtual-machine-images/azure-image-builder" - "avm/res/aad/domain-service" - "avm/res/alerts-management/action-rule" - "avm/res/analysis-services/server" diff --git a/.github/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml b/.github/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml new file mode 100644 index 00000000000..445bdfa481f --- /dev/null +++ b/.github/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml @@ -0,0 +1,88 @@ +name: "avm.ptn.virtual-machine-images.azure-image-builder" + +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" + - ".github/workflows/avm.ptn.virtual-machine-images.azure-image-builder.yml" + - "avm/ptn/virtual-machine-images/azure-image-builder/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/ptn/virtual-machine-images/azure-image-builder" + workflowPath: ".github/workflows/avm.ptn.virtual-machine-images.azure-image-builder.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 diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/README.md b/avm/ptn/virtual-machine-images/azure-image-builder/README.md new file mode 100644 index 00000000000..0b1e08858bd --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/README.md @@ -0,0 +1,1159 @@ +# Custom Images using Azure Image Builder `[VirtualMachineImages/AzureImageBuilder]` + +This module provides you with a packaged solution to create custom images using the Azure Image Builder service publishing to an Azure Compute Gallery. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Notes](#Notes) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.Compute/galleries` | [2022-03-03](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Compute/2022-03-03/galleries) | +| `Microsoft.Compute/galleries/applications` | [2022-03-03](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Compute/2022-03-03/galleries/applications) | +| `Microsoft.Compute/galleries/images` | [2022-03-03](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Compute/2022-03-03/galleries/images) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.ManagedIdentity/userAssignedIdentities` | [2023-01-31](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ManagedIdentity/2023-01-31/userAssignedIdentities) | +| `Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials` | [2023-01-31](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ManagedIdentity/2023-01-31/userAssignedIdentities/federatedIdentityCredentials) | +| `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Network/virtualNetworks` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/virtualNetworks) | +| `Microsoft.Network/virtualNetworks/subnets` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/virtualNetworks/subnets) | +| `Microsoft.Network/virtualNetworks/virtualNetworkPeerings` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/virtualNetworks/virtualNetworkPeerings) | +| `Microsoft.Resources/deploymentScripts` | [2023-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/2023-08-01/deploymentScripts) | +| `Microsoft.Resources/resourceGroups` | [2024-03-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/2024-03-01/resourceGroups) | +| `Microsoft.Storage/storageAccounts` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts) | +| `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | +| `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | +| `Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers/immutabilityPolicies) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/managementPolicies` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/managementPolicies) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | +| `Microsoft.VirtualMachineImages/imageTemplates` | [2023-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.VirtualMachineImages/imageTemplates) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/ptn/virtual-machine-images/azure-image-builder:`. + +- [Using small parameter set](#example-1-using-small-parameter-set) +- [Deploying all resources](#example-2-deploying-all-resources) +- [Deploying only the assets & image](#example-3-deploying-only-the-assets-image) +- [Deploying only the base services](#example-4-deploying-only-the-base-services) +- [Deploying only the image](#example-5-deploying-only-the-image) + +### Example 1: _Using small parameter set_ + +This instance deploys the module with min features enabled. + + +

+ +via Bicep module + +```bicep +module azureImageBuilder 'br/public:avm/ptn/virtual-machine-images/azure-image-builder:' = { + name: 'azureImageBuilderDeployment' + params: { + // Required parameters + computeGalleryImageDefinitionName: '' + computeGalleryImageDefinitions: [ + { + hyperVGeneration: 'V2' + name: 'sid-linux' + offer: 'devops_linux' + osType: 'Linux' + publisher: 'devops' + sku: 'devops_linux_az' + } + ] + computeGalleryName: 'galapvmiaibmin' + imageTemplateImageSource: { + offer: 'ubuntu-24_04-lts' + publisher: 'canonical' + sku: 'server' + type: 'PlatformImage' + version: 'latest' + } + // Non-required parameters + assetsStorageAccountName: 'stapvmiaibmin' + deploymentsToPerform: '' + location: '' + resourceGroupName: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "computeGalleryImageDefinitionName": { + "value": "" + }, + "computeGalleryImageDefinitions": { + "value": [ + { + "hyperVGeneration": "V2", + "name": "sid-linux", + "offer": "devops_linux", + "osType": "Linux", + "publisher": "devops", + "sku": "devops_linux_az" + } + ] + }, + "computeGalleryName": { + "value": "galapvmiaibmin" + }, + "imageTemplateImageSource": { + "value": { + "offer": "ubuntu-24_04-lts", + "publisher": "canonical", + "sku": "server", + "type": "PlatformImage", + "version": "latest" + } + }, + // Non-required parameters + "assetsStorageAccountName": { + "value": "stapvmiaibmin" + }, + "deploymentsToPerform": { + "value": "" + }, + "location": { + "value": "" + }, + "resourceGroupName": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Deploying all resources_ + +This instance deploys the module with the conditions set up to deploy all resource and build the image. + + +

+ +via Bicep module + +```bicep +module azureImageBuilder 'br/public:avm/ptn/virtual-machine-images/azure-image-builder:' = { + name: 'azureImageBuilderDeployment' + params: { + // Required parameters + computeGalleryImageDefinitionName: '' + computeGalleryImageDefinitions: [ + { + hyperVGeneration: 'V2' + name: '' + offer: 'devops_linux' + osType: 'Linux' + publisher: 'devops' + sku: 'devops_linux_az' + } + ] + computeGalleryName: 'galapvmiaiba' + imageTemplateImageSource: { + offer: '0001-com-ubuntu-server-jammy' + publisher: 'canonical' + sku: '22_04-lts-gen2' + type: 'PlatformImage' + version: 'latest' + } + // Non-required parameters + assetsStorageAccountContainerName: '' + assetsStorageAccountName: '' + deploymentsToPerform: '' + imageTemplateCustomizationSteps: [ + { + name: 'PowerShell installation' + scriptUri: '' + type: 'Shell' + } + { + destination: '' + name: '' + sourceUri: '' + type: 'File' + } + { + inline: [ + 'pwsh \'\'' + ] + name: 'Software installation' + type: 'Shell' + } + ] + location: '' + resourceGroupName: '' + storageAccountFilesToUpload: [ + { + name: '' + value: '' + } + { + name: '' + value: '' + } + ] + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "computeGalleryImageDefinitionName": { + "value": "" + }, + "computeGalleryImageDefinitions": { + "value": [ + { + "hyperVGeneration": "V2", + "name": "", + "offer": "devops_linux", + "osType": "Linux", + "publisher": "devops", + "sku": "devops_linux_az" + } + ] + }, + "computeGalleryName": { + "value": "galapvmiaiba" + }, + "imageTemplateImageSource": { + "value": { + "offer": "0001-com-ubuntu-server-jammy", + "publisher": "canonical", + "sku": "22_04-lts-gen2", + "type": "PlatformImage", + "version": "latest" + } + }, + // Non-required parameters + "assetsStorageAccountContainerName": { + "value": "" + }, + "assetsStorageAccountName": { + "value": "" + }, + "deploymentsToPerform": { + "value": "" + }, + "imageTemplateCustomizationSteps": { + "value": [ + { + "name": "PowerShell installation", + "scriptUri": "", + "type": "Shell" + }, + { + "destination": "", + "name": "", + "sourceUri": "", + "type": "File" + }, + { + "inline": [ + "pwsh \"\"" + ], + "name": "Software installation", + "type": "Shell" + } + ] + }, + "location": { + "value": "" + }, + "resourceGroupName": { + "value": "" + }, + "storageAccountFilesToUpload": { + "value": [ + { + "name": "", + "value": "" + }, + { + "name": "", + "value": "" + } + ] + } + } +} +``` + +
+

+ +### Example 3: _Deploying only the assets & image_ + +This instance deploys the module with the conditions set up to only update the assets on the assets storage account and build the image, assuming all dependencies are setup. + + +

+ +via Bicep module + +```bicep +module azureImageBuilder 'br/public:avm/ptn/virtual-machine-images/azure-image-builder:' = { + name: 'azureImageBuilderDeployment' + params: { + // Required parameters + computeGalleryImageDefinitionName: '' + computeGalleryImageDefinitions: '' + computeGalleryName: '' + imageTemplateImageSource: { + offer: 'ubuntu-24_04-lts' + publisher: 'canonical' + sku: 'server' + type: 'PlatformImage' + version: 'latest' + } + // Non-required parameters + assetsStorageAccountContainerName: '' + assetsStorageAccountName: '' + deploymentScriptManagedIdentityName: '' + deploymentScriptStorageAccountName: '' + deploymentScriptSubnetName: '' + deploymentsToPerform: 'Only assets & image' + imageManagedIdentityName: '' + imageSubnetName: '' + imageTemplateCustomizationSteps: [ + { + name: 'Example script' + scriptUri: '' + type: 'Shell' + } + ] + imageTemplateResourceGroupName: '' + location: '' + resourceGroupName: '' + storageAccountFilesToUpload: [ + { + name: '' + value: '' + } + ] + virtualNetworkName: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "computeGalleryImageDefinitionName": { + "value": "" + }, + "computeGalleryImageDefinitions": { + "value": "" + }, + "computeGalleryName": { + "value": "" + }, + "imageTemplateImageSource": { + "value": { + "offer": "ubuntu-24_04-lts", + "publisher": "canonical", + "sku": "server", + "type": "PlatformImage", + "version": "latest" + } + }, + // Non-required parameters + "assetsStorageAccountContainerName": { + "value": "" + }, + "assetsStorageAccountName": { + "value": "" + }, + "deploymentScriptManagedIdentityName": { + "value": "" + }, + "deploymentScriptStorageAccountName": { + "value": "" + }, + "deploymentScriptSubnetName": { + "value": "" + }, + "deploymentsToPerform": { + "value": "Only assets & image" + }, + "imageManagedIdentityName": { + "value": "" + }, + "imageSubnetName": { + "value": "" + }, + "imageTemplateCustomizationSteps": { + "value": [ + { + "name": "Example script", + "scriptUri": "", + "type": "Shell" + } + ] + }, + "imageTemplateResourceGroupName": { + "value": "" + }, + "location": { + "value": "" + }, + "resourceGroupName": { + "value": "" + }, + "storageAccountFilesToUpload": { + "value": [ + { + "name": "", + "value": "" + } + ] + }, + "virtualNetworkName": { + "value": "" + } + } +} +``` + +
+

+ +### Example 4: _Deploying only the base services_ + +This instance deploys the module with the conditions set up to only deploy the base resources, that is everything but the image. + + +

+ +via Bicep module + +```bicep +module azureImageBuilder 'br/public:avm/ptn/virtual-machine-images/azure-image-builder:' = { + name: 'azureImageBuilderDeployment' + params: { + // Required parameters + computeGalleryImageDefinitionName: '' + computeGalleryImageDefinitions: [ + { + hyperVGeneration: 'V2' + name: '' + offer: 'devops_linux' + osType: 'Linux' + publisher: 'devops' + sku: 'devops_linux_az' + } + ] + computeGalleryName: 'galapvmiaibob' + imageTemplateImageSource: { + offer: 'ubuntu-24_04-lts' + publisher: 'canonical' + sku: 'server' + type: 'PlatformImage' + version: 'latest' + } + // Non-required parameters + assetsStorageAccountName: 'stapvmiaibob' + deploymentsToPerform: 'Only base' + imageManagedIdentityName: 'msi-it-apvmiaibob' + location: '' + resourceGroupName: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "computeGalleryImageDefinitionName": { + "value": "" + }, + "computeGalleryImageDefinitions": { + "value": [ + { + "hyperVGeneration": "V2", + "name": "", + "offer": "devops_linux", + "osType": "Linux", + "publisher": "devops", + "sku": "devops_linux_az" + } + ] + }, + "computeGalleryName": { + "value": "galapvmiaibob" + }, + "imageTemplateImageSource": { + "value": { + "offer": "ubuntu-24_04-lts", + "publisher": "canonical", + "sku": "server", + "type": "PlatformImage", + "version": "latest" + } + }, + // Non-required parameters + "assetsStorageAccountName": { + "value": "stapvmiaibob" + }, + "deploymentsToPerform": { + "value": "Only base" + }, + "imageManagedIdentityName": { + "value": "msi-it-apvmiaibob" + }, + "location": { + "value": "" + }, + "resourceGroupName": { + "value": "" + } + } +} +``` + +
+

+ +### Example 5: _Deploying only the image_ + +This instance deploys the module with the conditions set up to only deploy and bake the image, assuming all dependencies are setup. + + +

+ +via Bicep module + +```bicep +module azureImageBuilder 'br/public:avm/ptn/virtual-machine-images/azure-image-builder:' = { + name: 'azureImageBuilderDeployment' + params: { + // Required parameters + computeGalleryImageDefinitionName: '' + computeGalleryImageDefinitions: '' + computeGalleryName: '' + imageTemplateImageSource: { + offer: 'ubuntu-24_04-lts' + publisher: 'canonical' + sku: 'server' + type: 'PlatformImage' + version: 'latest' + } + // Non-required parameters + deploymentScriptManagedIdentityName: '' + deploymentScriptStorageAccountName: '' + deploymentScriptSubnetName: '' + deploymentsToPerform: 'Only image' + imageManagedIdentityName: '' + imageSubnetName: '' + imageTemplateCustomizationSteps: [ + { + name: 'Example script' + scriptUri: '' + type: 'Shell' + } + ] + imageTemplateResourceGroupName: '' + location: '' + resourceGroupName: '' + virtualNetworkName: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "computeGalleryImageDefinitionName": { + "value": "" + }, + "computeGalleryImageDefinitions": { + "value": "" + }, + "computeGalleryName": { + "value": "" + }, + "imageTemplateImageSource": { + "value": { + "offer": "ubuntu-24_04-lts", + "publisher": "canonical", + "sku": "server", + "type": "PlatformImage", + "version": "latest" + } + }, + // Non-required parameters + "deploymentScriptManagedIdentityName": { + "value": "" + }, + "deploymentScriptStorageAccountName": { + "value": "" + }, + "deploymentScriptSubnetName": { + "value": "" + }, + "deploymentsToPerform": { + "value": "Only image" + }, + "imageManagedIdentityName": { + "value": "" + }, + "imageSubnetName": { + "value": "" + }, + "imageTemplateCustomizationSteps": { + "value": [ + { + "name": "Example script", + "scriptUri": "", + "type": "Shell" + } + ] + }, + "imageTemplateResourceGroupName": { + "value": "" + }, + "location": { + "value": "" + }, + "resourceGroupName": { + "value": "" + }, + "virtualNetworkName": { + "value": "" + } + } +} +``` + +
+

+ +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`computeGalleryImageDefinitionName`](#parameter-computegalleryimagedefinitionname) | string | The name of Image Definition of the Azure Compute Gallery to host the new image version. | +| [`computeGalleryImageDefinitions`](#parameter-computegalleryimagedefinitions) | array | The Image Definitions in the Azure Compute Gallery. | +| [`computeGalleryName`](#parameter-computegalleryname) | string | The name of the Azure Compute Gallery. | +| [`imageTemplateImageSource`](#parameter-imagetemplateimagesource) | object | The image source to use for the Image Template. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`assetsStorageAccountContainerName`](#parameter-assetsstorageaccountcontainername) | string | The name of container in the Storage Account. | +| [`assetsStorageAccountName`](#parameter-assetsstorageaccountname) | string | The name of the storage account. Only needed if you want to upload scripts to be used during image baking. | +| [`deploymentScriptManagedIdentityName`](#parameter-deploymentscriptmanagedidentityname) | string | The name of the Managed Identity used by deployment scripts. | +| [`deploymentScriptStorageAccountName`](#parameter-deploymentscriptstorageaccountname) | string | The name of the storage account. | +| [`deploymentScriptSubnetName`](#parameter-deploymentscriptsubnetname) | string | The name of the Image Template Virtual Network Subnet to create. | +| [`deploymentsToPerform`](#parameter-deploymentstoperform) | string | A parameter to control which deployments should be executed. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`imageManagedIdentityName`](#parameter-imagemanagedidentityname) | string | The name of the Managed Identity used by the Azure Image Builder. | +| [`imageSubnetName`](#parameter-imagesubnetname) | string | The name of the Image Template Virtual Network Subnet to create. | +| [`imageTemplateCustomizationSteps`](#parameter-imagetemplatecustomizationsteps) | array | The customization steps to use for the Image Template. | +| [`imageTemplateDeploymentScriptName`](#parameter-imagetemplatedeploymentscriptname) | string | The name of the Deployment Script to trigger the image template baking. | +| [`imageTemplateName`](#parameter-imagetemplatename) | string | The name of the Image Template. | +| [`imageTemplateResourceGroupName`](#parameter-imagetemplateresourcegroupname) | string | The name of the Resource Group to deploy the Image Template resources into. | +| [`location`](#parameter-location) | string | The location to deploy into. | +| [`resourceGroupName`](#parameter-resourcegroupname) | string | The name of the Resource Group. | +| [`storageAccountFilesToUpload`](#parameter-storageaccountfilestoupload) | array | The files to upload to the Assets Storage Account. | +| [`storageDeploymentScriptName`](#parameter-storagedeploymentscriptname) | string | The name of the Deployment Script to upload files to the assets storage account. | +| [`virtualNetworkAddressPrefix`](#parameter-virtualnetworkaddressprefix) | string | The address space of the Virtual Network. | +| [`virtualNetworkDeploymentScriptSubnetAddressPrefix`](#parameter-virtualnetworkdeploymentscriptsubnetaddressprefix) | string | The address space of the Virtual Network Subnet used by the deployment script. | +| [`virtualNetworkName`](#parameter-virtualnetworkname) | string | The name of the Virtual Network. | +| [`virtualNetworkSubnetAddressPrefix`](#parameter-virtualnetworksubnetaddressprefix) | string | The address space of the Virtual Network Subnet. | +| [`waitDeploymentScriptName`](#parameter-waitdeploymentscriptname) | string | The name of the Deployment Script to wait for for the image baking to conclude. | +| [`waitForImageBuild`](#parameter-waitforimagebuild) | bool | A parameter to control if the deployment should wait for the image build to complete. | +| [`waitForImageBuildTimeout`](#parameter-waitforimagebuildtimeout) | string | A parameter to control the timeout of the deployment script waiting for the image build. | + +**Generated parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`baseTime`](#parameter-basetime) | string | Do not provide a value! This date value is used to generate a SAS token to access the modules. | + +### Parameter: `computeGalleryImageDefinitionName` + +The name of Image Definition of the Azure Compute Gallery to host the new image version. + +- Required: Yes +- Type: string + +### Parameter: `computeGalleryImageDefinitions` + +The Image Definitions in the Azure Compute Gallery. + +- Required: Yes +- Type: array + +### Parameter: `computeGalleryName` + +The name of the Azure Compute Gallery. + +- Required: Yes +- Type: string + +### Parameter: `imageTemplateImageSource` + +The image source to use for the Image Template. + +- Required: Yes +- Type: object + +### Parameter: `assetsStorageAccountContainerName` + +The name of container in the Storage Account. + +- Required: No +- Type: string +- Default: `'aibscripts'` + +### Parameter: `assetsStorageAccountName` + +The name of the storage account. Only needed if you want to upload scripts to be used during image baking. + +- Required: No +- Type: string + +### Parameter: `deploymentScriptManagedIdentityName` + +The name of the Managed Identity used by deployment scripts. + +- Required: No +- Type: string +- Default: `'msi-ds'` + +### Parameter: `deploymentScriptStorageAccountName` + +The name of the storage account. + +- Required: No +- Type: string +- Default: `[format('{0}ds', parameters('assetsStorageAccountName'))]` + +### Parameter: `deploymentScriptSubnetName` + +The name of the Image Template Virtual Network Subnet to create. + +- Required: No +- Type: string +- Default: `'subnet-ds'` + +### Parameter: `deploymentsToPerform` + +A parameter to control which deployments should be executed. + +- Required: No +- Type: string +- Default: `'Only assets & image'` +- Allowed: + ```Bicep + [ + 'All' + 'Only assets & image' + 'Only base' + 'Only image' + ] + ``` + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `imageManagedIdentityName` + +The name of the Managed Identity used by the Azure Image Builder. + +- Required: No +- Type: string +- Default: `'msi-aib'` + +### Parameter: `imageSubnetName` + +The name of the Image Template Virtual Network Subnet to create. + +- Required: No +- Type: string +- Default: `'subnet-it'` + +### Parameter: `imageTemplateCustomizationSteps` + +The customization steps to use for the Image Template. + +- Required: No +- Type: array + +### Parameter: `imageTemplateDeploymentScriptName` + +The name of the Deployment Script to trigger the image template baking. + +- Required: No +- Type: string +- Default: `'ds-triggerBuild-imageTemplate'` + +### Parameter: `imageTemplateName` + +The name of the Image Template. + +- Required: No +- Type: string +- Default: `'it-aib'` + +### Parameter: `imageTemplateResourceGroupName` + +The name of the Resource Group to deploy the Image Template resources into. + +- Required: No +- Type: string +- Default: `[format('{0}-image-build', parameters('resourceGroupName'))]` + +### Parameter: `location` + +The location to deploy into. + +- Required: No +- Type: string +- Default: `[deployment().location]` + +### Parameter: `resourceGroupName` + +The name of the Resource Group. + +- Required: No +- Type: string +- Default: `'rg-ado-agents'` + +### Parameter: `storageAccountFilesToUpload` + +The files to upload to the Assets Storage Account. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`name`](#parameter-storageaccountfilestouploadname) | string | The name of the environment variable. | +| [`secureValue`](#parameter-storageaccountfilestouploadsecurevalue) | securestring | The value of the secure environment variable. | +| [`value`](#parameter-storageaccountfilestouploadvalue) | string | The value of the environment variable. | + +### Parameter: `storageAccountFilesToUpload.name` + +The name of the environment variable. + +- Required: Yes +- Type: string + +### Parameter: `storageAccountFilesToUpload.secureValue` + +The value of the secure environment variable. + +- Required: No +- Type: securestring + +### Parameter: `storageAccountFilesToUpload.value` + +The value of the environment variable. + +- Required: No +- Type: string + +### Parameter: `storageDeploymentScriptName` + +The name of the Deployment Script to upload files to the assets storage account. + +- Required: No +- Type: string +- Default: `'ds-triggerUpload-storage'` + +### Parameter: `virtualNetworkAddressPrefix` + +The address space of the Virtual Network. + +- Required: No +- Type: string +- Default: `'10.0.0.0/16'` + +### Parameter: `virtualNetworkDeploymentScriptSubnetAddressPrefix` + +The address space of the Virtual Network Subnet used by the deployment script. + +- Required: No +- Type: string +- Default: `[cidrSubnet(parameters('virtualNetworkAddressPrefix'), 24, 1)]` + +### Parameter: `virtualNetworkName` + +The name of the Virtual Network. + +- Required: No +- Type: string +- Default: `'vnet-it'` + +### Parameter: `virtualNetworkSubnetAddressPrefix` + +The address space of the Virtual Network Subnet. + +- Required: No +- Type: string +- Default: `[cidrSubnet(parameters('virtualNetworkAddressPrefix'), 24, 0)]` + +### Parameter: `waitDeploymentScriptName` + +The name of the Deployment Script to wait for for the image baking to conclude. + +- Required: No +- Type: string +- Default: `'ds-wait-imageTemplate-build'` + +### Parameter: `waitForImageBuild` + +A parameter to control if the deployment should wait for the image build to complete. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `waitForImageBuildTimeout` + +A parameter to control the timeout of the deployment script waiting for the image build. + +- Required: No +- Type: string +- Default: `'PT1H'` + +### Parameter: `baseTime` + +Do not provide a value! This date value is used to generate a SAS token to access the modules. + +- Required: No +- Type: string +- Default: `[utcNow()]` + +## Outputs + +_None_ + +## Cross-referenced modules + +This section gives you an overview of all local-referenced module files (i.e., other modules that are referenced in this module) and all remote-referenced files (i.e., Bicep modules that are referenced from a Bicep Registry or Template Specs). + +| Reference | Type | +| :-- | :-- | +| `br/public:avm/res/compute/gallery:0.4.0` | Remote reference | +| `br/public:avm/res/managed-identity/user-assigned-identity:0.2.2` | Remote reference | +| `br/public:avm/res/network/virtual-network:0.1.6` | Remote reference | +| `br/public:avm/res/resources/deployment-script:0.3.1` | Remote reference | +| `br/public:avm/res/storage/storage-account:0.9.1` | Remote reference | +| `br/public:avm/res/virtual-machine-images/image-template:0.3.1` | Remote reference | + +## Notes + + +### Prerequisites + +The deployments described in the following sections assume certain prerequisites to be in place prior to deployment. + +- The deployment principal (e.g., the Service Principal tied to the deploying Service Connection) must have at least `Contributor` & `User Access Adminitrator` permissions on the target subscription to be able to deploy both resources and assign permissions to created user-assigned identities +- If you have a policy in place that prevents Storage Accounts from being deployed without a Firewall, you have to create an exemption for the Image Template / Staging Resource Group you can configure for the Image Template Resource (parameter `imageTemplateResourceGroupName`). The rationale is that the Azure-Image-Builder service uses this resource group to deploy both temporal resources used during the image build (e.g., a Virtual Machine), as well as a Storage Account to store temporal files & a 'packerlogs/customization.log' file in (which contains the logs of the image build). This Storage Account has no firewall configured, has a random name, and cannot be configured at deploy time. + +### Elements +The image creation uses several components: + +|     | Resource | Description | +|--|--|--| +| ResourceGroup | Resource Group | The resource group hosting the image resources | +| ResourceGroup | (Image) Resource Group | The resource group hosting the resources created during the image build | +| Storage Account | (Assets) Storage Account | The storage account that hosts the image customization scripts used by the _Azure Image Building_ when executing the image template. | +| Storage Account | (DS) Storage Account | The storage account that hosts the files of the Deployment Scripts. Required for private networking. | +| Managed Identity | (Image) User-Assigned Managed Identity | Azure Active Directory feature that eliminates the need for credentials in code, rotates credentials automatically, and reduces identity maintenance. In the context of the imaging construct, the managed identity (MSI) is used by the Image Builder Service. It is assigned contributor permissions on the subscription to be able to bake the image. Further, it is assigned read permissions on the Assets Storage Account Container in order to consume the customization scripts. | +| Managed Identity | (DS) User-Assigned Managed Identity | Azure Active Directory feature that eliminates the need for credentials in code, rotates credentials automatically, and reduces identity maintenance. In the context of the imaging construct, the managed identity (MSI) is used by the Image Builder Service. It's assigned permissions on the Image Template to trigger it, the Deployment Script Storage Account for Private Networking, and the Assets Storage Account to upload files. | +| Managed Identity | (Storage) Deployment Script | The Deployment Script that uploads the customization scripts to the Assets Storage Account. | +| Managed Identity | (Trigger) Deployment Script | The Deployment Script that triggers the Image Template build. | +| Azure Compute Gallery | Azure Compute Gallery | Azure service that helps to build structure and organization for managed images. Provides global replication, versioning, grouping, sharing across subscriptions and scaling. The plain resource in itself is like an empty container. | +| Azure Compute Gallery Image | Azure Compute Gallery Image | Created within a gallery and contains information about the image and requirements for using it internally. This includes metadata like whether the image is Windows or Linux, release notes and recommended compute resources. Like the image gallery itself it acts like a container for the actual images. | +| Image Template | Image Template | A standard Azure Image Builder template that defines the parameters for building a custom image with AIB. The parameters include image source (Marketplace, custom image, etc.), customization options (i.e., Updates, scripts, restarts), and distribution (i.e., managed image, Azure Compute Gallery). The template is not an actual resource. Instead, when an image template is created, Azure stores all the metadata of the referenced Azure Compute Gallery Image alongside other image backing instructions as a hidden resource in a temporary resource group. | +| Image Version | Image Version | An image version (for example `0.24322.55884`) is what you use to create a VM when using a gallery. You can have multiple versions of an image as needed for your environment. This value **cannot** be chosen. | + +

+ +Run workflow + +### First deployment +When triggering the deployment for the first time, make sure you either select `All` or `Only base` for the `deploymentsToPerform` parameter. In either case the template will deploy all resources and scripts you will subsequently need to create the images. For any subsequent run, you can go with any option you need. + +The steps the _Azure Image Builder_ performs on the image are defined by elements configured in the `customizationSteps` parameter of the image template parameter file. In our setup we [Usage Examples](#usage-examples) we use one or multiple custom scripts that are uploaded by the template to a storage account ahead of the image deployment. + +### Mermaid Graphs + +The following graphs show which services are created based on the chosen `deploymentsToPerform`. As such, they show a (simplified) view of the order and relations in between the included deployments. + +#### (Simplified) All +```mermaid + graph TD; + imageTemplateRg --> imageTemplate + rg --> vnet + rg --> dsMsi + rg --> imageMSI + rg --> azureComputeGallery + + azureComputeGallery --> imageTemplate + + imageMSI --> imageMSI_rbac + + dsMsi --> assetsStorageAccount + imageMSI --> assetsStorageAccount + + dsMsi --> dsStorageAccount + vnet --> dsStorageAccount + + dsStorageAccount --> storageAccount_upload + assetsStorageAccount --> storageAccount_upload + dsMsi --> imageTemplate + storageAccount_upload ==> imageTemplate + + imageTemplate --> imageTemplate_trigger + + imageTemplate_trigger ==> imageTemplate_wait + imageMSI_rbac ==> imageTemplate +``` + + + +#### (Simplified) Only base +```mermaid + graph TD; + rg -- provides value to --> azureComputeGallery + rg -- provides value to --> vnet + rg -- provides value to --> dsMsi + rg -- provides value to --> imageMSI + + imageTemplateRg + + vnet -- provides value to --> dsStorageAccount + + dsMsi -- provides value to --> dsStorageAccount + dsStorageAccount -- provides value to --> storageAccount_upload + assetsStorageAccount -- provides value to --> storageAccount_upload + + dsMsi -- provides value to --> assetsStorageAccount + imageMSI -- provides value to --> assetsStorageAccount + imageMSI -- provides value to --> imageMSI_rbac +``` + +#### Only assets & image +Assumes all other services + permissions are deployed +```mermaid + graph TD; + imageTemplateRg -- provides value to --> imageTemplate + storageAccount_upload -- must come after --> imageTemplate + imageTemplate -- provides value to --> imageTemplate_trigger + imageTemplate -- provides value to --> imageTemplate_wait + imageTemplate_trigger -- must come after --> imageTemplate_wait +``` + +#### Only image +Assumes all other services + permissions are deployed +```mermaid + graph TD; + imageTemplateRg -- provides value to --> imageTemplate + imageTemplate -- provides value to --> imageTemplate_trigger + imageTemplate -- provides value to --> imageTemplate_wait + imageTemplate_trigger -- must come after --> imageTemplate_wait +``` + +### Troubleshooting + +Most commonly issues with the construct occur during the image building process due to script errors. As those are hard to troubleshoot and the AIB VMs that are used to bake images are not accessible, the AIB service writes logs into a storage account in the 'staging' resource group it generates during the building process as documented [here](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/image-builder-troubleshoot#customization-log). + +Aside from the packer logs, it will also contain the logs generated by the provided customization scripts and hence provide you insights into 'where' something wrong, and ideally also 'what' went wrong. + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/main.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/main.bicep new file mode 100644 index 00000000000..41c05593b8e --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/main.bicep @@ -0,0 +1,625 @@ +targetScope = 'subscription' + +metadata name = 'Custom Images using Azure Image Builder' +metadata description = 'This module provides you with a packaged solution to create custom images using the Azure Image Builder service publishing to an Azure Compute Gallery.' +metadata owner = 'AlexanderSehr' + +// ================ // +// Input Parameters // +// ================ // + +@description('Optional. A parameter to control which deployments should be executed.') +@allowed([ + 'All' + 'Only base' + 'Only assets & image' + 'Only image' +]) +param deploymentsToPerform string = 'Only assets & image' + +// Resource Group Parameters +@description('Optional. The name of the Resource Group.') +param resourceGroupName string = 'rg-ado-agents' + +@description('Optional. The name of the Resource Group to deploy the Image Template resources into.') +param imageTemplateResourceGroupName string = '${resourceGroupName}-image-build' + +// User Assigned Identity (MSI) Parameters +@description('Optional. The name of the Managed Identity used by deployment scripts.') +param deploymentScriptManagedIdentityName string = 'msi-ds' + +@description('Optional. The name of the Managed Identity used by the Azure Image Builder.') +param imageManagedIdentityName string = 'msi-aib' + +// Azure Compute Gallery Parameters +@description('Required. The name of the Azure Compute Gallery.') +param computeGalleryName string + +@description('Required. The Image Definitions in the Azure Compute Gallery.') +param computeGalleryImageDefinitions array + +// Storage Account Parameters +@description('Optional. The name of the storage account. Only needed if you want to upload scripts to be used during image baking.') +param assetsStorageAccountName string? + +@description('Optional. The name of the storage account.') +param deploymentScriptStorageAccountName string = '${assetsStorageAccountName}ds' + +@description('Optional. The name of container in the Storage Account.') +param assetsStorageAccountContainerName string = 'aibscripts' + +// Virtual Network Parameters +@description('Optional. The name of the Virtual Network.') +param virtualNetworkName string = 'vnet-it' + +@description('Optional. The address space of the Virtual Network.') +param virtualNetworkAddressPrefix string = '10.0.0.0/16' + +@description('Optional. The name of the Image Template Virtual Network Subnet to create.') +param imageSubnetName string = 'subnet-it' + +@description('Optional. The address space of the Virtual Network Subnet.') +param virtualNetworkSubnetAddressPrefix string = cidrSubnet(virtualNetworkAddressPrefix, 24, 0) + +@description('Optional. The name of the Image Template Virtual Network Subnet to create.') +param deploymentScriptSubnetName string = 'subnet-ds' + +@description('Optional. The address space of the Virtual Network Subnet used by the deployment script.') +param virtualNetworkDeploymentScriptSubnetAddressPrefix string = cidrSubnet(virtualNetworkAddressPrefix, 24, 1) + +// Deployment Script Parameters +@description('Optional. The name of the Deployment Script to upload files to the assets storage account.') +param storageDeploymentScriptName string = 'ds-triggerUpload-storage' + +@description('Optional. The files to upload to the Assets Storage Account.') +param storageAccountFilesToUpload storageAccountFilesToUploadType[]? + +@description('Optional. The name of the Deployment Script to trigger the image template baking.') +param imageTemplateDeploymentScriptName string = 'ds-triggerBuild-imageTemplate' + +@description('Optional. The name of the Deployment Script to wait for for the image baking to conclude.') +param waitDeploymentScriptName string = 'ds-wait-imageTemplate-build' + +// Image Template Parameters +@description('Optional. The name of the Image Template.') +param imageTemplateName string = 'it-aib' + +@description('Required. The image source to use for the Image Template.') +param imageTemplateImageSource object + +@description('Optional. The customization steps to use for the Image Template.') +@minLength(1) +param imageTemplateCustomizationSteps array? + +@description('Required. The name of Image Definition of the Azure Compute Gallery to host the new image version.') +param computeGalleryImageDefinitionName string + +@description('Optional. A parameter to control if the deployment should wait for the image build to complete.') +param waitForImageBuild bool = true + +@description('Optional. A parameter to control the timeout of the deployment script waiting for the image build.') +param waitForImageBuildTimeout string = 'PT1H' + +// Shared Parameters +@description('Optional. The location to deploy into.') +param location string = deployment().location + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Generated. Do not provide a value! This date value is used to generate a SAS token to access the modules.') +param baseTime string = utcNow() + +var formattedTime = replace(replace(replace(baseTime, ':', ''), '-', ''), ' ', '') + +// Role required for deployment script to be able to use a storage account via private networking +resource storageFileDataPrivilegedContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Priveleged Contributor + scope: tenant() +} +resource contributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor + scope: tenant() +} + +// =========== // +// Deployments // +// =========== // + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.ptn.vmimages-azureimagebuilder.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + location: location + 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' + } + } + } + } +} + +////////////////////////// +// START: ALL // +// START: ONLY BASE // +// ==================== // + +// Resource Groups +resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: resourceGroupName + location: location +} + +// Always deployed as both an infra element & needed as a staging resource group for image building +resource imageTemplateRg 'Microsoft.Resources/resourceGroups@2024-03-01' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: imageTemplateResourceGroupName + location: location +} + +// User Assigned Identity (MSI) +module dsMsi 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.2' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: '${deployment().name}-ds-msi' + scope: rg + params: { + name: deploymentScriptManagedIdentityName + location: location + enableTelemetry: enableTelemetry + } +} + +module imageMSI 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.2' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: '${deployment().name}-image-msi' + scope: rg + params: { + name: imageManagedIdentityName + location: location + enableTelemetry: enableTelemetry + } +} + +// MSI Subscription contributor assignment +resource imageMSI_rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + // name: guid(subscription().subscriptionId, imageManagedIdentityName, contributorRole.id) + name: guid( + subscription().id, + '${subscription().id}/resourceGroups/${resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${imageManagedIdentityName}', + contributorRole.id + ) + properties: { + // TODO: Requries conditions. Tracked issue: https://github.com/Azure/bicep/issues/2371 + principalId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') + ? imageMSI.outputs.principalId + : '' + roleDefinitionId: contributorRole.id + principalType: 'ServicePrincipal' + } +} + +// Azure Compute Gallery +module azureComputeGallery 'br/public:avm/res/compute/gallery:0.4.0' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: '${deployment().name}-acg' + scope: rg + params: { + name: computeGalleryName + images: computeGalleryImageDefinitions + location: location + enableTelemetry: enableTelemetry + } +} + +// Image Template Virtual Network +module vnet 'br/public:avm/res/network/virtual-network:0.1.6' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: '${deployment().name}-vnet' + scope: rg + params: { + name: virtualNetworkName + addressPrefixes: [ + virtualNetworkAddressPrefix + ] + subnets: [ + { + name: imageSubnetName + addressPrefix: virtualNetworkSubnetAddressPrefix + privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + } + { + name: deploymentScriptSubnetName + addressPrefix: virtualNetworkDeploymentScriptSubnetAddressPrefix + privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET - temp + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + delegations: [ + { + name: 'Microsoft.ContainerInstance.containerGroups' + properties: { + serviceName: 'Microsoft.ContainerInstance/containerGroups' + } + } + ] + } + ] + location: location + enableTelemetry: enableTelemetry + } +} + +// Assets Storage Account +module assetsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: '${deployment().name}-files-sa' + scope: rg + params: { + name: assetsStorageAccountName! + allowSharedKeyAccess: false // Keys not needed if MSI is granted access + enableTelemetry: enableTelemetry + location: location + networkAcls: { + // NOTE: If Firewall is enabled, it causes the Image Template to not be able to connect to the storage account. It's NOT a permission issue (ref: https://github.com/danielsollondon/azvmimagebuilder/issues/31#issuecomment-1793779854) + defaultAction: 'Allow' + // defaultAction: 'Deny' + // virtualNetworkRules: [ + // { + // // Allow image template to access data + // action: 'Allow' + // id: vnet.outputs.subnetResourceIds[0] // imageSubnet + // } + // { + // // Allow deployment script to access storage account to upload data + // action: 'Allow' + // id: vnet.outputs.subnetResourceIds[1] // deploymentScriptSubnet + // } + // ] + } + blobServices: { + containers: [ + { + name: assetsStorageAccountContainerName + publicAccess: 'None' + roleAssignments: [ + { + // Allow Infra MSI to access storage account container to upload files - DO NOT REMOVE + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') + ? dsMsi.outputs.principalId + : '' // Requires condition als Bicep will otherwise try to resolve the null reference + principalType: 'ServicePrincipal' + } + { + // Allow image MSI to access storage account container to read files - DO NOT REMOVE + roleDefinitionIdOrName: 'Storage Blob Data Reader' // 'Storage Blob Data Reader' + principalId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') + ? imageMSI.outputs.principalId + : '' // Requires condition als Bicep will otherwise try to resolve the null reference + principalType: 'ServicePrincipal' + } + ] + } + ] + containerDeleteRetentionPolicyEnabled: true + containerDeleteRetentionPolicyDays: 10 + } + } +} + +// Deployment scripts & their storage account +module dsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') { + name: '${deployment().name}-ds-sa' + scope: rg + params: { + name: deploymentScriptStorageAccountName + allowSharedKeyAccess: true // May not be disabled to allow deployment script to access storage account files + enableTelemetry: enableTelemetry + roleAssignments: [ + { + // Allow MSI to leverage the storage account for private networking of container instance + // ref: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-bicep#access-private-virtual-network + roleDefinitionIdOrName: storageFileDataPrivilegedContributorRole.id // Storage File Data Priveleged Contributor + principalId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base') + ? dsMsi.outputs.principalId + : '' // Requires condition als Bicep will otherwise try to resolve the null reference + principalType: 'ServicePrincipal' + } + ] + location: location + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ + { + // Allow deployment script to use storage account for private networking of container instance + action: 'Allow' + id: resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Network/virtualNetworks/subnets', + virtualNetworkName, + deploymentScriptSubnetName + ) + } + ] + } + } + dependsOn: [ + vnet + ] +} + +//////////////////////////////////// +// START: ONLY ASSETS & IMAGE // +// ============================== // + +// Upload storage account files +module storageAccount_upload 'br/public:avm/res/resources/deployment-script:0.3.1' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only base' || deploymentsToPerform == 'Only assets & image') { + name: '${deployment().name}-storage-upload-ds' + scope: resourceGroup(resourceGroupName) + params: { + name: '${storageDeploymentScriptName}-${formattedTime}' + kind: 'AzurePowerShell' + azPowerShellVersion: '12.0' + enableTelemetry: enableTelemetry + managedIdentities: { + userAssignedResourcesIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.ManagedIdentity/userAssignedIdentities', + deploymentScriptManagedIdentityName + ) + ] + } + scriptContent: loadTextContent('../../../utilities/e2e-template-assets/scripts/Set-StorageContainerContentByEnvVar.ps1') + // environmentVariables: [ + // map(range(0, length(storageAccountFilesToUpload ?? [])), index => { + // name: '__SCRIPT__${storageAccountFilesToUpload![index].name}' + // value: storageAccountFilesToUpload![index].?value + // secureValue: storageAccountFilesToUpload![index].?secureValue + // }) + // ] + environmentVariables: map(storageAccountFilesToUpload ?? [], file => { + name: '__SCRIPT__${replace(replace(file.name, '-', '__'), '.', '_') }' // May only be alphanumeric characters & underscores. The upload will replace '_' with '.' and '__' with '-'. E.g., Install__LinuxPowerShell_sh will be Install-LinuxPowerShell.sh + value: file.?value + secureValue: file.?secureValue + }) + arguments: ' -StorageAccountName "${assetsStorageAccountName}" -TargetContainer "${assetsStorageAccountContainerName}"' + timeout: 'PT30M' + cleanupPreference: 'Always' + location: location + storageAccountResourceId: resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Storage/storageAccounts', + deploymentScriptStorageAccountName + ) + subnetResourceIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Network/virtualNetworks/subnets', + virtualNetworkName, + deploymentScriptSubnetName + ) + ] + } + dependsOn: [ + // Conditionally required + rg + assetsStorageAccount + dsMsi + dsStorageAccount + vnet + ] +} + +// ================== // +// END: ONLY BASE // +//////////////////////// + +/////////////////////////// +// START: ONLY IMAGE // +// ===================== // + +// Image template +resource dsMsi_existing 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image') { + name: deploymentScriptManagedIdentityName + scope: resourceGroup(resourceGroupName) +} + +module imageTemplate 'br/public:avm/res/virtual-machine-images/image-template:0.3.1' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image') { + name: '${deployment().name}-it' + scope: resourceGroup(resourceGroupName) + params: { + customizationSteps: imageTemplateCustomizationSteps + imageSource: imageTemplateImageSource + name: imageTemplateName + enableTelemetry: enableTelemetry + managedIdentities: { + userAssignedResourceIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.ManagedIdentity/userAssignedIdentities', + imageManagedIdentityName + ) + ] + } + distributions: [ + { + type: 'SharedImage' + sharedImageGalleryImageDefinitionResourceId: resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Compute/galleries/images', + computeGalleryName, + computeGalleryImageDefinitionName + ) + } + ] + + // subnetResourceId: vnet.outputs.subnetResourceIds[0] // Image Subnet + subnetResourceId: resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Network/virtualNetworks/subnets', + virtualNetworkName, + imageSubnetName + ) + location: location + stagingResourceGroupResourceId: imageTemplateRg.id + roleAssignments: [ + { + roleDefinitionIdOrName: 'Contributor' + // Allow deployment script to trigger image build. Use 'existing' reference if only part of solution is deployed + principalId: (deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image') + ? dsMsi_existing.properties.principalId + : dsMsi.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + } + dependsOn: [ + storageAccount_upload + imageMSI_rbac + rg + imageMSI + azureComputeGallery + vnet + ] +} + +// Deployment script to trigger image build +module imageTemplate_trigger 'br/public:avm/res/resources/deployment-script:0.3.1' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image') { + name: '${deployment().name}-imageTemplate-trigger-ds' + scope: resourceGroup(resourceGroupName) + params: { + name: '${imageTemplateDeploymentScriptName}-${formattedTime}-${(deploymentsToPerform == 'All' || deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image') ? imageTemplate.outputs.name : ''}' // Requires condition als Bicep will otherwise try to resolve the null reference + kind: 'AzurePowerShell' + azPowerShellVersion: '12.0' + managedIdentities: { + userAssignedResourcesIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.ManagedIdentity/userAssignedIdentities', + deploymentScriptManagedIdentityName + ) + ] + } + enableTelemetry: enableTelemetry + scriptContent: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image') + ? imageTemplate.outputs.runThisCommand + : '' // Requires condition als Bicep will otherwise try to resolve the null reference + timeout: 'PT30M' + cleanupPreference: 'Always' + location: location + // storageAccountResourceId: dsStorageAccount.outputs.resourceId + storageAccountResourceId: resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Storage/storageAccounts', + deploymentScriptStorageAccountName + ) + subnetResourceIds: [ + // vnet.outputs.subnetResourceIds[1] // deploymentScriptSubnet + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Network/virtualNetworks/subnets', + virtualNetworkName, + deploymentScriptSubnetName + ) + ] + } + dependsOn: [ + // Always required + imageTemplate + // Conditionally required + rg + dsMsi + dsStorageAccount + storageAccount_upload + vnet + ] +} + +module imageTemplate_wait 'br/public:avm/res/resources/deployment-script:0.3.1' = if (waitForImageBuild && (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only assets & image' || deploymentsToPerform == 'Only image')) { + name: '${deployment().name}-imageTemplate-wait-ds' + scope: resourceGroup(resourceGroupName) + params: { + name: '${waitDeploymentScriptName}-${formattedTime}' + kind: 'AzurePowerShell' + azPowerShellVersion: '12.0' + managedIdentities: { + userAssignedResourcesIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.ManagedIdentity/userAssignedIdentities', + deploymentScriptManagedIdentityName + ) + ] + } + scriptContent: loadTextContent('../../../utilities/e2e-template-assets/scripts/Wait-ForImageBuild.ps1') + arguments: ' -ImageTemplateName "${imageTemplate.outputs.name}" -ResourceGroupName "${resourceGroupName}"' + timeout: waitForImageBuildTimeout + cleanupPreference: 'Always' + location: location + storageAccountResourceId: resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Storage/storageAccounts', + deploymentScriptStorageAccountName + ) + subnetResourceIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.Network/virtualNetworks/subnets', + virtualNetworkName, + deploymentScriptSubnetName + ) + ] + } + dependsOn: [ + imageTemplate_trigger + rg + vnet + dsStorageAccount + dsMsi + ] +} + +// ============================= // +// END: ALL // +// END: ONLY ASSETS & IMAGE // +// END: ONLY IMAGE // +/////////////////////////////////// + +// =============== // +// Definitions // +// =============== // + +type storageAccountFilesToUploadType = { + @description('Required. The name of the environment variable.') + name: string + + @description('Required. The value of the secure environment variable.') + @secure() + secureValue: string? + + @description('Required. The value of the environment variable.') + value: string? +} diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/main.json b/avm/ptn/virtual-machine-images/azure-image-builder/main.json new file mode 100644 index 00000000000..408049bed07 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/main.json @@ -0,0 +1,15487 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "12788075391199236027" + }, + "name": "Custom Images using Azure Image Builder", + "description": "This module provides you with a packaged solution to create custom images using the Azure Image Builder service publishing to an Azure Compute Gallery.", + "owner": "AlexanderSehr" + }, + "definitions": { + "storageAccountFilesToUploadType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the environment variable." + } + }, + "secureValue": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Required. The value of the secure environment variable." + } + }, + "value": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The value of the environment variable." + } + } + } + } + }, + "parameters": { + "deploymentsToPerform": { + "type": "string", + "defaultValue": "Only assets & image", + "allowedValues": [ + "All", + "Only base", + "Only assets & image", + "Only image" + ], + "metadata": { + "description": "Optional. A parameter to control which deployments should be executed." + } + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "rg-ado-agents", + "metadata": { + "description": "Optional. The name of the Resource Group." + } + }, + "imageTemplateResourceGroupName": { + "type": "string", + "defaultValue": "[format('{0}-image-build', parameters('resourceGroupName'))]", + "metadata": { + "description": "Optional. The name of the Resource Group to deploy the Image Template resources into." + } + }, + "deploymentScriptManagedIdentityName": { + "type": "string", + "defaultValue": "msi-ds", + "metadata": { + "description": "Optional. The name of the Managed Identity used by deployment scripts." + } + }, + "imageManagedIdentityName": { + "type": "string", + "defaultValue": "msi-aib", + "metadata": { + "description": "Optional. The name of the Managed Identity used by the Azure Image Builder." + } + }, + "computeGalleryName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Azure Compute Gallery." + } + }, + "computeGalleryImageDefinitions": { + "type": "array", + "metadata": { + "description": "Required. The Image Definitions in the Azure Compute Gallery." + } + }, + "assetsStorageAccountName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the storage account. Only needed if you want to upload scripts to be used during image baking." + } + }, + "deploymentScriptStorageAccountName": { + "type": "string", + "defaultValue": "[format('{0}ds', parameters('assetsStorageAccountName'))]", + "metadata": { + "description": "Optional. The name of the storage account." + } + }, + "assetsStorageAccountContainerName": { + "type": "string", + "defaultValue": "aibscripts", + "metadata": { + "description": "Optional. The name of container in the Storage Account." + } + }, + "virtualNetworkName": { + "type": "string", + "defaultValue": "vnet-it", + "metadata": { + "description": "Optional. The name of the Virtual Network." + } + }, + "virtualNetworkAddressPrefix": { + "type": "string", + "defaultValue": "10.0.0.0/16", + "metadata": { + "description": "Optional. The address space of the Virtual Network." + } + }, + "imageSubnetName": { + "type": "string", + "defaultValue": "subnet-it", + "metadata": { + "description": "Optional. The name of the Image Template Virtual Network Subnet to create." + } + }, + "virtualNetworkSubnetAddressPrefix": { + "type": "string", + "defaultValue": "[cidrSubnet(parameters('virtualNetworkAddressPrefix'), 24, 0)]", + "metadata": { + "description": "Optional. The address space of the Virtual Network Subnet." + } + }, + "deploymentScriptSubnetName": { + "type": "string", + "defaultValue": "subnet-ds", + "metadata": { + "description": "Optional. The name of the Image Template Virtual Network Subnet to create." + } + }, + "virtualNetworkDeploymentScriptSubnetAddressPrefix": { + "type": "string", + "defaultValue": "[cidrSubnet(parameters('virtualNetworkAddressPrefix'), 24, 1)]", + "metadata": { + "description": "Optional. The address space of the Virtual Network Subnet used by the deployment script." + } + }, + "storageDeploymentScriptName": { + "type": "string", + "defaultValue": "ds-triggerUpload-storage", + "metadata": { + "description": "Optional. The name of the Deployment Script to upload files to the assets storage account." + } + }, + "storageAccountFilesToUpload": { + "type": "array", + "items": { + "$ref": "#/definitions/storageAccountFilesToUploadType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The files to upload to the Assets Storage Account." + } + }, + "imageTemplateDeploymentScriptName": { + "type": "string", + "defaultValue": "ds-triggerBuild-imageTemplate", + "metadata": { + "description": "Optional. The name of the Deployment Script to trigger the image template baking." + } + }, + "waitDeploymentScriptName": { + "type": "string", + "defaultValue": "ds-wait-imageTemplate-build", + "metadata": { + "description": "Optional. The name of the Deployment Script to wait for for the image baking to conclude." + } + }, + "imageTemplateName": { + "type": "string", + "defaultValue": "it-aib", + "metadata": { + "description": "Optional. The name of the Image Template." + } + }, + "imageTemplateImageSource": { + "type": "object", + "metadata": { + "description": "Required. The image source to use for the Image Template." + } + }, + "imageTemplateCustomizationSteps": { + "type": "array", + "nullable": true, + "minLength": 1, + "metadata": { + "description": "Optional. The customization steps to use for the Image Template." + } + }, + "computeGalleryImageDefinitionName": { + "type": "string", + "metadata": { + "description": "Required. The name of Image Definition of the Azure Compute Gallery to host the new image version." + } + }, + "waitForImageBuild": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. A parameter to control if the deployment should wait for the image build to complete." + } + }, + "waitForImageBuildTimeout": { + "type": "string", + "defaultValue": "PT1H", + "metadata": { + "description": "Optional. A parameter to control the timeout of the deployment script waiting for the image build." + } + }, + "location": { + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "Optional. The location to deploy into." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to generate a SAS token to access the modules." + } + } + }, + "variables": { + "$fxv#0": "<#\n.SYNOPSIS\nRun the Post-Deployment for the storage account deployment & upload required data to the storage account.\n\n.DESCRIPTION\nRun the Post-Deployment for the storage account deployment & upload required data to the storage account.\nAny content that should be uploaded must exist as an environment variable with a 'script_' prefix (for example 'script_Initialize-LinuxSoftware_ps1').\nThe script will fetch any matching environment variable, store it as a file (for example 'script_Initialize__LinuxSoftware_ps1' is stored as 'Initialize-LinuxSoftware.ps1')\nand uploade it as blob to the given container.\n\n.PARAMETER StorageAccountName\nRequired. The name of the Storage Account to upload to\n\n.PARAMETER TargetContainer\nRequired. The container to upload the files to\n\n.EXAMPLE\n. 'Set-StorageContainerContentByEnvVar.ps1' -StorageAccountName 'mystorage' -TargetContainer 'myContainer'\n\nUpload any required data to the storage account 'mystorage' and container 'myContainer'.\n#>\n\n[CmdletBinding(SupportsShouldProcess = $True)]\nparam(\n [Parameter(Mandatory = $true)]\n [string] $StorageAccountName,\n\n [Parameter(Mandatory = $true)]\n [string] $TargetContainer\n)\n\nWrite-Verbose 'Fetching & storing scripts' -Verbose\n$contentDirectoryName = 'scripts'\n$contentDirectory = (New-Item $contentDirectoryName -ItemType 'Directory' -Force).FullName\n$scriptPaths = @()\nforeach ($scriptEnvVar in (Get-ChildItem 'env:*').Name | Where-Object { $_ -like '__SCRIPT__*' }) {\n # Handle value like 'script_Initialize__LinuxSoftware_ps1'\n $scriptName = $scriptEnvVar -replace '__SCRIPT__', '' -replace '__', '-' -replace '_', '.'\n $scriptContent = (Get-Item env:$scriptEnvVar).Value\n\n Write-Verbose ('Storing file [{0}] with length [{1}]' -f $scriptName, $scriptContent.Length) -Verbose\n $scriptPaths += (New-Item (Join-Path $contentDirectoryName $scriptName) -ItemType 'File' -Value $scriptContent -Force).FullName\n}\n\nWrite-Verbose 'Getting storage account context.' -Verbose\n$ctx = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount\n\nWrite-Verbose 'Building paths to the local folders to upload.' -Verbose\nWrite-Verbose \"Content directory: '$contentDirectory'\" -Verbose\n\nforeach ($scriptPath in $scriptPaths) {\n\n try {\n Write-Verbose 'Testing blob container' -Verbose\n Get-AzStorageContainer -Name $targetContainer -Context $ctx -ErrorAction 'Stop'\n Write-Verbose 'Testing blob container SUCCEEDED' -Verbose\n\n Write-Verbose ('Uploading file [{0}] to container [{1}]' -f (Split-Path $scriptPath -Leaf), $TargetContainer) -Verbose\n if ($PSCmdlet.ShouldProcess(('File [{0}] to container [{1}]' -f (Split-Path $scriptPath -Leaf), $TargetContainer), 'Upload')) {\n $null = Set-AzStorageBlobContent -File $scriptPath -Container $targetContainer -Context $ctx -Force -ErrorAction 'Stop'\n }\n Write-Verbose 'Upload successful' -Verbose\n } catch {\n throw \"Upload FAILED: $_\"\n }\n}\n", + "$fxv#1": "<#\n.SYNOPSIS\nFetch the latest build status for the provided image template\n\n.DESCRIPTION\nFetch the latest build status for the provided image template\n\n.PARAMETER ResourceGroupName\nRequired. The name of the Resource Group containing the image template\n\n.PARAMETER ImageTemplateName\nRequired. The name of the image template to query to build status for. E.g. 'lin_it-2022-02-20-16-17-38'\n\n.EXAMPLE\n. 'Wait-ForImageBuild.ps1' -ResourceGroupName' 'myRG' -ImageTemplateName 'lin_it-2022-02-20-16-17-38'\n\nCheck the current build status of Image Template 'lin_it-2022-02-20-16-17-38' in Resource Group 'myRG'\n#>\n[CmdletBinding()]\nparam(\n [Parameter(Mandatory)]\n [string] $ResourceGroupName,\n\n [Parameter(Mandatory)]\n [string] $ImageTemplateName\n)\n\nbegin {\n Write-Debug ('[{0} entered]' -f $MyInvocation.MyCommand)\n}\n\nprocess {\n # Logic\n # -----\n $context = Get-AzContext\n $subscriptionId = $context.Subscription.Id\n $currentRetry = 1\n $maximumRetries = 720\n $timeToWait = 15\n $maxTimeCalc = '{0:hh\\:mm\\:ss}' -f [timespan]::fromseconds($maximumRetries * $timeToWait)\n do {\n\n # Runnning fetch in retry as it happened that the status was not available\n $statusFetchRetryCount = 3\n $statusFetchCurrentRetry = 1\n do {\n $path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.VirtualMachineImages/imageTemplates/{2}?api-version=2020-02-14' -f $subscriptionId, $ResourceGroupName, $ImageTemplateName\n $requestInputObject = @{\n Method = 'GET'\n Path = $path\n }\n\n $response = ((Invoke-AzRestMethod @requestInputObject).Content | ConvertFrom-Json).properties\n\n if ($response.lastRunStatus) {\n $latestStatus = $response.lastRunStatus\n break\n }\n Start-Sleep 5\n $statusFetchCurrentRetry++\n } while ($statusFetchCurrentRetry -le $statusFetchRetryCount)\n\n if (-not $latestStatus) {\n Write-Verbose ('Image Build failed with error: [{0}]' -f $response.provisioningError.message) -Verbose\n $latestStatus = 'failed'\n }\n\n\n if ($latestStatus -eq 'failed' -or $latestStatus.runState.ToLower() -eq 'failed') {\n $failedMessage = 'Image Template [{0}] build failed with status [{1}]. API reply: [{2}]' -f $ImageTemplateName, $latestStatus.runState, $response.lastRunStatus.message\n Write-Verbose $failedMessage -Verbose\n throw $failedMessage\n }\n\n if ($latestStatus.runState.ToLower() -notIn @('running', 'new')) {\n break\n }\n\n $currTimeCalc = '{0:hh\\:mm\\:ss}' -f [timespan]::fromseconds($currentRetry * $timeToWait)\n\n Write-Verbose ('[{0}] Waiting 15 seconds [{1}|{2}]' -f (Get-Date -Format 'HH:mm:ss'), $currTimeCalc, $maxTimeCalc) -Verbose\n $currentRetry++\n Start-Sleep $timeToWait\n } while ($currentRetry -le $maximumRetries)\n\n if ($latestStatus) {\n $duration = New-TimeSpan -Start $latestStatus.startTime -End $latestStatus.endTime\n Write-Verbose ('It took [{0}] minutes and [{1}] seconds to build and distribute the image.' -f $duration.Minutes, $duration.Seconds) -Verbose\n } else {\n Write-Warning \"Timeout at [$currTimeCalc]. Note, the Azure Image Builder may still succeed.\"\n }\n return $latestStatus\n}\n\nend {\n Write-Debug ('[{0} existed]' -f $MyInvocation.MyCommand)\n}\n", + "formattedTime": "[replace(replace(replace(parameters('baseTime'), ':', ''), '-', ''), ' ', '')]" + }, + "resources": { + "storageFileDataPrivilegedContributorRole": { + "existing": true, + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2022-04-01", + "scope": "/", + "name": "69566ab7-960f-475b-8e7c-b3118f30c6bd" + }, + "contributorRole": { + "existing": true, + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2022-04-01", + "scope": "/", + "name": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.ptn.vmimages-azureimagebuilder.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "location": "[parameters('location')]", + "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" + } + } + } + } + }, + "rg": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2024-03-01", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('location')]" + }, + "imageTemplateRg": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2024-03-01", + "name": "[parameters('imageTemplateResourceGroupName')]", + "location": "[parameters('location')]" + }, + "imageMSI_rbac": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(subscription().id, format('{0}/resourceGroups/{1}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{2}', subscription().id, parameters('resourceGroupName'), parameters('imageManagedIdentityName')), tenantResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))]", + "properties": { + "principalId": "[if(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base')), reference('imageMSI').outputs.principalId.value, '')]", + "roleDefinitionId": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "contributorRole", + "imageMSI" + ] + }, + "dsMsi_existing": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'Only assets & image'), equals(parameters('deploymentsToPerform'), 'Only image'))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "resourceGroup": "[parameters('resourceGroupName')]", + "name": "[parameters('deploymentScriptManagedIdentityName')]" + }, + "dsMsi": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-ds-msi', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('deploymentScriptManagedIdentityName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "8316967256052237432" + }, + "name": "User Assigned Identities", + "description": "This module deploys a User Assigned Identity.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "federatedIdentityCredentialsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the federated identity credential." + } + }, + "audiences": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of audiences that can appear in the issued token." + } + }, + "issuer": { + "type": "string", + "metadata": { + "description": "Required. The URL of the issuer to be trusted." + } + }, + "subject": { + "type": "string", + "metadata": { + "description": "Required. The identifier of the external identity." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the User Assigned Identity." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "federatedIdentityCredentials": { + "$ref": "#/definitions/federatedIdentityCredentialsType", + "metadata": { + "description": "Optional. The federated identity credentials list to indicate which token from the external IdP should be trusted by your application. Federated identity credentials are supported on applications only. A maximum of 20 federated identity credentials can be added per application object." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Managed Identity Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59')]", + "Managed Identity Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f1a07417-d97a-45cb-824c-7a7467783830')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.managedidentity-userassignedidentity.{0}.{1}', replace('0.2.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "userAssignedIdentity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + }, + "userAssignedIdentity_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "userAssignedIdentity" + ] + }, + "userAssignedIdentity_roleAssignments": { + "copy": { + "name": "userAssignedIdentity_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "userAssignedIdentity" + ] + }, + "userAssignedIdentity_federatedIdentityCredentials": { + "copy": { + "name": "userAssignedIdentity_federatedIdentityCredentials", + "count": "[length(coalesce(parameters('federatedIdentityCredentials'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-UserMSI-FederatedIdentityCredential-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].name]" + }, + "userAssignedIdentityName": { + "value": "[parameters('name')]" + }, + "audiences": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].audiences]" + }, + "issuer": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].issuer]" + }, + "subject": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].subject]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "663270811232806628" + }, + "name": "User Assigned Identity Federated Identity Credential", + "description": "This module deploys a User Assigned Identity Federated Identity Credential.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "userAssignedIdentityName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent user assigned identity. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "audiences": { + "type": "array", + "metadata": { + "description": "Required. The list of audiences that can appear in the issued token. Should be set to api://AzureADTokenExchange for Azure AD. It says what Microsoft identity platform should accept in the aud claim in the incoming token. This value represents Azure AD in your external identity provider and has no fixed value across identity providers - you might need to create a new application registration in your IdP to serve as the audience of this token." + } + }, + "issuer": { + "type": "string", + "metadata": { + "description": "Required. The URL of the issuer to be trusted. Must match the issuer claim of the external token being exchanged." + } + }, + "subject": { + "type": "string", + "metadata": { + "description": "Required. The identifier of the external software workload within the external identity provider. Like the audience value, it has no fixed format, as each IdP uses their own - sometimes a GUID, sometimes a colon delimited identifier, sometimes arbitrary strings. The value here must match the sub claim within the token presented to Azure AD." + } + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials", + "apiVersion": "2023-01-31", + "name": "[format('{0}/{1}', parameters('userAssignedIdentityName'), parameters('name'))]", + "properties": { + "audiences": "[parameters('audiences')]", + "issuer": "[parameters('issuer')]", + "subject": "[parameters('subject')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the federated identity credential." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the federated identity credential." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials', parameters('userAssignedIdentityName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the federated identity credential was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "userAssignedIdentity" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the user assigned identity." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID (object ID) of the user assigned identity." + }, + "value": "[reference('userAssignedIdentity').principalId]" + }, + "clientId": { + "type": "string", + "metadata": { + "description": "The client ID (application ID) of the user assigned identity." + }, + "value": "[reference('userAssignedIdentity').clientId]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the user assigned identity was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('userAssignedIdentity', '2023-01-31', 'full').location]" + } + } + } + }, + "dependsOn": [ + "rg" + ] + }, + "imageMSI": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-image-msi', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('imageManagedIdentityName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "8316967256052237432" + }, + "name": "User Assigned Identities", + "description": "This module deploys a User Assigned Identity.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "federatedIdentityCredentialsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the federated identity credential." + } + }, + "audiences": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of audiences that can appear in the issued token." + } + }, + "issuer": { + "type": "string", + "metadata": { + "description": "Required. The URL of the issuer to be trusted." + } + }, + "subject": { + "type": "string", + "metadata": { + "description": "Required. The identifier of the external identity." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the User Assigned Identity." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "federatedIdentityCredentials": { + "$ref": "#/definitions/federatedIdentityCredentialsType", + "metadata": { + "description": "Optional. The federated identity credentials list to indicate which token from the external IdP should be trusted by your application. Federated identity credentials are supported on applications only. A maximum of 20 federated identity credentials can be added per application object." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Managed Identity Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e40ec5ca-96e0-45a2-b4ff-59039f2c2b59')]", + "Managed Identity Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f1a07417-d97a-45cb-824c-7a7467783830')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.managedidentity-userassignedidentity.{0}.{1}', replace('0.2.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "userAssignedIdentity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + }, + "userAssignedIdentity_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "userAssignedIdentity" + ] + }, + "userAssignedIdentity_roleAssignments": { + "copy": { + "name": "userAssignedIdentity_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "userAssignedIdentity" + ] + }, + "userAssignedIdentity_federatedIdentityCredentials": { + "copy": { + "name": "userAssignedIdentity_federatedIdentityCredentials", + "count": "[length(coalesce(parameters('federatedIdentityCredentials'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-UserMSI-FederatedIdentityCredential-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].name]" + }, + "userAssignedIdentityName": { + "value": "[parameters('name')]" + }, + "audiences": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].audiences]" + }, + "issuer": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].issuer]" + }, + "subject": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].subject]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "663270811232806628" + }, + "name": "User Assigned Identity Federated Identity Credential", + "description": "This module deploys a User Assigned Identity Federated Identity Credential.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "userAssignedIdentityName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent user assigned identity. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "audiences": { + "type": "array", + "metadata": { + "description": "Required. The list of audiences that can appear in the issued token. Should be set to api://AzureADTokenExchange for Azure AD. It says what Microsoft identity platform should accept in the aud claim in the incoming token. This value represents Azure AD in your external identity provider and has no fixed value across identity providers - you might need to create a new application registration in your IdP to serve as the audience of this token." + } + }, + "issuer": { + "type": "string", + "metadata": { + "description": "Required. The URL of the issuer to be trusted. Must match the issuer claim of the external token being exchanged." + } + }, + "subject": { + "type": "string", + "metadata": { + "description": "Required. The identifier of the external software workload within the external identity provider. Like the audience value, it has no fixed format, as each IdP uses their own - sometimes a GUID, sometimes a colon delimited identifier, sometimes arbitrary strings. The value here must match the sub claim within the token presented to Azure AD." + } + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials", + "apiVersion": "2023-01-31", + "name": "[format('{0}/{1}', parameters('userAssignedIdentityName'), parameters('name'))]", + "properties": { + "audiences": "[parameters('audiences')]", + "issuer": "[parameters('issuer')]", + "subject": "[parameters('subject')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the federated identity credential." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the federated identity credential." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials', parameters('userAssignedIdentityName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the federated identity credential was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "userAssignedIdentity" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the user assigned identity." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID (object ID) of the user assigned identity." + }, + "value": "[reference('userAssignedIdentity').principalId]" + }, + "clientId": { + "type": "string", + "metadata": { + "description": "The client ID (application ID) of the user assigned identity." + }, + "value": "[reference('userAssignedIdentity').clientId]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the user assigned identity was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('userAssignedIdentity', '2023-01-31', 'full').location]" + } + } + } + }, + "dependsOn": [ + "rg" + ] + }, + "azureComputeGallery": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-acg', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('computeGalleryName')]" + }, + "images": { + "value": "[parameters('computeGalleryImageDefinitions')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "9752194366887174506" + }, + "name": "Azure Compute Galleries", + "description": "This module deploys an Azure Compute Gallery (formerly known as Shared Image Gallery).", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Required. Name of the Azure Compute Gallery." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description of the Azure Shared Image Gallery." + } + }, + "applications": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Applications to create." + } + }, + "images": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Images to create." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags for all resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "sharingProfile": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Profile for gallery sharing to subscription or tenant." + } + }, + "softDeletePolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Soft deletion policy of the gallery." + } + } + }, + "variables": { + "builtInRoleNames": { + "Compute Gallery Sharing Admin": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1ef6a3be-d0ac-425d-8c01-acb62866290b')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.compute-gallery.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "gallery": { + "type": "Microsoft.Compute/galleries", + "apiVersion": "2022-03-03", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "description": "[parameters('description')]", + "sharingProfile": "[parameters('sharingProfile')]", + "softDeletePolicy": "[parameters('softDeletePolicy')]" + } + }, + "gallery_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Compute/galleries/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "gallery" + ] + }, + "gallery_roleAssignments": { + "copy": { + "name": "gallery_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Compute/galleries/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Compute/galleries', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "gallery" + ] + }, + "galleries_applications": { + "copy": { + "name": "galleries_applications", + "count": "[length(coalesce(parameters('applications'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Gallery-Application-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": { + "value": "[coalesce(parameters('applications'), createArray())[copyIndex()].name]" + }, + "galleryName": { + "value": "[parameters('name')]" + }, + "supportedOSType": { + "value": "[coalesce(parameters('applications'), createArray())[copyIndex()].supportedOSType]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'description')]" + }, + "eula": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'eula')]" + }, + "privacyStatementUri": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'privacyStatementUri')]" + }, + "releaseNoteUri": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'releaseNoteUri')]" + }, + "endOfLifeDate": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'endOfLifeDate')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "customActions": { + "value": "[tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'customActions')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('applications'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "9175012718933553578" + }, + "name": "Compute Galleries Applications", + "description": "This module deploys an Azure Compute Gallery Application.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the application definition." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "galleryName": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Conditional. The name of the parent Azure Compute Gallery. Required if the template is used in a standalone deployment." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of this gallery Application Definition resource. This property is updatable." + } + }, + "eula": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Eula agreement for the gallery Application Definition. Has to be a valid URL." + } + }, + "privacyStatementUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The privacy statement uri. Has to be a valid URL." + } + }, + "releaseNoteUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The release note uri. Has to be a valid URL." + } + }, + "supportedOSType": { + "type": "string", + "allowedValues": [ + "Windows", + "Linux" + ], + "metadata": { + "description": "Required. This property allows you to specify the supported type of the OS that application is built for." + } + }, + "endOfLifeDate": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The end of life date of the gallery Image Definition. This property can be used for decommissioning purposes. This property is updatable. Allowed format: 2020-01-10T23:00:00.000Z." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags for all resources." + } + }, + "customActions": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. A list of custom actions that can be performed with all of the Gallery Application Versions within this Gallery Application." + } + } + }, + "variables": { + "builtInRoleNames": { + "Compute Gallery Sharing Admin": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1ef6a3be-d0ac-425d-8c01-acb62866290b')]", + "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')]" + } + }, + "resources": { + "gallery": { + "existing": true, + "type": "Microsoft.Compute/galleries", + "apiVersion": "2022-03-03", + "name": "[parameters('galleryName')]" + }, + "application": { + "type": "Microsoft.Compute/galleries/applications", + "apiVersion": "2022-03-03", + "name": "[format('{0}/{1}', parameters('galleryName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "customActions": "[parameters('customActions')]", + "description": "[parameters('description')]", + "endOfLifeDate": "[parameters('endOfLifeDate')]", + "eula": "[parameters('eula')]", + "privacyStatementUri": "[parameters('privacyStatementUri')]", + "releaseNoteUri": "[parameters('releaseNoteUri')]", + "supportedOSType": "[parameters('supportedOSType')]" + }, + "dependsOn": [ + "gallery" + ] + }, + "application_roleAssignments": { + "copy": { + "name": "application_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Compute/galleries/{0}/applications/{1}', parameters('galleryName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Compute/galleries/applications', parameters('galleryName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "application" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the image was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the image." + }, + "value": "[resourceId('Microsoft.Compute/galleries/applications', parameters('galleryName'), parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the image." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('application', '2022-03-03', 'full').location]" + } + } + } + }, + "dependsOn": [ + "gallery" + ] + }, + "galleries_images": { + "copy": { + "name": "galleries_images", + "count": "[length(coalesce(parameters('images'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Gallery-Image-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": { + "value": "[coalesce(parameters('images'), createArray())[copyIndex()].name]" + }, + "galleryName": { + "value": "[parameters('name')]" + }, + "osType": { + "value": "[coalesce(parameters('images'), createArray())[copyIndex()].osType]" + }, + "osState": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'osState')]" + }, + "publisher": { + "value": "[coalesce(parameters('images'), createArray())[copyIndex()].publisher]" + }, + "offer": { + "value": "[coalesce(parameters('images'), createArray())[copyIndex()].offer]" + }, + "sku": { + "value": "[coalesce(parameters('images'), createArray())[copyIndex()].sku]" + }, + "minRecommendedvCPUs": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'minRecommendedvCPUs')]" + }, + "maxRecommendedvCPUs": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'maxRecommendedvCPUs')]" + }, + "minRecommendedMemory": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'minRecommendedMemory')]" + }, + "maxRecommendedMemory": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'maxRecommendedMemory')]" + }, + "hyperVGeneration": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'hyperVGeneration')]" + }, + "securityType": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'securityType')]" + }, + "isAcceleratedNetworkSupported": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'isAcceleratedNetworkSupported')]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'description')]" + }, + "eula": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'eula')]" + }, + "privacyStatementUri": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'privacyStatementUri')]" + }, + "releaseNoteUri": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'releaseNoteUri')]" + }, + "productName": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'productName')]" + }, + "planName": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'planName')]" + }, + "planPublisherName": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'planPublisherName')]" + }, + "endOfLife": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'endOfLife')]" + }, + "excludedDiskTypes": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'excludedDiskTypes')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('images'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "7059991596058545894" + }, + "name": "Compute Galleries Image Definitions", + "description": "This module deploys an Azure Compute Gallery Image Definition.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the image definition." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "galleryName": { + "type": "string", + "minLength": 1, + "metadata": { + "description": "Conditional. The name of the parent Azure Shared Image Gallery. Required if the template is used in a standalone deployment." + } + }, + "osType": { + "type": "string", + "allowedValues": [ + "Windows", + "Linux" + ], + "metadata": { + "description": "Required. OS type of the image to be created." + } + }, + "osState": { + "type": "string", + "defaultValue": "Generalized", + "allowedValues": [ + "Generalized", + "Specialized" + ], + "metadata": { + "description": "Optional. This property allows the user to specify whether the virtual machines created under this image are 'Generalized' or 'Specialized'." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the gallery Image Definition publisher." + } + }, + "offer": { + "type": "string", + "metadata": { + "description": "Required. The name of the gallery Image Definition offer." + } + }, + "sku": { + "type": "string", + "metadata": { + "description": "Required. The name of the gallery Image Definition SKU." + } + }, + "minRecommendedvCPUs": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 128, + "metadata": { + "description": "Optional. The minimum number of the CPU cores recommended for this image." + } + }, + "maxRecommendedvCPUs": { + "type": "int", + "defaultValue": 4, + "minValue": 1, + "maxValue": 128, + "metadata": { + "description": "Optional. The maximum number of the CPU cores recommended for this image." + } + }, + "minRecommendedMemory": { + "type": "int", + "defaultValue": 4, + "minValue": 1, + "maxValue": 4000, + "metadata": { + "description": "Optional. The minimum amount of RAM in GB recommended for this image." + } + }, + "maxRecommendedMemory": { + "type": "int", + "defaultValue": 16, + "minValue": 1, + "maxValue": 4000, + "metadata": { + "description": "Optional. The maximum amount of RAM in GB recommended for this image." + } + }, + "hyperVGeneration": { + "type": "string", + "nullable": true, + "allowedValues": [ + "V1", + "V2" + ], + "metadata": { + "description": "Optional. The hypervisor generation of the Virtual Machine.\n- If this value is not specified, then it is determined by the securityType parameter.\n- If the securityType parameter is specified, then the value of hyperVGeneration will be V2, else V1.\n" + } + }, + "securityType": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Standard", + "TrustedLaunch", + "ConfidentialVM", + "ConfidentialVMSupported" + ], + "metadata": { + "description": "Optional. The security type of the image. Requires a hyperVGeneration V2." + } + }, + "isHibernateSupported": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifiy if the image supports hibernation." + } + }, + "isAcceleratedNetworkSupported": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specify if the image supports accelerated networking.\nAccelerated networking enables single root I/O virtualization (SR-IOV) to a VM, greatly improving its networking performance.\nThis high-performance path bypasses the host from the data path, which reduces latency, jitter, and CPU utilization for the most demanding network workloads on supported VM types.\n" + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of this gallery Image Definition resource. This property is updatable." + } + }, + "eula": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Eula agreement for the gallery Image Definition. Has to be a valid URL." + } + }, + "privacyStatementUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The privacy statement uri. Has to be a valid URL." + } + }, + "releaseNoteUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The release note uri. Has to be a valid URL." + } + }, + "productName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The product ID." + } + }, + "planName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The plan ID." + } + }, + "planPublisherName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher ID." + } + }, + "endOfLife": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The end of life date of the gallery Image Definition. This property can be used for decommissioning purposes. This property is updatable. Allowed format: 2020-01-10T23:00:00.000Z." + } + }, + "excludedDiskTypes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of the excluded disk types (e.g., Standard_LRS)." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags for all resources." + } + } + }, + "variables": { + "builtInRoleNames": { + "Compute Gallery Sharing Admin": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1ef6a3be-d0ac-425d-8c01-acb62866290b')]", + "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')]" + } + }, + "resources": { + "gallery": { + "existing": true, + "type": "Microsoft.Compute/galleries", + "apiVersion": "2022-03-03", + "name": "[parameters('galleryName')]" + }, + "image": { + "type": "Microsoft.Compute/galleries/images", + "apiVersion": "2022-03-03", + "name": "[format('{0}/{1}', parameters('galleryName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "osType": "[parameters('osType')]", + "osState": "[parameters('osState')]", + "identifier": { + "publisher": "[parameters('publisher')]", + "offer": "[parameters('offer')]", + "sku": "[parameters('sku')]" + }, + "recommended": { + "vCPUs": { + "min": "[parameters('minRecommendedvCPUs')]", + "max": "[parameters('maxRecommendedvCPUs')]" + }, + "memory": { + "min": "[parameters('minRecommendedMemory')]", + "max": "[parameters('maxRecommendedMemory')]" + } + }, + "hyperVGeneration": "[coalesce(parameters('hyperVGeneration'), if(not(empty(parameters('securityType'))), 'V2', 'V1'))]", + "features": "[union(createArray(createObject('name', 'IsAcceleratedNetworkSupported', 'value', format('{0}', parameters('isAcceleratedNetworkSupported'))), createObject('name', 'IsHibernateSupported', 'value', format('{0}', parameters('isHibernateSupported')))), if(not(equals(parameters('securityType'), 'Standard')), createArray(createObject('name', 'SecurityType', 'value', parameters('securityType'))), createArray()))]", + "description": "[parameters('description')]", + "eula": "[parameters('eula')]", + "privacyStatementUri": "[parameters('privacyStatementUri')]", + "releaseNoteUri": "[parameters('releaseNoteUri')]", + "purchasePlan": { + "product": "[parameters('productName')]", + "name": "[parameters('planName')]", + "publisher": "[parameters('planPublisherName')]" + }, + "endOfLifeDate": "[parameters('endOfLife')]", + "disallowed": { + "diskTypes": "[parameters('excludedDiskTypes')]" + } + }, + "dependsOn": [ + "gallery" + ] + }, + "image_roleAssignments": { + "copy": { + "name": "image_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Compute/galleries/{0}/images/{1}', parameters('galleryName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Compute/galleries/images', parameters('galleryName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "image" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the image was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the image." + }, + "value": "[resourceId('Microsoft.Compute/galleries/images', parameters('galleryName'), parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the image." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('image', '2022-03-03', 'full').location]" + } + } + } + }, + "dependsOn": [ + "gallery" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed image gallery." + }, + "value": "[resourceId('Microsoft.Compute/galleries', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed image gallery." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed image gallery." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('gallery', '2022-03-03', 'full').location]" + }, + "imageResourceIds": { + "type": "array", + "metadata": { + "description": "The resource ids of the deployed images." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('images'), createArray()))))]", + "input": "[reference(format('galleries_images[{0}]', range(0, length(coalesce(parameters('images'), createArray())))[copyIndex()])).outputs.resourceId.value]" + } + } + } + } + }, + "dependsOn": [ + "rg" + ] + }, + "vnet": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-vnet', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('virtualNetworkName')]" + }, + "addressPrefixes": { + "value": [ + "[parameters('virtualNetworkAddressPrefix')]" + ] + }, + "subnets": { + "value": [ + { + "name": "[parameters('imageSubnetName')]", + "addressPrefix": "[parameters('virtualNetworkSubnetAddressPrefix')]", + "privateLinkServiceNetworkPolicies": "Disabled", + "serviceEndpoints": [ + { + "service": "Microsoft.Storage" + } + ] + }, + { + "name": "[parameters('deploymentScriptSubnetName')]", + "addressPrefix": "[parameters('virtualNetworkDeploymentScriptSubnetAddressPrefix')]", + "privateLinkServiceNetworkPolicies": "Disabled", + "serviceEndpoints": [ + { + "service": "Microsoft.Storage" + } + ], + "delegations": [ + { + "name": "Microsoft.ContainerInstance.containerGroups", + "properties": { + "serviceName": "Microsoft.ContainerInstance/containerGroups" + } + } + ] + } + ] + }, + "location": { + "value": "[parameters('location')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "18408205474040416108" + }, + "name": "Virtual Networks", + "description": "This module deploys a Virtual Network (vNet).", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Virtual Network (vNet)." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network." + } + }, + "subnets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An Array of subnets to deploy to the Virtual Network." + } + }, + "dnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. DNS Servers associated to the Virtual Network." + } + }, + "ddosProtectionPlanResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the DDoS protection plan to assign the VNET to. If it's left blank, DDoS protection will not be configured. If it's provided, the VNET created by this template will be attached to the referenced DDoS protection plan. The DDoS protection plan can exist in the same or in a different subscription." + } + }, + "peerings": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Virtual Network Peerings configurations." + } + }, + "vnetEncryption": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates if encryption is enabled on virtual network and if VM without encryption is allowed in encrypted VNet. Requires the EnableVNetEncryption feature to be registered for the subscription and a supported region to use this property." + } + }, + "vnetEncryptionEnforcement": { + "type": "string", + "defaultValue": "AllowUnencrypted", + "allowedValues": [ + "AllowUnencrypted", + "DropUnencrypted" + ], + "metadata": { + "description": "Optional. If the encrypted VNet allows VM that does not support encryption. Can only be used when vnetEncryption is enabled." + } + }, + "flowTimeoutInMinutes": { + "type": "int", + "defaultValue": 0, + "maxValue": 30, + "metadata": { + "description": "Optional. The flow timeout in minutes for the Virtual Network, which is used to enable connection tracking for intra-VM flows. Possible values are between 4 and 30 minutes. Default value 0 will set the property to null." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.1.6', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "virtualNetwork": { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "subnets", + "count": "[length(parameters('subnets'))]", + "input": { + "name": "[parameters('subnets')[copyIndex('subnets')].name]", + "properties": { + "addressPrefix": "[parameters('subnets')[copyIndex('subnets')].addressPrefix]", + "addressPrefixes": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'addressPrefixes'), parameters('subnets')[copyIndex('subnets')].addressPrefixes, createArray())]", + "applicationGatewayIPConfigurations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'applicationGatewayIPConfigurations'), parameters('subnets')[copyIndex('subnets')].applicationGatewayIPConfigurations, createArray())]", + "delegations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'delegations'), parameters('subnets')[copyIndex('subnets')].delegations, createArray())]", + "ipAllocations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'ipAllocations'), parameters('subnets')[copyIndex('subnets')].ipAllocations, createArray())]", + "natGateway": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'natGatewayResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].natGatewayResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].natGatewayResourceId), null())]", + "networkSecurityGroup": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'networkSecurityGroupResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].networkSecurityGroupResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].networkSecurityGroupResourceId), null())]", + "privateEndpointNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'privateEndpointNetworkPolicies'), parameters('subnets')[copyIndex('subnets')].privateEndpointNetworkPolicies, null())]", + "privateLinkServiceNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'privateLinkServiceNetworkPolicies'), parameters('subnets')[copyIndex('subnets')].privateLinkServiceNetworkPolicies, null())]", + "routeTable": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'routeTableResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].routeTableResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].routeTableResourceId), null())]", + "serviceEndpoints": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'serviceEndpoints'), parameters('subnets')[copyIndex('subnets')].serviceEndpoints, createArray())]", + "serviceEndpointPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'serviceEndpointPolicies'), parameters('subnets')[copyIndex('subnets')].serviceEndpointPolicies, createArray())]" + } + } + } + ], + "addressSpace": { + "addressPrefixes": "[parameters('addressPrefixes')]" + }, + "ddosProtectionPlan": "[if(not(empty(parameters('ddosProtectionPlanResourceId'))), createObject('id', parameters('ddosProtectionPlanResourceId')), null())]", + "dhcpOptions": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', array(parameters('dnsServers'))), null())]", + "enableDdosProtection": "[not(empty(parameters('ddosProtectionPlanResourceId')))]", + "encryption": "[if(equals(parameters('vnetEncryption'), true()), createObject('enabled', parameters('vnetEncryption'), 'enforcement', parameters('vnetEncryptionEnforcement')), null())]", + "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]" + } + }, + "virtualNetwork_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_diagnosticSettings": { + "copy": { + "name": "virtualNetwork_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_roleAssignments": { + "copy": { + "name": "virtualNetwork_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_subnets": { + "copy": { + "name": "virtualNetwork_subnets", + "count": "[length(parameters('subnets'))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-subnet-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('subnets')[copyIndex()].name]" + }, + "addressPrefix": { + "value": "[parameters('subnets')[copyIndex()].addressPrefix]" + }, + "addressPrefixes": "[if(contains(parameters('subnets')[copyIndex()], 'addressPrefixes'), createObject('value', parameters('subnets')[copyIndex()].addressPrefixes), createObject('value', createArray()))]", + "applicationGatewayIPConfigurations": "[if(contains(parameters('subnets')[copyIndex()], 'applicationGatewayIPConfigurations'), createObject('value', parameters('subnets')[copyIndex()].applicationGatewayIPConfigurations), createObject('value', createArray()))]", + "delegations": "[if(contains(parameters('subnets')[copyIndex()], 'delegations'), createObject('value', parameters('subnets')[copyIndex()].delegations), createObject('value', createArray()))]", + "ipAllocations": "[if(contains(parameters('subnets')[copyIndex()], 'ipAllocations'), createObject('value', parameters('subnets')[copyIndex()].ipAllocations), createObject('value', createArray()))]", + "natGatewayResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'natGatewayResourceId'), createObject('value', parameters('subnets')[copyIndex()].natGatewayResourceId), createObject('value', ''))]", + "networkSecurityGroupResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'networkSecurityGroupResourceId'), createObject('value', parameters('subnets')[copyIndex()].networkSecurityGroupResourceId), createObject('value', ''))]", + "privateEndpointNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'privateEndpointNetworkPolicies'), createObject('value', parameters('subnets')[copyIndex()].privateEndpointNetworkPolicies), createObject('value', ''))]", + "privateLinkServiceNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'privateLinkServiceNetworkPolicies'), createObject('value', parameters('subnets')[copyIndex()].privateLinkServiceNetworkPolicies), createObject('value', ''))]", + "roleAssignments": "[if(contains(parameters('subnets')[copyIndex()], 'roleAssignments'), createObject('value', parameters('subnets')[copyIndex()].roleAssignments), createObject('value', createArray()))]", + "routeTableResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'routeTableResourceId'), createObject('value', parameters('subnets')[copyIndex()].routeTableResourceId), createObject('value', ''))]", + "serviceEndpointPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'serviceEndpointPolicies'), createObject('value', parameters('subnets')[copyIndex()].serviceEndpointPolicies), createObject('value', createArray()))]", + "serviceEndpoints": "[if(contains(parameters('subnets')[copyIndex()], 'serviceEndpoints'), createObject('value', parameters('subnets')[copyIndex()].serviceEndpoints), createObject('value', createArray()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "17306638026226376877" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Optional. The Name of the subnet resource." + } + }, + "virtualNetworkName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual network. Required if the template is used in a standalone deployment." + } + }, + "addressPrefix": { + "type": "string", + "metadata": { + "description": "Required. The address prefix for the subnet." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "routeTableResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpoints": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "delegations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The delegations to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. enable or disable apply network policies on private link service in the subnet." + } + }, + "addressPrefixes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of address prefixes for the subnet." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "ipAllocations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of IpAllocation which reference this subnet." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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')]" + } + }, + "resources": { + "virtualNetwork": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-04-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "properties": { + "addressPrefix": "[parameters('addressPrefix')]", + "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]", + "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]", + "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]", + "serviceEndpoints": "[parameters('serviceEndpoints')]", + "delegations": "[parameters('delegations')]", + "privateEndpointNetworkPolicies": "[if(not(empty(parameters('privateEndpointNetworkPolicies'))), parameters('privateEndpointNetworkPolicies'), null())]", + "privateLinkServiceNetworkPolicies": "[if(not(empty(parameters('privateLinkServiceNetworkPolicies'))), parameters('privateLinkServiceNetworkPolicies'), null())]", + "addressPrefixes": "[parameters('addressPrefixes')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "ipAllocations": "[parameters('ipAllocations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "subnet_roleAssignments": { + "copy": { + "name": "subnet_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "subnet" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]" + }, + "subnetAddressPrefix": { + "type": "string", + "metadata": { + "description": "The address prefix for the subnet." + }, + "value": "[reference('subnet').addressPrefix]" + }, + "subnetAddressPrefixes": { + "type": "array", + "metadata": { + "description": "List of address prefixes for the subnet." + }, + "value": "[if(not(empty(parameters('addressPrefixes'))), reference('subnet').addressPrefixes, createArray())]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_peering_local": { + "copy": { + "name": "virtualNetwork_peering_local", + "count": "[length(parameters('peerings'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetworkPeering-local-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "localVnetName": { + "value": "[parameters('name')]" + }, + "remoteVirtualNetworkId": { + "value": "[parameters('peerings')[copyIndex()].remoteVirtualNetworkId]" + }, + "name": "[if(contains(parameters('peerings')[copyIndex()], 'name'), createObject('value', parameters('peerings')[copyIndex()].name), createObject('value', format('{0}-{1}', parameters('name'), last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')))))]", + "allowForwardedTraffic": "[if(contains(parameters('peerings')[copyIndex()], 'allowForwardedTraffic'), createObject('value', parameters('peerings')[copyIndex()].allowForwardedTraffic), createObject('value', true()))]", + "allowGatewayTransit": "[if(contains(parameters('peerings')[copyIndex()], 'allowGatewayTransit'), createObject('value', parameters('peerings')[copyIndex()].allowGatewayTransit), createObject('value', false()))]", + "allowVirtualNetworkAccess": "[if(contains(parameters('peerings')[copyIndex()], 'allowVirtualNetworkAccess'), createObject('value', parameters('peerings')[copyIndex()].allowVirtualNetworkAccess), createObject('value', true()))]", + "doNotVerifyRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'doNotVerifyRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].doNotVerifyRemoteGateways), createObject('value', true()))]", + "useRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'useRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].useRemoteGateways), createObject('value', false()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "17624189975510507274" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "[format('{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkId'), '/')))]", + "metadata": { + "description": "Optional. The Name of Vnet Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + } + }, + "localVnetName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." + } + }, + "remoteVirtualNetworkId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", + "properties": { + "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", + "allowGatewayTransit": "[parameters('allowGatewayTransit')]", + "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]", + "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", + "useRemoteGateways": "[parameters('useRemoteGateways')]", + "remoteVirtualNetwork": { + "id": "[parameters('remoteVirtualNetworkId')]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_peering_remote": { + "copy": { + "name": "virtualNetwork_peering_remote", + "count": "[length(parameters('peerings'))]" + }, + "condition": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringEnabled'), equals(parameters('peerings')[copyIndex()].remotePeeringEnabled, true()), false())]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetworkPeering-remote-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')[2]]", + "resourceGroup": "[split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "localVnetName": { + "value": "[last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/'))]" + }, + "remoteVirtualNetworkId": { + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "name": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringName'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringName), createObject('value', format('{0}-{1}', last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')), parameters('name'))))]", + "allowForwardedTraffic": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowForwardedTraffic'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowForwardedTraffic), createObject('value', true()))]", + "allowGatewayTransit": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowGatewayTransit'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowGatewayTransit), createObject('value', false()))]", + "allowVirtualNetworkAccess": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowVirtualNetworkAccess), createObject('value', true()))]", + "doNotVerifyRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringDoNotVerifyRemoteGateways), createObject('value', true()))]", + "useRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringUseRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringUseRemoteGateways), createObject('value', false()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.26.170.59819", + "templateHash": "17624189975510507274" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "[format('{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkId'), '/')))]", + "metadata": { + "description": "Optional. The Name of Vnet Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + } + }, + "localVnetName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." + } + }, + "remoteVirtualNetworkId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", + "properties": { + "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", + "allowGatewayTransit": "[parameters('allowGatewayTransit')]", + "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]", + "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", + "useRemoteGateways": "[parameters('useRemoteGateways')]", + "remoteVirtualNetwork": { + "id": "[parameters('remoteVirtualNetworkId')]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network." + }, + "value": "[parameters('name')]" + }, + "subnetNames": { + "type": "array", + "metadata": { + "description": "The names of the deployed subnets." + }, + "copy": { + "count": "[length(parameters('subnets'))]", + "input": "[parameters('subnets')[copyIndex()].name]" + } + }, + "subnetResourceIds": { + "type": "array", + "metadata": { + "description": "The resource IDs of the deployed subnets." + }, + "copy": { + "count": "[length(parameters('subnets'))]", + "input": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), parameters('subnets')[copyIndex()].name)]" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('virtualNetwork', '2023-04-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "rg" + ] + }, + "assetsStorageAccount": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-files-sa', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('assetsStorageAccountName')]" + }, + "allowSharedKeyAccess": { + "value": false + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "networkAcls": { + "value": { + "defaultAction": "Allow" + } + }, + "blobServices": { + "value": { + "containers": [ + { + "name": "[parameters('assetsStorageAccountContainerName')]", + "publicAccess": "None", + "roleAssignments": [ + { + "roleDefinitionIdOrName": "Storage Blob Data Contributor", + "principalId": "[if(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base')), reference('dsMsi').outputs.principalId.value, '')]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "Storage Blob Data Reader", + "principalId": "[if(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base')), reference('imageMSI').outputs.principalId.value, '')]", + "principalType": "ServicePrincipal" + } + ] + } + ], + "containerDeleteRetentionPolicyEnabled": true, + "containerDeleteRetentionPolicyDays": 10 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "3958760216991467865" + }, + "name": "Storage Accounts", + "description": "This module deploys a Storage Account.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "networkAclsType": { + "type": "object", + "properties": { + "resourceAccessRules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "metadata": { + "description": "Required. The ID of the tenant in which the resource resides in." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Sets the resource access rules. Array entries must consist of \"tenantId\" and \"resourceId\" fields only." + } + }, + "bypass": { + "type": "string", + "allowedValues": [ + "AzureServices", + "AzureServices, Logging", + "AzureServices, Logging, Metrics", + "AzureServices, Metrics", + "Logging", + "Logging, Metrics", + "Metrics", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics." + } + }, + "virtualNetworkRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the virtual network rules." + } + }, + "ipRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the IP ACL rules." + } + }, + "defaultAction": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the default action of allow or deny when no other rules match." + } + } + } + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint ip address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private ip addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private ip address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "customerManagedKeyType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using 'latest'." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. If used must also be specified in `managedIdentities.userAssignedResourceIds`. Required if no system assigned identity is available for use." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Storage Account. Must be lower-case." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2", + "allowedValues": [ + "Storage", + "StorageV2", + "BlobStorage", + "FileStorage", + "BlockBlobStorage" + ], + "metadata": { + "description": "Optional. Type of Storage Account to create." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard_GRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_RAGRS", + "Standard_ZRS", + "Premium_LRS", + "Premium_ZRS", + "Standard_GZRS", + "Standard_RAGZRS" + ], + "metadata": { + "description": "Optional. Storage Account Sku Name." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "Hot", + "allowedValues": [ + "Premium", + "Hot", + "Cool" + ], + "metadata": { + "description": "Conditional. Required if the Storage Account kind is set to BlobStorage. The access tier is used for billing. The \"Premium\" access tier is the default value for premium block blobs storage account type and it cannot be changed for the premium block blobs storage account type." + } + }, + "largeFileSharesState": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Allow large file shares if sets to 'Enabled'. It cannot be disabled once it is enabled. Only supported on locally redundant and zone redundant file shares. It cannot be set on FileStorage storage accounts (storage accounts for premium file shares)." + } + }, + "azureFilesIdentityBasedAuthentication": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Provides the identity based authentication settings for Azure Files." + } + }, + "defaultToOAuthAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. A boolean flag which indicates whether the default authentication is OAuth or not." + } + }, + "allowSharedKeyAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether the storage account permits requests to be authorized with the account access key via Shared Key. If false, then all requests, including shared access signatures, must be authorized with Azure Active Directory (Azure AD). The default value is null, which is equivalent to true." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "managementPolicyRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The Storage Account ManagementPolicies Rules." + } + }, + "networkAcls": { + "$ref": "#/definitions/networkAclsType", + "nullable": true, + "metadata": { + "description": "Optional. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny." + } + }, + "requireInfrastructureEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. A Boolean indicating whether or not the service applies a secondary layer of encryption with platform managed keys for data at rest. For security reasons, it is recommended to set it to true." + } + }, + "allowCrossTenantReplication": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allow or disallow cross AAD tenant object replication." + } + }, + "customDomainName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Sets the custom domain name assigned to the storage account. Name is the CNAME source." + } + }, + "customDomainUseSubDomainName": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether indirect CName validation is enabled. This should only be set on updates." + } + }, + "dnsEndpointType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "AzureDnsZone", + "Standard" + ], + "metadata": { + "description": "Optional. Allows you to specify the type of endpoint. Set this to AzureDNSZone to create a large number of accounts in a single subscription, which creates accounts in an Azure DNS Zone and the endpoint URL will have an alphanumeric DNS Zone identifier." + } + }, + "blobServices": { + "type": "object", + "defaultValue": "[if(not(equals(parameters('kind'), 'FileStorage')), createObject('containerDeleteRetentionPolicyEnabled', true(), 'containerDeleteRetentionPolicyDays', 7, 'deleteRetentionPolicyEnabled', true(), 'deleteRetentionPolicyDays', 6), createObject())]", + "metadata": { + "description": "Optional. Blob service and containers to deploy." + } + }, + "fileServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. File service and shares to deploy." + } + }, + "queueServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Queue service and queues to create." + } + }, + "tableServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Table service and tables to create." + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether public access is enabled for all blobs or containers in the storage account. For security reasons, it is recommended to set it to false." + } + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "TLS1_2", + "allowedValues": [ + "TLS1_0", + "TLS1_1", + "TLS1_2" + ], + "metadata": { + "description": "Optional. Set the minimum TLS version on request to storage." + } + }, + "enableHierarchicalNamespace": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Conditional. If true, enables Hierarchical Namespace for the storage account. Required if enableSftp or enableNfsV3 is set to true." + } + }, + "enableSftp": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables Secure File Transfer Protocol for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "localUsers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Local users to deploy for SFTP authentication." + } + }, + "isLocalUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables local users feature, if set to true." + } + }, + "enableNfsV3": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables NFS 3.0 support for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "allowedCopyScope": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "AAD", + "PrivateLink" + ], + "metadata": { + "description": "Optional. Restrict copy to and from Storage Accounts within an AAD tenant or with Private Links to the same VNet." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set." + } + }, + "supportsHttpsTrafficOnly": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allows HTTPS traffic only to storage service if sets to true." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyType", + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "sasExpirationPeriod": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The SAS expiration period. DD.HH:MM:SS." + } + }, + "keyType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Account", + "Service" + ], + "metadata": { + "description": "Optional. The keyType to use with Queue & Table services." + } + } + }, + "variables": { + "supportsBlobService": "[or(or(or(equals(parameters('kind'), 'BlockBlobStorage'), equals(parameters('kind'), 'BlobStorage')), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "supportsFileService": "[or(or(equals(parameters('kind'), 'FileStorage'), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/')), coalesce(tryGet(parameters('customerManagedKey'), 'keyName'), 'dummyKey'))]", + "dependsOn": [ + "cMKKeyVault" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.9.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))]" + }, + "storageAccount": { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "[parameters('kind')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "properties": { + "allowSharedKeyAccess": "[parameters('allowSharedKeyAccess')]", + "defaultToOAuthAuthentication": "[parameters('defaultToOAuthAuthentication')]", + "allowCrossTenantReplication": "[parameters('allowCrossTenantReplication')]", + "allowedCopyScope": "[if(not(empty(parameters('allowedCopyScope'))), parameters('allowedCopyScope'), null())]", + "customDomain": { + "name": "[parameters('customDomainName')]", + "useSubDomainName": "[parameters('customDomainUseSubDomainName')]" + }, + "dnsEndpointType": "[if(not(empty(parameters('dnsEndpointType'))), parameters('dnsEndpointType'), null())]", + "isLocalUserEnabled": "[parameters('isLocalUserEnabled')]", + "encryption": "[union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true(), 'keyType', parameters('keyType')), 'queue', createObject('enabled', true(), 'keyType', parameters('keyType'))), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', reference('cMKKeyVault').vaultUri, 'keyversion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))), null()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject()))]", + "accessTier": "[if(and(not(equals(parameters('kind'), 'Storage')), not(equals(parameters('kind'), 'BlockBlobStorage'))), parameters('accessTier'), null())]", + "sasPolicy": "[if(not(empty(parameters('sasExpirationPeriod'))), createObject('expirationAction', 'Log', 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null())]", + "supportsHttpsTrafficOnly": "[parameters('supportsHttpsTrafficOnly')]", + "isHnsEnabled": "[if(parameters('enableHierarchicalNamespace'), parameters('enableHierarchicalNamespace'), null())]", + "isSftpEnabled": "[parameters('enableSftp')]", + "isNfsV3Enabled": "[if(parameters('enableNfsV3'), parameters('enableNfsV3'), '')]", + "largeFileSharesState": "[if(or(equals(parameters('skuName'), 'Standard_LRS'), equals(parameters('skuName'), 'Standard_ZRS')), parameters('largeFileSharesState'), null())]", + "minimumTlsVersion": "[parameters('minimumTlsVersion')]", + "networkAcls": "[if(not(empty(parameters('networkAcls'))), union(createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'defaultAction', coalesce(tryGet(parameters('networkAcls'), 'defaultAction'), 'Deny'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), if(contains(parameters('networkAcls'), 'bypass'), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass')), createObject())), createObject('bypass', 'AzureServices', 'defaultAction', 'Deny'))]", + "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkAcls'))), 'Disabled', null()))]", + "azureFilesIdentityBasedAuthentication": "[if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), parameters('azureFilesIdentityBasedAuthentication'), null())]" + }, + "dependsOn": [ + "cMKKeyVault", + "cMKUserAssignedIdentity" + ] + }, + "storageAccount_diagnosticSettings": { + "copy": { + "name": "storageAccount_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_roleAssignments": { + "copy": { + "name": "storageAccount_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_privateEndpoints": { + "copy": { + "name": "storageAccount_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StorageAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4120048060064073955" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11244630631275470040" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_managementPolicies": { + "condition": "[not(empty(coalesce(parameters('managementPolicyRules'), createArray())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-ManagementPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "rules": { + "value": "[coalesce(parameters('managementPolicyRules'), createArray())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "9473195527943694039" + }, + "name": "Storage Account Management Policies", + "description": "This module deploys a Storage Account Management Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "rules": { + "type": "array", + "metadata": { + "description": "Required. The Storage Account ManagementPolicies Rules." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/managementPolicies", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "properties": { + "policy": { + "rules": "[parameters('rules')]" + } + } + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed management policy." + }, + "value": "default" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed management policy." + }, + "value": "default" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed management policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount", + "storageAccount_blobServices" + ] + }, + "storageAccount_localUsers": { + "copy": { + "name": "storageAccount_localUsers", + "count": "[length(parameters('localUsers'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-LocalUsers-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('localUsers')[copyIndex()].name]" + }, + "hasSshKey": { + "value": "[parameters('localUsers')[copyIndex()].hasSshKey]" + }, + "hasSshPassword": { + "value": "[parameters('localUsers')[copyIndex()].hasSshPassword]" + }, + "permissionScopes": { + "value": "[parameters('localUsers')[copyIndex()].permissionScopes]" + }, + "hasSharedKey": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'hasSharedKey')]" + }, + "homeDirectory": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'homeDirectory')]" + }, + "sshAuthorizedKeys": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'sshAuthorizedKeys')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "14968464858285923305" + }, + "name": "Storage Account Local Users", + "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "sshAuthorizedKeysType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "string", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + } + }, + "metadata": { + "description": "Optional. The list of SSH authorized keys." + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the local user used for SFTP Authentication." + } + }, + "hasSharedKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key." + } + }, + "hasSshKey": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key." + } + }, + "hasSshPassword": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password." + } + }, + "homeDirectory": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The local user home directory." + } + }, + "permissionScopes": { + "type": "array", + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "$ref": "#/definitions/sshAuthorizedKeysType", + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "localUsers": { + "type": "Microsoft.Storage/storageAccounts/localUsers", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "hasSharedKey": "[parameters('hasSharedKey')]", + "hasSshKey": "[parameters('hasSshKey')]", + "hasSshPassword": "[parameters('hasSshPassword')]", + "homeDirectory": "[parameters('homeDirectory')]", + "permissionScopes": "[parameters('permissionScopes')]", + "sshAuthorizedKeys": "[tryGet(parameters('sshAuthorizedKeys'), 'secureList')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed local user." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed local user." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed local user." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_blobServices": { + "condition": "[not(empty(parameters('blobServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-BlobServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(parameters('blobServices'), 'containers')]" + }, + "automaticSnapshotPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'automaticSnapshotPolicyEnabled')]" + }, + "changeFeedEnabled": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedEnabled')]" + }, + "changeFeedRetentionInDays": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedRetentionInDays')]" + }, + "containerDeleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyEnabled')]" + }, + "containerDeleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyDays')]" + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyAllowPermanentDelete')]" + }, + "corsRules": { + "value": "[tryGet(parameters('blobServices'), 'corsRules')]" + }, + "defaultServiceVersion": { + "value": "[tryGet(parameters('blobServices'), 'defaultServiceVersion')]" + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyAllowPermanentDelete')]" + }, + "deleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyEnabled')]" + }, + "deleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyDays')]" + }, + "isVersioningEnabled": { + "value": "[tryGet(parameters('blobServices'), 'isVersioningEnabled')]" + }, + "lastAccessTimeTrackingPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'lastAccessTimeTrackingPolicyEnabled')]" + }, + "restorePolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyEnabled')]" + }, + "restorePolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyDays')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('blobServices'), 'diagnosticSettings')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "2306287879023715578" + }, + "name": "Storage Account blob Services", + "description": "This module deploys a Storage Account Blob Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "automaticSnapshotPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Automatic Snapshot is enabled if set to true." + } + }, + "changeFeedEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service." + } + }, + "changeFeedRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 146000, + "metadata": { + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed." + } + }, + "containerDeleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled." + } + }, + "containerDeleteRetentionPolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted item should be retained." + } + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "corsRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies CORS rules for the Blob service. You can include up to five CorsRule elements in the request. If no CorsRule elements are included in the request body, all CORS rules will be deleted, and CORS will be disabled for the Blob service." + } + }, + "defaultServiceVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions." + } + }, + "deleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for blob soft delete." + } + }, + "deleteRetentionPolicyDays": { + "type": "int", + "defaultValue": 7, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted blob should be retained." + } + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "isVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use versioning to automatically maintain previous versions of your blobs." + } + }, + "lastAccessTimeTrackingPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled." + } + }, + "restorePolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled." + } + }, + "restorePolicyDays": { + "type": "int", + "defaultValue": 6, + "minValue": 1, + "metadata": { + "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days." + } + }, + "containers": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Blob containers to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "blobServices": { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "automaticSnapshotPolicyEnabled": "[parameters('automaticSnapshotPolicyEnabled')]", + "changeFeed": "[if(parameters('changeFeedEnabled'), createObject('enabled', true(), 'retentionInDays', parameters('changeFeedRetentionInDays')), null())]", + "containerDeleteRetentionPolicy": { + "enabled": "[parameters('containerDeleteRetentionPolicyEnabled')]", + "days": "[parameters('containerDeleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(equals(parameters('containerDeleteRetentionPolicyEnabled'), true()), parameters('containerDeleteRetentionPolicyAllowPermanentDelete'), null())]" + }, + "cors": { + "corsRules": "[parameters('corsRules')]" + }, + "defaultServiceVersion": "[if(not(empty(parameters('defaultServiceVersion'))), parameters('defaultServiceVersion'), null())]", + "deleteRetentionPolicy": { + "enabled": "[parameters('deleteRetentionPolicyEnabled')]", + "days": "[parameters('deleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(and(parameters('deleteRetentionPolicyEnabled'), parameters('deleteRetentionPolicyAllowPermanentDelete')), true(), null())]" + }, + "isVersioningEnabled": "[parameters('isVersioningEnabled')]", + "lastAccessTimeTrackingPolicy": "[if(not(equals(reference('storageAccount', '2022-09-01', 'full').kind, 'Storage')), createObject('enable', parameters('lastAccessTimeTrackingPolicyEnabled'), 'name', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 'AccessTimeTracking', null()), 'trackingGranularityInDays', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 1, null())), null())]", + "restorePolicy": "[if(parameters('restorePolicyEnabled'), createObject('enabled', true(), 'days', parameters('restorePolicyDays')), null())]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "blobServices_diagnosticSettings": { + "copy": { + "name": "blobServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "blobServices" + ] + }, + "blobServices_container": { + "copy": { + "name": "blobServices_container", + "count": "[length(coalesce(parameters('containers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Container-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" + }, + "defaultEncryptionScope": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultEncryptionScope')]" + }, + "denyEncryptionScopeOverride": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'denyEncryptionScopeOverride')]" + }, + "enableNfsV3AllSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3AllSquash')]" + }, + "enableNfsV3RootSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3RootSquash')]" + }, + "immutableStorageWithVersioningEnabled": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutableStorageWithVersioningEnabled')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'metadata')]" + }, + "publicAccess": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'publicAccess')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "immutabilityPolicyProperties": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutabilityPolicyProperties')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "7045309160947869799" + }, + "name": "Storage Account Blob Containers", + "description": "This module deploys a Storage Account Blob Container.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Block override of encryption scope from the container default." + } + }, + "enableNfsV3AllSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 all squash on blob container." + } + }, + "enableNfsV3RootSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 root squash on blob container." + } + }, + "immutableStorageWithVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process." + } + }, + "immutabilityPolicyName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. Name of the immutable policy." + } + }, + "immutabilityPolicyProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Configure immutability policy." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. A name-value pair to associate with the container as metadata." + } + }, + "publicAccess": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "Container", + "Blob", + "None" + ], + "metadata": { + "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::blobServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "container": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "defaultEncryptionScope": "[if(not(empty(parameters('defaultEncryptionScope'))), parameters('defaultEncryptionScope'), null())]", + "denyEncryptionScopeOverride": "[if(equals(parameters('denyEncryptionScopeOverride'), true()), parameters('denyEncryptionScopeOverride'), null())]", + "enableNfsV3AllSquash": "[if(equals(parameters('enableNfsV3AllSquash'), true()), parameters('enableNfsV3AllSquash'), null())]", + "enableNfsV3RootSquash": "[if(equals(parameters('enableNfsV3RootSquash'), true()), parameters('enableNfsV3RootSquash'), null())]", + "immutableStorageWithVersioning": "[if(equals(parameters('immutableStorageWithVersioningEnabled'), true()), createObject('enabled', parameters('immutableStorageWithVersioningEnabled')), null())]", + "metadata": "[parameters('metadata')]", + "publicAccess": "[parameters('publicAccess')]" + }, + "dependsOn": [ + "storageAccount::blobServices" + ] + }, + "container_roleAssignments": { + "copy": { + "name": "container_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "container" + ] + }, + "immutabilityPolicy": { + "condition": "[not(empty(coalesce(parameters('immutabilityPolicyProperties'), createObject())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[parameters('immutabilityPolicyName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "containerName": { + "value": "[parameters('name')]" + }, + "immutabilityPeriodSinceCreationInDays": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'immutabilityPeriodSinceCreationInDays')]" + }, + "allowProtectedAppendWrites": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'allowProtectedAppendWrites')]" + }, + "allowProtectedAppendWritesAll": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'allowProtectedAppendWritesAll')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "2543276032744560941" + }, + "name": "Storage Account Blob Container Immutability Policies", + "description": "This module deploys a Storage Account Blob Container Immutability Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent container to apply the policy to. Required if the template is used in a standalone deployment." + } + }, + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]", + "properties": { + "immutabilityPeriodSinceCreationInDays": "[parameters('immutabilityPeriodSinceCreationInDays')]", + "allowProtectedAppendWrites": "[parameters('allowProtectedAppendWrites')]", + "allowProtectedAppendWritesAll": "[parameters('allowProtectedAppendWritesAll')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed immutability policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed immutability policy." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed immutability policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "container", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed container." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed container." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed container." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed blob service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_fileServices": { + "condition": "[not(empty(parameters('fileServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-FileServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('fileServices'), 'diagnosticSettings')]" + }, + "protocolSettings": { + "value": "[tryGet(parameters('fileServices'), 'protocolSettings')]" + }, + "shareDeleteRetentionPolicy": { + "value": "[tryGet(parameters('fileServices'), 'shareDeleteRetentionPolicy')]" + }, + "shares": { + "value": "[tryGet(parameters('fileServices'), 'shares')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "7463227074634701879" + }, + "name": "Storage Account File Share Services", + "description": "This module deploys a Storage Account File Share Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the file service." + } + }, + "protocolSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Protocol settings for file service." + } + }, + "shareDeleteRetentionPolicy": { + "type": "object", + "defaultValue": { + "enabled": true, + "days": 7 + }, + "metadata": { + "description": "Optional. The service properties for soft delete." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "shares": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. File shares to create." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "fileServices": { + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "protocolSettings": "[parameters('protocolSettings')]", + "shareDeleteRetentionPolicy": "[parameters('shareDeleteRetentionPolicy')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "fileServices_diagnosticSettings": { + "copy": { + "name": "fileServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/fileServices/{1}', parameters('storageAccountName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "fileServices" + ] + }, + "fileServices_shares": { + "copy": { + "name": "fileServices_shares", + "count": "[length(coalesce(parameters('shares'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-shares-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "fileServicesName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" + }, + "accessTier": { + "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2023-04-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" + }, + "enabledProtocols": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]" + }, + "rootSquash": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'rootSquash')]" + }, + "shareQuota": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'shareQuota')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "1342480740201032357" + }, + "name": "Storage Account File Shares", + "description": "This module deploys a Storage Account File Share.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "fileServicesName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Conditional. The name of the parent file service. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share to create." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "TransactionOptimized", + "allowedValues": [ + "Premium", + "Hot", + "Cool", + "TransactionOptimized" + ], + "metadata": { + "description": "Conditional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool." + } + }, + "shareQuota": { + "type": "int", + "defaultValue": 5120, + "metadata": { + "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)." + } + }, + "enabledProtocols": { + "type": "string", + "defaultValue": "SMB", + "allowedValues": [ + "NFS", + "SMB" + ], + "metadata": { + "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share." + } + }, + "rootSquash": { + "type": "string", + "defaultValue": "NoRootSquash", + "allowedValues": [ + "AllSquash", + "NoRootSquash", + "RootSquash" + ], + "metadata": { + "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "resources": { + "storageAccount::fileService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "fileShare": { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]", + "properties": { + "accessTier": "[parameters('accessTier')]", + "shareQuota": "[parameters('shareQuota')]", + "rootSquash": "[if(equals(parameters('enabledProtocols'), 'NFS'), parameters('rootSquash'), null())]", + "enabledProtocols": "[parameters('enabledProtocols')]" + }, + "dependsOn": [ + "storageAccount::fileService" + ] + }, + "fileShare_roleAssignments": { + "condition": "[not(empty(parameters('roleAssignments')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Share-Rbac', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "fileShareResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "roleAssignments": { + "value": "[parameters('roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "8779226603522513073" + } + }, + "parameters": { + "roleAssignments": { + "type": "array", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "fileShareResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the file share to assign the roles to." + } + } + }, + "variables": { + "$fxv#0": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "scope": { + "type": "string", + "metadata": { + "description": "Required. The scope to deploy the role assignment to." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The role definition Id to assign." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User", + "" + ], + "defaultValue": "", + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "defaultValue": "2.0", + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[[parameters('scope')]", + "name": "[[parameters('name')]", + "properties": { + "roleDefinitionId": "[[parameters('roleDefinitionId')]", + "principalId": "[[parameters('principalId')]", + "description": "[[parameters('description')]", + "principalType": "[[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "condition": "[[if(not(empty(parameters('condition'))), parameters('condition'), null())]", + "conditionVersion": "[[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "delegatedManagedIdentityResourceId": "[[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]" + } + } + ] + }, + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": [ + { + "copy": { + "name": "fileShare_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('{0}-Share-Rbac-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "Outer" + }, + "template": "[variables('$fxv#0')]", + "parameters": { + "scope": { + "value": "[replace(parameters('fileShareResourceId'), '/shares/', '/fileShares/')]" + }, + "name": { + "value": "[guid(parameters('fileShareResourceId'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, 'tyfa')]" + }, + "roleDefinitionId": { + "value": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]" + }, + "principalId": { + "value": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]" + }, + "principalType": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]" + }, + "condition": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]" + }, + "conditionVersion": { + "value": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]" + }, + "delegatedManagedIdentityResourceId": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + } + } + } + } + ] + } + }, + "dependsOn": [ + "fileShare" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "fileServices", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('storageAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_queueServices": { + "condition": "[not(empty(parameters('queueServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-QueueServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('queueServices'), 'diagnosticSettings')]" + }, + "queues": { + "value": "[tryGet(parameters('queueServices'), 'queues')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "10678250016540336570" + }, + "name": "Storage Account Queue Services", + "description": "This module deploys a Storage Account Queue Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "queues": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Queues to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "queueServices": { + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": {}, + "dependsOn": [ + "storageAccount" + ] + }, + "queueServices_diagnosticSettings": { + "copy": { + "name": "queueServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "queueServices" + ] + }, + "queueServices_queues": { + "copy": { + "name": "queueServices_queues", + "count": "[length(coalesce(parameters('queues'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Queue-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('queues'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'metadata')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "13487964166280180730" + }, + "name": "Storage Account Queues", + "description": "This module deploys a Storage Account Queue.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage queue to deploy." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Required. A name-value pair that represents queue metadata." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::queueServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "queue": { + "type": "Microsoft.Storage/storageAccounts/queueServices/queues", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]" + }, + "dependsOn": [ + "storageAccount::queueServices" + ] + }, + "queue_roleAssignments": { + "copy": { + "name": "queue_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}/queues/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "queue" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed queue." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed queue." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed queue." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_tableServices": { + "condition": "[not(empty(parameters('tableServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-TableServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('tableServices'), 'diagnosticSettings')]" + }, + "tables": { + "value": "[tryGet(parameters('tableServices'), 'tables')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "16839054392438941735" + }, + "name": "Storage Account Table Services", + "description": "This module deploys a Storage Account Table Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "tables": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. tables to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "tableServices": { + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": {}, + "dependsOn": [ + "storageAccount" + ] + }, + "tableServices_diagnosticSettings": { + "copy": { + "name": "tableServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "tableServices" + ] + }, + "tableServices_tables": { + "copy": { + "name": "tableServices_tables", + "count": "[length(parameters('tables'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Table-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('tables')[copyIndex()].name]" + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "roleAssignments": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "3177845984945141330" + }, + "name": "Storage Account Table", + "description": "This module deploys a Storage Account Table.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::tableServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "table": { + "type": "Microsoft.Storage/storageAccounts/tableServices/tables", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "dependsOn": [ + "storageAccount::tableServices" + ] + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}/tables/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "table" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed table service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed table service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed table service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage account." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed storage account." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed storage account." + }, + "value": "[resourceGroup().name]" + }, + "primaryBlobEndpoint": { + "type": "string", + "metadata": { + "description": "The primary blob endpoint reference if blob services are deployed." + }, + "value": "[if(and(not(empty(parameters('blobServices'))), contains(parameters('blobServices'), 'containers')), reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('name')), '2019-04-01').primaryEndpoints.blob, '')]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[coalesce(tryGet(tryGet(reference('storageAccount', '2022-09-01', 'full'), 'identity'), 'principalId'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('storageAccount', '2022-09-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "dsMsi", + "imageMSI", + "rg" + ] + }, + "dsStorageAccount": { + "condition": "[or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-ds-sa', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('deploymentScriptStorageAccountName')]" + }, + "allowSharedKeyAccess": { + "value": true + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "[tenantResourceId('Microsoft.Authorization/roleDefinitions', '69566ab7-960f-475b-8e7c-b3118f30c6bd')]", + "principalId": "[if(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base')), reference('dsMsi').outputs.principalId.value, '')]", + "principalType": "ServicePrincipal" + } + ] + }, + "location": { + "value": "[parameters('location')]" + }, + "networkAcls": { + "value": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "action": "Allow", + "id": "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('deploymentScriptSubnetName'))]" + } + ] + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "3958760216991467865" + }, + "name": "Storage Accounts", + "description": "This module deploys a Storage Account.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "networkAclsType": { + "type": "object", + "properties": { + "resourceAccessRules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "metadata": { + "description": "Required. The ID of the tenant in which the resource resides in." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Sets the resource access rules. Array entries must consist of \"tenantId\" and \"resourceId\" fields only." + } + }, + "bypass": { + "type": "string", + "allowedValues": [ + "AzureServices", + "AzureServices, Logging", + "AzureServices, Logging, Metrics", + "AzureServices, Metrics", + "Logging", + "Logging, Metrics", + "Metrics", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics." + } + }, + "virtualNetworkRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the virtual network rules." + } + }, + "ipRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the IP ACL rules." + } + }, + "defaultAction": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the default action of allow or deny when no other rules match." + } + } + } + }, + "privateEndpointType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service (sub-) type to deploy the private endpoint for. For example \"vault\" or \"blob\"." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if privateDnsZoneResourceIds were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint with. A DNS zone group can support up to 5 DNS zones." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Manual PrivateLink Service Connections." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint ip address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private ip addresses of the private endpoint." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private ip address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "customerManagedKeyType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using 'latest'." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. If used must also be specified in `managedIdentities.userAssignedResourceIds`. Required if no system assigned identity is available for use." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Storage Account. Must be lower-case." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2", + "allowedValues": [ + "Storage", + "StorageV2", + "BlobStorage", + "FileStorage", + "BlockBlobStorage" + ], + "metadata": { + "description": "Optional. Type of Storage Account to create." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard_GRS", + "allowedValues": [ + "Standard_LRS", + "Standard_GRS", + "Standard_RAGRS", + "Standard_ZRS", + "Premium_LRS", + "Premium_ZRS", + "Standard_GZRS", + "Standard_RAGZRS" + ], + "metadata": { + "description": "Optional. Storage Account Sku Name." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "Hot", + "allowedValues": [ + "Premium", + "Hot", + "Cool" + ], + "metadata": { + "description": "Conditional. Required if the Storage Account kind is set to BlobStorage. The access tier is used for billing. The \"Premium\" access tier is the default value for premium block blobs storage account type and it cannot be changed for the premium block blobs storage account type." + } + }, + "largeFileSharesState": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Allow large file shares if sets to 'Enabled'. It cannot be disabled once it is enabled. Only supported on locally redundant and zone redundant file shares. It cannot be set on FileStorage storage accounts (storage accounts for premium file shares)." + } + }, + "azureFilesIdentityBasedAuthentication": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Provides the identity based authentication settings for Azure Files." + } + }, + "defaultToOAuthAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. A boolean flag which indicates whether the default authentication is OAuth or not." + } + }, + "allowSharedKeyAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether the storage account permits requests to be authorized with the account access key via Shared Key. If false, then all requests, including shared access signatures, must be authorized with Azure Active Directory (Azure AD). The default value is null, which is equivalent to true." + } + }, + "privateEndpoints": { + "$ref": "#/definitions/privateEndpointType", + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "managementPolicyRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The Storage Account ManagementPolicies Rules." + } + }, + "networkAcls": { + "$ref": "#/definitions/networkAclsType", + "nullable": true, + "metadata": { + "description": "Optional. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny." + } + }, + "requireInfrastructureEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. A Boolean indicating whether or not the service applies a secondary layer of encryption with platform managed keys for data at rest. For security reasons, it is recommended to set it to true." + } + }, + "allowCrossTenantReplication": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allow or disallow cross AAD tenant object replication." + } + }, + "customDomainName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Sets the custom domain name assigned to the storage account. Name is the CNAME source." + } + }, + "customDomainUseSubDomainName": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether indirect CName validation is enabled. This should only be set on updates." + } + }, + "dnsEndpointType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "AzureDnsZone", + "Standard" + ], + "metadata": { + "description": "Optional. Allows you to specify the type of endpoint. Set this to AzureDNSZone to create a large number of accounts in a single subscription, which creates accounts in an Azure DNS Zone and the endpoint URL will have an alphanumeric DNS Zone identifier." + } + }, + "blobServices": { + "type": "object", + "defaultValue": "[if(not(equals(parameters('kind'), 'FileStorage')), createObject('containerDeleteRetentionPolicyEnabled', true(), 'containerDeleteRetentionPolicyDays', 7, 'deleteRetentionPolicyEnabled', true(), 'deleteRetentionPolicyDays', 6), createObject())]", + "metadata": { + "description": "Optional. Blob service and containers to deploy." + } + }, + "fileServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. File service and shares to deploy." + } + }, + "queueServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Queue service and queues to create." + } + }, + "tableServices": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Table service and tables to create." + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether public access is enabled for all blobs or containers in the storage account. For security reasons, it is recommended to set it to false." + } + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "TLS1_2", + "allowedValues": [ + "TLS1_0", + "TLS1_1", + "TLS1_2" + ], + "metadata": { + "description": "Optional. Set the minimum TLS version on request to storage." + } + }, + "enableHierarchicalNamespace": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Conditional. If true, enables Hierarchical Namespace for the storage account. Required if enableSftp or enableNfsV3 is set to true." + } + }, + "enableSftp": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables Secure File Transfer Protocol for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "localUsers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Local users to deploy for SFTP authentication." + } + }, + "isLocalUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables local users feature, if set to true." + } + }, + "enableNfsV3": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables NFS 3.0 support for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "allowedCopyScope": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "AAD", + "PrivateLink" + ], + "metadata": { + "description": "Optional. Restrict copy to and from Storage Accounts within an AAD tenant or with Private Links to the same VNet." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set." + } + }, + "supportsHttpsTrafficOnly": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allows HTTPS traffic only to storage service if sets to true." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyType", + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "sasExpirationPeriod": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The SAS expiration period. DD.HH:MM:SS." + } + }, + "keyType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Account", + "Service" + ], + "metadata": { + "description": "Optional. The keyType to use with Queue & Table services." + } + } + }, + "variables": { + "supportsBlobService": "[or(or(or(equals(parameters('kind'), 'BlockBlobStorage'), equals(parameters('kind'), 'BlobStorage')), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "supportsFileService": "[or(or(equals(parameters('kind'), 'FileStorage'), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/')), coalesce(tryGet(parameters('customerManagedKey'), 'keyName'), 'dummyKey'))]", + "dependsOn": [ + "cMKKeyVault" + ] + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.9.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), 'dummyVault'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "subscriptionId": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))]" + }, + "storageAccount": { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "[parameters('kind')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "properties": { + "allowSharedKeyAccess": "[parameters('allowSharedKeyAccess')]", + "defaultToOAuthAuthentication": "[parameters('defaultToOAuthAuthentication')]", + "allowCrossTenantReplication": "[parameters('allowCrossTenantReplication')]", + "allowedCopyScope": "[if(not(empty(parameters('allowedCopyScope'))), parameters('allowedCopyScope'), null())]", + "customDomain": { + "name": "[parameters('customDomainName')]", + "useSubDomainName": "[parameters('customDomainUseSubDomainName')]" + }, + "dnsEndpointType": "[if(not(empty(parameters('dnsEndpointType'))), parameters('dnsEndpointType'), null())]", + "isLocalUserEnabled": "[parameters('isLocalUserEnabled')]", + "encryption": "[union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true(), 'keyType', parameters('keyType')), 'queue', createObject('enabled', true(), 'keyType', parameters('keyType'))), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', reference('cMKKeyVault').vaultUri, 'keyversion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), parameters('customerManagedKey').keyVersion, last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '//'), '/')[2], split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '////'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), 'dummyMsi'), '/'))), null()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject()))]", + "accessTier": "[if(and(not(equals(parameters('kind'), 'Storage')), not(equals(parameters('kind'), 'BlockBlobStorage'))), parameters('accessTier'), null())]", + "sasPolicy": "[if(not(empty(parameters('sasExpirationPeriod'))), createObject('expirationAction', 'Log', 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null())]", + "supportsHttpsTrafficOnly": "[parameters('supportsHttpsTrafficOnly')]", + "isHnsEnabled": "[if(parameters('enableHierarchicalNamespace'), parameters('enableHierarchicalNamespace'), null())]", + "isSftpEnabled": "[parameters('enableSftp')]", + "isNfsV3Enabled": "[if(parameters('enableNfsV3'), parameters('enableNfsV3'), '')]", + "largeFileSharesState": "[if(or(equals(parameters('skuName'), 'Standard_LRS'), equals(parameters('skuName'), 'Standard_ZRS')), parameters('largeFileSharesState'), null())]", + "minimumTlsVersion": "[parameters('minimumTlsVersion')]", + "networkAcls": "[if(not(empty(parameters('networkAcls'))), union(createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'defaultAction', coalesce(tryGet(parameters('networkAcls'), 'defaultAction'), 'Deny'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), if(contains(parameters('networkAcls'), 'bypass'), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass')), createObject())), createObject('bypass', 'AzureServices', 'defaultAction', 'Deny'))]", + "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkAcls'))), 'Disabled', null()))]", + "azureFilesIdentityBasedAuthentication": "[if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), parameters('azureFilesIdentityBasedAuthentication'), null())]" + }, + "dependsOn": [ + "cMKKeyVault", + "cMKUserAssignedIdentity" + ] + }, + "storageAccount_diagnosticSettings": { + "copy": { + "name": "storageAccount_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_roleAssignments": { + "copy": { + "name": "storageAccount_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_privateEndpoints": { + "copy": { + "name": "storageAccount_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-StorageAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualPrivateLinkServiceConnections'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroupName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroupName')]" + }, + "privateDnsZoneResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneResourceIds')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "4120048060064073955" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "ipConfigurationsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + } + }, + "nullable": true + }, + "manualPrivateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "privateLinkServiceConnectionsType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + } + }, + "nullable": true + }, + "customDnsConfigType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "metadata": { + "description": "Required. Fqdn that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "$ref": "#/definitions/ipConfigurationsType", + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "privateDnsZoneGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group to create if `privateDnsZoneResourceIds` were provided." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "customDnsConfigs": { + "$ref": "#/definitions/customDnsConfigType", + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource." + } + }, + "privateLinkServiceConnections": { + "$ref": "#/definitions/privateLinkServiceConnectionsType", + "metadata": { + "description": "Optional. A grouping of information about the connection to the remote resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.4.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneResourceIds')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('privateDnsZoneGroupName'), 'default')]" + }, + "privateDNSResourceIds": { + "value": "[coalesce(parameters('privateDnsZoneResourceIds'), createArray())]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.25.53.49325", + "templateHash": "11244630631275470040" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDNSResourceIds": { + "type": "array", + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone resource IDs. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDNSResourceIds'))]", + "input": { + "name": "[last(split(parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')], '/'))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDNSResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + } + ] + }, + "resources": [ + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigs')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2023-04-01', 'full').location]" + }, + "groupId": { + "type": "string", + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[if(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties.groupIds[0], reference('privateEndpoint').privateLinkServiceConnections[0].properties.groupIds[0])]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_managementPolicies": { + "condition": "[not(empty(coalesce(parameters('managementPolicyRules'), createArray())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-ManagementPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "rules": { + "value": "[coalesce(parameters('managementPolicyRules'), createArray())]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "9473195527943694039" + }, + "name": "Storage Account Management Policies", + "description": "This module deploys a Storage Account Management Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "rules": { + "type": "array", + "metadata": { + "description": "Required. The Storage Account ManagementPolicies Rules." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/managementPolicies", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "properties": { + "policy": { + "rules": "[parameters('rules')]" + } + } + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed management policy." + }, + "value": "default" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed management policy." + }, + "value": "default" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed management policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount", + "storageAccount_blobServices" + ] + }, + "storageAccount_localUsers": { + "copy": { + "name": "storageAccount_localUsers", + "count": "[length(parameters('localUsers'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-LocalUsers-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('localUsers')[copyIndex()].name]" + }, + "hasSshKey": { + "value": "[parameters('localUsers')[copyIndex()].hasSshKey]" + }, + "hasSshPassword": { + "value": "[parameters('localUsers')[copyIndex()].hasSshPassword]" + }, + "permissionScopes": { + "value": "[parameters('localUsers')[copyIndex()].permissionScopes]" + }, + "hasSharedKey": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'hasSharedKey')]" + }, + "homeDirectory": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'homeDirectory')]" + }, + "sshAuthorizedKeys": { + "value": "[tryGet(parameters('localUsers')[copyIndex()], 'sshAuthorizedKeys')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "14968464858285923305" + }, + "name": "Storage Account Local Users", + "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "sshAuthorizedKeysType": { + "type": "secureObject", + "properties": { + "secureList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "string", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + } + }, + "metadata": { + "description": "Optional. The list of SSH authorized keys." + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the local user used for SFTP Authentication." + } + }, + "hasSharedKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key." + } + }, + "hasSshKey": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key." + } + }, + "hasSshPassword": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password." + } + }, + "homeDirectory": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The local user home directory." + } + }, + "permissionScopes": { + "type": "array", + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "$ref": "#/definitions/sshAuthorizedKeysType", + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "localUsers": { + "type": "Microsoft.Storage/storageAccounts/localUsers", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "hasSharedKey": "[parameters('hasSharedKey')]", + "hasSshKey": "[parameters('hasSshKey')]", + "hasSshPassword": "[parameters('hasSshPassword')]", + "homeDirectory": "[parameters('homeDirectory')]", + "permissionScopes": "[parameters('permissionScopes')]", + "sshAuthorizedKeys": "[tryGet(parameters('sshAuthorizedKeys'), 'secureList')]" + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed local user." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed local user." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed local user." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_blobServices": { + "condition": "[not(empty(parameters('blobServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-BlobServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(parameters('blobServices'), 'containers')]" + }, + "automaticSnapshotPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'automaticSnapshotPolicyEnabled')]" + }, + "changeFeedEnabled": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedEnabled')]" + }, + "changeFeedRetentionInDays": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedRetentionInDays')]" + }, + "containerDeleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyEnabled')]" + }, + "containerDeleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyDays')]" + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyAllowPermanentDelete')]" + }, + "corsRules": { + "value": "[tryGet(parameters('blobServices'), 'corsRules')]" + }, + "defaultServiceVersion": { + "value": "[tryGet(parameters('blobServices'), 'defaultServiceVersion')]" + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyAllowPermanentDelete')]" + }, + "deleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyEnabled')]" + }, + "deleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyDays')]" + }, + "isVersioningEnabled": { + "value": "[tryGet(parameters('blobServices'), 'isVersioningEnabled')]" + }, + "lastAccessTimeTrackingPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'lastAccessTimeTrackingPolicyEnabled')]" + }, + "restorePolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyEnabled')]" + }, + "restorePolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyDays')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('blobServices'), 'diagnosticSettings')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "2306287879023715578" + }, + "name": "Storage Account blob Services", + "description": "This module deploys a Storage Account Blob Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "automaticSnapshotPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Automatic Snapshot is enabled if set to true." + } + }, + "changeFeedEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service." + } + }, + "changeFeedRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 146000, + "metadata": { + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed." + } + }, + "containerDeleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled." + } + }, + "containerDeleteRetentionPolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted item should be retained." + } + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "corsRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies CORS rules for the Blob service. You can include up to five CorsRule elements in the request. If no CorsRule elements are included in the request body, all CORS rules will be deleted, and CORS will be disabled for the Blob service." + } + }, + "defaultServiceVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions." + } + }, + "deleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for blob soft delete." + } + }, + "deleteRetentionPolicyDays": { + "type": "int", + "defaultValue": 7, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted blob should be retained." + } + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "isVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use versioning to automatically maintain previous versions of your blobs." + } + }, + "lastAccessTimeTrackingPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled." + } + }, + "restorePolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled." + } + }, + "restorePolicyDays": { + "type": "int", + "defaultValue": 6, + "minValue": 1, + "metadata": { + "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days." + } + }, + "containers": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Blob containers to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "blobServices": { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "automaticSnapshotPolicyEnabled": "[parameters('automaticSnapshotPolicyEnabled')]", + "changeFeed": "[if(parameters('changeFeedEnabled'), createObject('enabled', true(), 'retentionInDays', parameters('changeFeedRetentionInDays')), null())]", + "containerDeleteRetentionPolicy": { + "enabled": "[parameters('containerDeleteRetentionPolicyEnabled')]", + "days": "[parameters('containerDeleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(equals(parameters('containerDeleteRetentionPolicyEnabled'), true()), parameters('containerDeleteRetentionPolicyAllowPermanentDelete'), null())]" + }, + "cors": { + "corsRules": "[parameters('corsRules')]" + }, + "defaultServiceVersion": "[if(not(empty(parameters('defaultServiceVersion'))), parameters('defaultServiceVersion'), null())]", + "deleteRetentionPolicy": { + "enabled": "[parameters('deleteRetentionPolicyEnabled')]", + "days": "[parameters('deleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(and(parameters('deleteRetentionPolicyEnabled'), parameters('deleteRetentionPolicyAllowPermanentDelete')), true(), null())]" + }, + "isVersioningEnabled": "[parameters('isVersioningEnabled')]", + "lastAccessTimeTrackingPolicy": "[if(not(equals(reference('storageAccount', '2022-09-01', 'full').kind, 'Storage')), createObject('enable', parameters('lastAccessTimeTrackingPolicyEnabled'), 'name', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 'AccessTimeTracking', null()), 'trackingGranularityInDays', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 1, null())), null())]", + "restorePolicy": "[if(parameters('restorePolicyEnabled'), createObject('enabled', true(), 'days', parameters('restorePolicyDays')), null())]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "blobServices_diagnosticSettings": { + "copy": { + "name": "blobServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "blobServices" + ] + }, + "blobServices_container": { + "copy": { + "name": "blobServices_container", + "count": "[length(coalesce(parameters('containers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Container-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" + }, + "defaultEncryptionScope": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultEncryptionScope')]" + }, + "denyEncryptionScopeOverride": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'denyEncryptionScopeOverride')]" + }, + "enableNfsV3AllSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3AllSquash')]" + }, + "enableNfsV3RootSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3RootSquash')]" + }, + "immutableStorageWithVersioningEnabled": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutableStorageWithVersioningEnabled')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'metadata')]" + }, + "publicAccess": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'publicAccess')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "immutabilityPolicyProperties": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutabilityPolicyProperties')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "7045309160947869799" + }, + "name": "Storage Account Blob Containers", + "description": "This module deploys a Storage Account Blob Container.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Block override of encryption scope from the container default." + } + }, + "enableNfsV3AllSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 all squash on blob container." + } + }, + "enableNfsV3RootSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 root squash on blob container." + } + }, + "immutableStorageWithVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process." + } + }, + "immutabilityPolicyName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. Name of the immutable policy." + } + }, + "immutabilityPolicyProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Configure immutability policy." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. A name-value pair to associate with the container as metadata." + } + }, + "publicAccess": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "Container", + "Blob", + "None" + ], + "metadata": { + "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::blobServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "container": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "defaultEncryptionScope": "[if(not(empty(parameters('defaultEncryptionScope'))), parameters('defaultEncryptionScope'), null())]", + "denyEncryptionScopeOverride": "[if(equals(parameters('denyEncryptionScopeOverride'), true()), parameters('denyEncryptionScopeOverride'), null())]", + "enableNfsV3AllSquash": "[if(equals(parameters('enableNfsV3AllSquash'), true()), parameters('enableNfsV3AllSquash'), null())]", + "enableNfsV3RootSquash": "[if(equals(parameters('enableNfsV3RootSquash'), true()), parameters('enableNfsV3RootSquash'), null())]", + "immutableStorageWithVersioning": "[if(equals(parameters('immutableStorageWithVersioningEnabled'), true()), createObject('enabled', parameters('immutableStorageWithVersioningEnabled')), null())]", + "metadata": "[parameters('metadata')]", + "publicAccess": "[parameters('publicAccess')]" + }, + "dependsOn": [ + "storageAccount::blobServices" + ] + }, + "container_roleAssignments": { + "copy": { + "name": "container_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "container" + ] + }, + "immutabilityPolicy": { + "condition": "[not(empty(coalesce(parameters('immutabilityPolicyProperties'), createObject())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[parameters('immutabilityPolicyName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "containerName": { + "value": "[parameters('name')]" + }, + "immutabilityPeriodSinceCreationInDays": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'immutabilityPeriodSinceCreationInDays')]" + }, + "allowProtectedAppendWrites": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'allowProtectedAppendWrites')]" + }, + "allowProtectedAppendWritesAll": { + "value": "[tryGet(parameters('immutabilityPolicyProperties'), 'allowProtectedAppendWritesAll')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "2543276032744560941" + }, + "name": "Storage Account Blob Container Immutability Policies", + "description": "This module deploys a Storage Account Blob Container Immutability Policy.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent container to apply the policy to. Required if the template is used in a standalone deployment." + } + }, + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]", + "properties": { + "immutabilityPeriodSinceCreationInDays": "[parameters('immutabilityPeriodSinceCreationInDays')]", + "allowProtectedAppendWrites": "[parameters('allowProtectedAppendWrites')]", + "allowProtectedAppendWritesAll": "[parameters('allowProtectedAppendWritesAll')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed immutability policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed immutability policy." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed immutability policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "container", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed container." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed container." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed container." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed blob service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_fileServices": { + "condition": "[not(empty(parameters('fileServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-FileServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('fileServices'), 'diagnosticSettings')]" + }, + "protocolSettings": { + "value": "[tryGet(parameters('fileServices'), 'protocolSettings')]" + }, + "shareDeleteRetentionPolicy": { + "value": "[tryGet(parameters('fileServices'), 'shareDeleteRetentionPolicy')]" + }, + "shares": { + "value": "[tryGet(parameters('fileServices'), 'shares')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "7463227074634701879" + }, + "name": "Storage Account File Share Services", + "description": "This module deploys a Storage Account File Share Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the file service." + } + }, + "protocolSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Protocol settings for file service." + } + }, + "shareDeleteRetentionPolicy": { + "type": "object", + "defaultValue": { + "enabled": true, + "days": 7 + }, + "metadata": { + "description": "Optional. The service properties for soft delete." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "shares": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. File shares to create." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "fileServices": { + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "protocolSettings": "[parameters('protocolSettings')]", + "shareDeleteRetentionPolicy": "[parameters('shareDeleteRetentionPolicy')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "fileServices_diagnosticSettings": { + "copy": { + "name": "fileServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/fileServices/{1}', parameters('storageAccountName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "fileServices" + ] + }, + "fileServices_shares": { + "copy": { + "name": "fileServices_shares", + "count": "[length(coalesce(parameters('shares'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-shares-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "fileServicesName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" + }, + "accessTier": { + "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2023-04-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" + }, + "enabledProtocols": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]" + }, + "rootSquash": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'rootSquash')]" + }, + "shareQuota": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'shareQuota')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "1342480740201032357" + }, + "name": "Storage Account File Shares", + "description": "This module deploys a Storage Account File Share.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "fileServicesName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Conditional. The name of the parent file service. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share to create." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "TransactionOptimized", + "allowedValues": [ + "Premium", + "Hot", + "Cool", + "TransactionOptimized" + ], + "metadata": { + "description": "Conditional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool." + } + }, + "shareQuota": { + "type": "int", + "defaultValue": 5120, + "metadata": { + "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)." + } + }, + "enabledProtocols": { + "type": "string", + "defaultValue": "SMB", + "allowedValues": [ + "NFS", + "SMB" + ], + "metadata": { + "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share." + } + }, + "rootSquash": { + "type": "string", + "defaultValue": "NoRootSquash", + "allowedValues": [ + "AllSquash", + "NoRootSquash", + "RootSquash" + ], + "metadata": { + "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "resources": { + "storageAccount::fileService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "fileShare": { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]", + "properties": { + "accessTier": "[parameters('accessTier')]", + "shareQuota": "[parameters('shareQuota')]", + "rootSquash": "[if(equals(parameters('enabledProtocols'), 'NFS'), parameters('rootSquash'), null())]", + "enabledProtocols": "[parameters('enabledProtocols')]" + }, + "dependsOn": [ + "storageAccount::fileService" + ] + }, + "fileShare_roleAssignments": { + "condition": "[not(empty(parameters('roleAssignments')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Share-Rbac', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "fileShareResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "roleAssignments": { + "value": "[parameters('roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "8779226603522513073" + } + }, + "parameters": { + "roleAssignments": { + "type": "array", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "fileShareResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the file share to assign the roles to." + } + } + }, + "variables": { + "$fxv#0": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "scope": { + "type": "string", + "metadata": { + "description": "Required. The scope to deploy the role assignment to." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The role definition Id to assign." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User", + "" + ], + "defaultValue": "", + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "defaultValue": "2.0", + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[[parameters('scope')]", + "name": "[[parameters('name')]", + "properties": { + "roleDefinitionId": "[[parameters('roleDefinitionId')]", + "principalId": "[[parameters('principalId')]", + "description": "[[parameters('description')]", + "principalType": "[[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "condition": "[[if(not(empty(parameters('condition'))), parameters('condition'), null())]", + "conditionVersion": "[[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "delegatedManagedIdentityResourceId": "[[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]" + } + } + ] + }, + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": [ + { + "copy": { + "name": "fileShare_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('{0}-Share-Rbac-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "Outer" + }, + "template": "[variables('$fxv#0')]", + "parameters": { + "scope": { + "value": "[replace(parameters('fileShareResourceId'), '/shares/', '/fileShares/')]" + }, + "name": { + "value": "[guid(parameters('fileShareResourceId'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, 'tyfa')]" + }, + "roleDefinitionId": { + "value": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]" + }, + "principalId": { + "value": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]" + }, + "principalType": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]" + }, + "condition": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]" + }, + "conditionVersion": { + "value": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]" + }, + "delegatedManagedIdentityResourceId": { + "value": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + } + } + } + } + ] + } + }, + "dependsOn": [ + "fileShare" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "fileServices", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('storageAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_queueServices": { + "condition": "[not(empty(parameters('queueServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-QueueServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('queueServices'), 'diagnosticSettings')]" + }, + "queues": { + "value": "[tryGet(parameters('queueServices'), 'queues')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "10678250016540336570" + }, + "name": "Storage Account Queue Services", + "description": "This module deploys a Storage Account Queue Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "queues": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Queues to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "queueServices": { + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": {}, + "dependsOn": [ + "storageAccount" + ] + }, + "queueServices_diagnosticSettings": { + "copy": { + "name": "queueServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "queueServices" + ] + }, + "queueServices_queues": { + "copy": { + "name": "queueServices_queues", + "count": "[length(coalesce(parameters('queues'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Queue-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('queues'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'metadata')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "13487964166280180730" + }, + "name": "Storage Account Queues", + "description": "This module deploys a Storage Account Queue.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage queue to deploy." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Required. A name-value pair that represents queue metadata." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::queueServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "queue": { + "type": "Microsoft.Storage/storageAccounts/queueServices/queues", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]" + }, + "dependsOn": [ + "storageAccount::queueServices" + ] + }, + "queue_roleAssignments": { + "copy": { + "name": "queue_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}/queues/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "queue" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed queue." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed queue." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed queue." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_tableServices": { + "condition": "[not(empty(parameters('tableServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Storage-TableServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('tableServices'), 'diagnosticSettings')]" + }, + "tables": { + "value": "[tryGet(parameters('tableServices'), 'tables')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "16839054392438941735" + }, + "name": "Storage Account Table Services", + "description": "This module deploys a Storage Account Table Service.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "tables": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. tables to create." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "tableServices": { + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": {}, + "dependsOn": [ + "storageAccount" + ] + }, + "tableServices_diagnosticSettings": { + "copy": { + "name": "tableServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "tableServices" + ] + }, + "tableServices_tables": { + "copy": { + "name": "tableServices_tables", + "count": "[length(parameters('tables'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Table-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('tables')[copyIndex()].name]" + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "roleAssignments": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.28.1.47646", + "templateHash": "3177845984945141330" + }, + "name": "Storage Account Table", + "description": "This module deploys a Storage Account Table.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + } + }, + "variables": { + "builtInRoleNames": { + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::tableServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-04-01", + "name": "[parameters('storageAccountName')]" + }, + "table": { + "type": "Microsoft.Storage/storageAccounts/tableServices/tables", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "dependsOn": [ + "storageAccount::tableServices" + ] + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}/tables/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "table" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed table service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed table service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed table service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage account." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed storage account." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed storage account." + }, + "value": "[resourceGroup().name]" + }, + "primaryBlobEndpoint": { + "type": "string", + "metadata": { + "description": "The primary blob endpoint reference if blob services are deployed." + }, + "value": "[if(and(not(empty(parameters('blobServices'))), contains(parameters('blobServices'), 'containers')), reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('name')), '2019-04-01').primaryEndpoints.blob, '')]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[coalesce(tryGet(tryGet(reference('storageAccount', '2022-09-01', 'full'), 'identity'), 'principalId'), '')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('storageAccount', '2022-09-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "dsMsi", + "rg", + "storageFileDataPrivilegedContributorRole", + "vnet" + ] + }, + "storageAccount_upload": { + "condition": "[or(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only base')), equals(parameters('deploymentsToPerform'), 'Only assets & image'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-storage-upload-ds', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', parameters('storageDeploymentScriptName'), variables('formattedTime'))]" + }, + "kind": { + "value": "AzurePowerShell" + }, + "azPowerShellVersion": { + "value": "12.0" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.ManagedIdentity/userAssignedIdentities', parameters('deploymentScriptManagedIdentityName'))]" + ] + } + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": "[map(coalesce(parameters('storageAccountFilesToUpload'), createArray()), lambda('file', createObject('name', format('__SCRIPT__{0}', replace(replace(lambdaVariables('file').name, '-', '__'), '.', '_')), 'value', tryGet(lambdaVariables('file'), 'value'), 'secureValue', tryGet(lambdaVariables('file'), 'secureValue'))))]" + }, + "arguments": { + "value": "[format(' -StorageAccountName \"{0}\" -TargetContainer \"{1}\"', parameters('assetsStorageAccountName'), parameters('assetsStorageAccountContainerName'))]" + }, + "timeout": { + "value": "PT30M" + }, + "cleanupPreference": { + "value": "Always" + }, + "location": { + "value": "[parameters('location')]" + }, + "storageAccountResourceId": { + "value": "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Storage/storageAccounts', parameters('deploymentScriptStorageAccountName'))]" + }, + "subnetResourceIds": { + "value": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('deploymentScriptSubnetName'))]" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "10563518610969019714" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the environment variable." + } + }, + "secureValue": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Required. The value of the secure environment variable." + } + }, + "value": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The value of the environment variable." + } + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 90, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/environmentVariableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The environment variables to pass over to the script." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "defaultValue": "P1D", + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + }, + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "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')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[parameters('environmentVariables')]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScriptLogs": { + "existing": true, + "type": "Microsoft.Resources/deploymentScripts/logs", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "dependsOn": [ + "deploymentScript" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[coalesce(tryGet(reference('deploymentScript'), 'outputs'), createObject())]" + }, + "deploymentScriptLogs": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The logs of the deployment script." + }, + "value": "[split(reference('deploymentScriptLogs').log, '\n')]" + } + } + } + }, + "dependsOn": [ + "assetsStorageAccount", + "dsMsi", + "dsStorageAccount", + "rg", + "vnet" + ] + }, + "imageTemplate": { + "condition": "[or(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only assets & image')), equals(parameters('deploymentsToPerform'), 'Only image'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-it', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "customizationSteps": { + "value": "[parameters('imageTemplateCustomizationSteps')]" + }, + "imageSource": { + "value": "[parameters('imageTemplateImageSource')]" + }, + "name": { + "value": "[parameters('imageTemplateName')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "managedIdentities": { + "value": { + "userAssignedResourceIds": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.ManagedIdentity/userAssignedIdentities', parameters('imageManagedIdentityName'))]" + ] + } + }, + "distributions": { + "value": [ + { + "type": "SharedImage", + "sharedImageGalleryImageDefinitionResourceId": "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Compute/galleries/images', parameters('computeGalleryName'), parameters('computeGalleryImageDefinitionName'))]" + } + ] + }, + "subnetResourceId": { + "value": "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('imageSubnetName'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "stagingResourceGroupResourceId": { + "value": "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('imageTemplateResourceGroupName'))]" + }, + "roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "Contributor", + "principalId": "[if(or(equals(parameters('deploymentsToPerform'), 'Only assets & image'), equals(parameters('deploymentsToPerform'), 'Only image')), reference('dsMsi_existing').principalId, reference('dsMsi').outputs.principalId.value)]", + "principalType": "ServicePrincipal" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "3107459634056863407" + }, + "name": "Virtual Machine Image Templates", + "description": "This module deploys a Virtual Machine Image Template that can be consumed by Azure Image Builder (AIB).", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + } + }, + "distributionType": { + "type": "object", + "discriminator": { + "propertyName": "type", + "mapping": { + "SharedImage": { + "$ref": "#/definitions/sharedImageDistributionType" + }, + "ManagedImage": { + "$ref": "#/definitions/managedImageDistributionType" + }, + "VHD": { + "$ref": "#/definitions/unManagedDistributionType" + } + } + } + }, + "sharedImageDistributionType": { + "type": "object", + "properties": { + "runOutputName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name to be used for the associated RunOutput. If not provided, a name will be calculated." + } + }, + "artifactTags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags that will be applied to the artifact once it has been created/updated by the distributor. If not provided will set tags based on the provided image source." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "SharedImage" + ], + "metadata": { + "description": "Required. The type of distribution." + } + }, + "sharedImageGalleryImageDefinitionResourceId": { + "type": "string", + "metadata": { + "description": "Conditional. Resource ID of Compute Gallery Image Definition to distribute image to, e.g.: /subscriptions//resourceGroups//providers/Microsoft.Compute/galleries//images/." + } + }, + "sharedImageGalleryImageDefinitionTargetVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Version of the Compute Gallery Image. Supports the following Version Syntax: Major.Minor.Build (i.e., '1.1.1' or '10.1.2'). If not provided, a version will be calculated." + } + }, + "excludeFromLatest": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The exclude from latest flag of the image. Defaults to [false]." + } + }, + "replicationRegions": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The replication regions of the image. Defaults to the value of the 'location' parameter." + } + }, + "storageAccountType": { + "type": "string", + "allowedValues": [ + "Standard_LRS", + "Standard_ZRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. The storage account type of the image. Defaults to [Standard_LRS]." + } + } + } + }, + "unManagedDistributionType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "VHD" + ], + "metadata": { + "description": "Required. The type of distribution." + } + }, + "runOutputName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name to be used for the associated RunOutput. If not provided, a name will be calculated." + } + }, + "artifactTags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags that will be applied to the artifact once it has been created/updated by the distributor. If not provided will set tags based on the provided image source." + } + }, + "imageName": { + "type": "string", + "metadata": { + "description": "Conditional. Name of the managed or unmanaged image that will be created." + } + } + } + }, + "managedImageDistributionType": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "ManagedImage" + ], + "metadata": { + "description": "Required. The type of distribution." + } + }, + "runOutputName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name to be used for the associated RunOutput. If not provided, a name will be calculated." + } + }, + "artifactTags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags that will be applied to the artifact once it has been created/updated by the distributor. If not provided will set tags based on the provided image source." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure location for the image, should match if image already exists. Defaults to the value of the 'location' parameter." + } + }, + "imageResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The resource ID of the managed image. Defaults to a compute image with name 'imageName-baseTime' in the current resource group." + } + }, + "imageName": { + "type": "string", + "metadata": { + "description": "Conditional. Name of the managed or unmanaged image that will be created." + } + } + } + }, + "validationProcessType": { + "type": "object", + "properties": { + "continueDistributeOnFailure": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If validation fails and this field is set to false, output image(s) will not be distributed. This is the default behavior. If validation fails and this field is set to true, output image(s) will still be distributed. Please use this option with caution as it may result in bad images being distributed for use. In either case (true or false), the end to end image run will be reported as having failed in case of a validation failure. [Note: This field has no effect if validation succeeds.]." + } + }, + "inVMValidations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "File", + "PowerShell", + "Shell" + ], + "metadata": { + "description": "Required. The type of validation." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Friendly Name to provide context on what this validation step does." + } + }, + "scriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. URI of the PowerShell script to be run for validation. It can be a github link, Azure Storage URI, etc." + } + }, + "inline": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of commands to be run, separated by commas." + } + }, + "validExitCodes": { + "type": "array", + "items": { + "type": "int" + }, + "nullable": true, + "metadata": { + "description": "Optional. Valid codes that can be returned from the script/inline command, this avoids reported failure of the script/inline command." + } + }, + "sha256Checksum": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Value of sha256 checksum of the file, you generate this locally, and then Image Builder will checksum and validate." + } + }, + "sourceUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source URI of the file." + } + }, + "destination": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Destination of the file." + } + }, + "runAsSystem": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If specified, the PowerShell script will be run with elevated privileges using the Local System user. Can only be true when the runElevated field above is set to true." + } + }, + "runElevated": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If specified, the PowerShell script will be run with elevated privileges." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of validators that will be performed on the image. Azure Image Builder supports File, PowerShell and Shell validators." + } + }, + "sourceValidationOnly": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If this field is set to true, the image specified in the 'source' section will directly be validated. No separate build will be run to generate and then validate a customized image. Not supported when performing customizations, validations or distributions on the image." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name prefix of the Image Template to be built by the Azure Image Builder service." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "buildTimeoutInMinutes": { + "type": "int", + "defaultValue": 0, + "minValue": 0, + "maxValue": 960, + "metadata": { + "description": "Optional. The image build timeout in minutes. 0 means the default 240 minutes." + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_D2s_v3", + "metadata": { + "description": "Optional. Specifies the size for the VM." + } + }, + "osDiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "Optional. Specifies the size of OS disk." + } + }, + "subnetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of an already existing subnet, e.g.: /subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks//subnets/.

If no value is provided, a new temporary VNET and subnet will be created in the staging resource group and will be deleted along with the remaining temporary resources." + } + }, + "imageSource": { + "type": "object", + "metadata": { + "description": "Required. Image source definition in object format." + } + }, + "customizationSteps": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Customization steps to be run when building the VM image." + } + }, + "stagingResourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the staging resource group in the same subscription and location as the image template that will be used to build the image.

If this field is empty, a resource group with a random name will be created.

If the resource group specified in this field doesn't exist, it will be created with the same name.

If the resource group specified exists, it must be empty and in the same region as the image template.

The resource group created will be deleted during template deletion if this field is empty or the resource group specified doesn't exist,

but if the resource group specified exists the resources created in the resource group will be deleted during template deletion and the resource group itself will remain." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to generate a unique image template name." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "distributions": { + "type": "array", + "items": { + "$ref": "#/definitions/distributionType" + }, + "metadata": { + "description": "Required. The distribution targets where the image output needs to go to." + } + }, + "vmUserAssignedIdentities": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of User-Assigned Identities associated to the Build VM for accessing Azure resources such as Key Vaults from your customizer scripts. Be aware, the user assigned identities specified in the 'managedIdentities' parameter must have the 'Managed Identity Operator' role assignment on all the user assigned identities specified in this parameter for Azure Image Builder to be able to associate them to the build VM." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Required. The managed identity definition for this resource." + } + }, + "validationProcess": { + "$ref": "#/definitions/validationProcessType", + "metadata": { + "description": "Optional. Configuration options and list of validations to be performed on the resulting image." + } + }, + "optimizeVmBoot": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The optimize property can be enabled while creating a VM image and allows VM optimization to improve image creation time." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]" + }, + "builtInRoleNames": { + "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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.virtualmachineimages-imagetemplate.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "imageTemplate": { + "type": "Microsoft.VirtualMachineImages/imageTemplates", + "apiVersion": "2023-07-01", + "name": "[format('{0}-{1}', parameters('name'), parameters('baseTime'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "copy": [ + { + "name": "distribute", + "count": "[length(parameters('distributions'))]", + "input": "[union(createObject('type', parameters('distributions')[copyIndex('distribute')].type, 'artifactTags', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'artifactTags'), createObject('sourceType', parameters('imageSource').type, 'sourcePublisher', tryGet(parameters('imageSource'), 'publisher'), 'sourceOffer', tryGet(parameters('imageSource'), 'offer'), 'sourceSku', tryGet(parameters('imageSource'), 'sku'), 'sourceVersion', tryGet(parameters('imageSource'), 'version'), 'sourceImageId', tryGet(parameters('imageSource'), 'imageId'), 'sourceImageVersionID', tryGet(parameters('imageSource'), 'imageVersionID'), 'creationTime', parameters('baseTime')))), if(equals(parameters('distributions')[copyIndex('distribute')].type, 'ManagedImage'), createObject('runOutputName', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'runOutputName'), format('{0}-{1}-ManagedImage', parameters('distributions')[copyIndex('distribute')].imageName, parameters('baseTime'))), 'location', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'location'), parameters('location')), 'imageId', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'imageResourceId'), format('{0}/resourceGroups/{1}/providers/Microsoft.Compute/images/{2}-{3}', subscription().id, resourceGroup().name, parameters('distributions')[copyIndex('distribute')].imageName, parameters('baseTime')))), createObject()), if(equals(parameters('distributions')[copyIndex('distribute')].type, 'SharedImage'), createObject('runOutputName', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'runOutputName'), if(not(empty(tryGet(parameters('distributions')[copyIndex('distribute')], 'sharedImageGalleryImageDefinitionResourceId'))), format('{0}-SharedImage', last(split(coalesce(parameters('distributions')[copyIndex('distribute')].sharedImageGalleryImageDefinitionResourceId, '/'), '/'))), 'SharedImage')), 'galleryImageId', if(not(empty(tryGet(parameters('distributions')[copyIndex('distribute')], 'sharedImageGalleryImageDefinitionTargetVersion'))), format('{0}/versions/{1}', parameters('distributions')[copyIndex('distribute')].sharedImageGalleryImageDefinitionResourceId, parameters('distributions')[copyIndex('distribute')].sharedImageGalleryImageDefinitionTargetVersion), parameters('distributions')[copyIndex('distribute')].sharedImageGalleryImageDefinitionResourceId), 'excludeFromLatest', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'excludeFromLatest'), false()), 'replicationRegions', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'replicationRegions'), createArray(parameters('location'))), 'storageAccountType', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'storageAccountType'), 'Standard_LRS')), createObject()), if(equals(parameters('distributions')[copyIndex('distribute')].type, 'VHD'), createObject('runOutputName', coalesce(tryGet(parameters('distributions')[copyIndex('distribute')], 'runOutputName'), format('{0}-VHD', parameters('distributions')[copyIndex('distribute')].imageName))), createObject()))]" + } + ], + "buildTimeoutInMinutes": "[parameters('buildTimeoutInMinutes')]", + "vmProfile": { + "vmSize": "[parameters('vmSize')]", + "osDiskSizeGB": "[parameters('osDiskSizeGB')]", + "userAssignedIdentities": "[parameters('vmUserAssignedIdentities')]", + "vnetConfig": "[if(not(empty(parameters('subnetResourceId'))), createObject('subnetId', parameters('subnetResourceId')), null())]" + }, + "source": "[parameters('imageSource')]", + "customize": "[parameters('customizationSteps')]", + "stagingResourceGroup": "[parameters('stagingResourceGroupResourceId')]", + "validate": "[parameters('validationProcess')]", + "optimize": "[if(not(equals(parameters('optimizeVmBoot'), null())), createObject('vmBoot', createObject('state', parameters('optimizeVmBoot'))), null())]" + } + }, + "imageTemplate_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.VirtualMachineImages/imageTemplates/{0}', format('{0}-{1}', parameters('name'), parameters('baseTime')))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "imageTemplate" + ] + }, + "imageTemplate_roleAssignments": { + "copy": { + "name": "imageTemplate_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.VirtualMachineImages/imageTemplates/{0}', format('{0}-{1}', parameters('name'), parameters('baseTime')))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.VirtualMachineImages/imageTemplates', format('{0}-{1}', parameters('name'), parameters('baseTime'))), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "imageTemplate" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the image template." + }, + "value": "[resourceId('Microsoft.VirtualMachineImages/imageTemplates', format('{0}-{1}', parameters('name'), parameters('baseTime')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the image template was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The full name of the deployed image template." + }, + "value": "[format('{0}-{1}', parameters('name'), parameters('baseTime'))]" + }, + "namePrefix": { + "type": "string", + "metadata": { + "description": "The prefix of the image template name provided as input." + }, + "value": "[parameters('name')]" + }, + "runThisCommand": { + "type": "string", + "metadata": { + "description": "The command to run in order to trigger the image build." + }, + "value": "[format('Invoke-AzResourceAction -ResourceName {0} -ResourceGroupName {1} -ResourceType Microsoft.VirtualMachineImages/imageTemplates -Action Run -Force', format('{0}-{1}', parameters('name'), parameters('baseTime')), resourceGroup().name)]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('imageTemplate', '2023-07-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "azureComputeGallery", + "dsMsi", + "dsMsi_existing", + "imageMSI", + "imageMSI_rbac", + "imageTemplateRg", + "rg", + "storageAccount_upload", + "vnet" + ] + }, + "imageTemplate_trigger": { + "condition": "[or(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only assets & image')), equals(parameters('deploymentsToPerform'), 'Only image'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-imageTemplate-trigger-ds', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}-{2}', parameters('imageTemplateDeploymentScriptName'), variables('formattedTime'), if(or(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only assets & image')), equals(parameters('deploymentsToPerform'), 'Only image')), reference('imageTemplate').outputs.name.value, ''))]" + }, + "kind": { + "value": "AzurePowerShell" + }, + "azPowerShellVersion": { + "value": "12.0" + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.ManagedIdentity/userAssignedIdentities', parameters('deploymentScriptManagedIdentityName'))]" + ] + } + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "scriptContent": "[if(or(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only assets & image')), equals(parameters('deploymentsToPerform'), 'Only image')), createObject('value', reference('imageTemplate').outputs.runThisCommand.value), createObject('value', ''))]", + "timeout": { + "value": "PT30M" + }, + "cleanupPreference": { + "value": "Always" + }, + "location": { + "value": "[parameters('location')]" + }, + "storageAccountResourceId": { + "value": "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Storage/storageAccounts', parameters('deploymentScriptStorageAccountName'))]" + }, + "subnetResourceIds": { + "value": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('deploymentScriptSubnetName'))]" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "10563518610969019714" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the environment variable." + } + }, + "secureValue": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Required. The value of the secure environment variable." + } + }, + "value": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The value of the environment variable." + } + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 90, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/environmentVariableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The environment variables to pass over to the script." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "defaultValue": "P1D", + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + }, + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "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')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[parameters('environmentVariables')]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScriptLogs": { + "existing": true, + "type": "Microsoft.Resources/deploymentScripts/logs", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "dependsOn": [ + "deploymentScript" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[coalesce(tryGet(reference('deploymentScript'), 'outputs'), createObject())]" + }, + "deploymentScriptLogs": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The logs of the deployment script." + }, + "value": "[split(reference('deploymentScriptLogs').log, '\n')]" + } + } + } + }, + "dependsOn": [ + "dsMsi", + "dsStorageAccount", + "imageTemplate", + "rg", + "storageAccount_upload", + "vnet" + ] + }, + "imageTemplate_wait": { + "condition": "[and(parameters('waitForImageBuild'), or(or(equals(parameters('deploymentsToPerform'), 'All'), equals(parameters('deploymentsToPerform'), 'Only assets & image')), equals(parameters('deploymentsToPerform'), 'Only image')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-imageTemplate-wait-ds', deployment().name)]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', parameters('waitDeploymentScriptName'), variables('formattedTime'))]" + }, + "kind": { + "value": "AzurePowerShell" + }, + "azPowerShellVersion": { + "value": "12.0" + }, + "managedIdentities": { + "value": { + "userAssignedResourcesIds": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.ManagedIdentity/userAssignedIdentities', parameters('deploymentScriptManagedIdentityName'))]" + ] + } + }, + "scriptContent": { + "value": "[variables('$fxv#1')]" + }, + "arguments": { + "value": "[format(' -ImageTemplateName \"{0}\" -ResourceGroupName \"{1}\"', reference('imageTemplate').outputs.name.value, parameters('resourceGroupName'))]" + }, + "timeout": { + "value": "[parameters('waitForImageBuildTimeout')]" + }, + "cleanupPreference": { + "value": "Always" + }, + "location": { + "value": "[parameters('location')]" + }, + "storageAccountResourceId": { + "value": "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Storage/storageAccounts', parameters('deploymentScriptStorageAccountName'))]" + }, + "subnetResourceIds": { + "value": [ + "[resourceId(subscription().subscriptionId, parameters('resourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('deploymentScriptSubnetName'))]" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "10563518610969019714" + }, + "name": "Deployment Scripts", + "description": "This module deploys Deployment Scripts.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "userAssignedResourcesIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "environmentVariableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the environment variable." + } + }, + "secureValue": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Required. The value of the secure environment variable." + } + }, + "value": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Required. The value of the environment variable." + } + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 90, + "metadata": { + "description": "Required. Name of the Deployment Script." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AzureCLI", + "AzurePowerShell" + ], + "metadata": { + "description": "Required. Specifies the Kind of the Deployment Script." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "azPowerShellVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." + } + }, + "azCliVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." + } + }, + "scriptContent": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." + } + }, + "primaryScriptUri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/environmentVariableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The environment variables to pass over to the script." + } + }, + "supportingScriptUris": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." + } + }, + "subnetResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." + } + }, + "arguments": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." + } + }, + "retentionInterval": { + "type": "string", + "defaultValue": "P1D", + "metadata": { + "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." + } + }, + "runOnce": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." + } + }, + "cleanupPreference": { + "type": "string", + "defaultValue": "Always", + "allowedValues": [ + "Always", + "OnSuccess", + "OnExpiration" + ], + "metadata": { + "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." + } + }, + "containerGroupName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." + } + }, + "timeout": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + }, + { + "name": "subnetIds", + "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" + } + } + ], + "builtInRoleNames": { + "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')]" + }, + "containerSettings": { + "containerGroupName": "[parameters('containerGroupName')]", + "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourcesIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2021-04-01", + "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", + "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "deploymentScript": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[parameters('kind')]", + "properties": { + "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", + "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", + "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", + "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", + "arguments": "[parameters('arguments')]", + "environmentVariables": "[parameters('environmentVariables')]", + "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", + "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", + "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", + "cleanupPreference": "[parameters('cleanupPreference')]", + "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", + "retentionInterval": "[parameters('retentionInterval')]", + "timeout": "[parameters('timeout')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "deploymentScript_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScript_roleAssignments": { + "copy": { + "name": "deploymentScript_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "deploymentScript" + ] + }, + "deploymentScriptLogs": { + "existing": true, + "type": "Microsoft.Resources/deploymentScripts/logs", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "dependsOn": [ + "deploymentScript" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployment script." + }, + "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the deployment script was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployment script." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" + }, + "outputs": { + "type": "object", + "metadata": { + "description": "The output of the deployment script." + }, + "value": "[coalesce(tryGet(reference('deploymentScript'), 'outputs'), createObject())]" + }, + "deploymentScriptLogs": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The logs of the deployment script." + }, + "value": "[split(reference('deploymentScriptLogs').log, '\n')]" + } + } + } + }, + "dependsOn": [ + "dsMsi", + "dsStorageAccount", + "imageTemplate", + "imageTemplate_trigger", + "rg", + "vnet" + ] + } + } +} \ No newline at end of file diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/AzureComputeGalleries.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/AzureComputeGalleries.svg new file mode 100644 index 00000000000..f6b18793617 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/AzureComputeGalleries.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Deployment-Script.png b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Deployment-Script.png new file mode 100644 index 0000000000000000000000000000000000000000..e453471d13ca01ff8062f26acc0c4d92b99e3ed6 GIT binary patch literal 22186 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 cKMxtb8*Npk|DO5} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Managed-identities.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Managed-identities.svg new file mode 100644 index 00000000000..de5e4f48a31 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Managed-identities.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + Icon-identity-227 + + + + + + + + + + + + + + + + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Network-Security-Groups.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Network-Security-Groups.svg new file mode 100644 index 00000000000..a55b053a8a8 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Network-Security-Groups.svg @@ -0,0 +1,9 @@ +Icon-networking-67 + + + public:true + sdk:false + category: Networking + + + \ No newline at end of file diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Resource-Groups.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Resource-Groups.svg new file mode 100644 index 00000000000..c99d7b5952d --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Resource-Groups.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Storage-Accounts.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Storage-Accounts.svg new file mode 100644 index 00000000000..2fc8eb6c528 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Storage-Accounts.svg @@ -0,0 +1,22 @@ + + + + + + + + + Icon-storage-86 + + + + + + + + public:true + sdk:MsPortalFx.Base.Images.Polychromatic.Storage() + category: Storage + + + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageDefinitions.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageDefinitions.svg new file mode 100644 index 00000000000..908c8d70846 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageDefinitions.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageVersions.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageVersions.svg new file mode 100644 index 00000000000..a93a8aafa31 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/VMImageVersions.svg @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Δ + + + + + + + + + + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Machine-Scale-Sets.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Machine-Scale-Sets.svg new file mode 100644 index 00000000000..90fea8cf2c4 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Machine-Scale-Sets.svg @@ -0,0 +1,9 @@ +Icon-compute-34 + + + public:true + sdk:false + category: Compute + + + \ No newline at end of file diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Networks.svg b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Networks.svg new file mode 100644 index 00000000000..e2d8868d8a8 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/src/icons/Virtual-Networks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/src/image/imageBuilderimage.png b/avm/ptn/virtual-machine-images/azure-image-builder/src/image/imageBuilderimage.png new file mode 100644 index 0000000000000000000000000000000000000000..23fcca251b78bae7c90944ad7274877d4afa72d9 GIT binary patch literal 211739 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

^s^_g+{z-0eW1)1 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 GGywq24_(9n literal 0 HcmV?d00001 diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/defaults/main.test.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/defaults/main.test.bicep new file mode 100644 index 00000000000..a96ed2e5b9a --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,58 @@ +targetScope = 'subscription' + +metadata name = 'Using small parameter set' +metadata description = 'This instance deploys the module with min features enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-virtualmachineimages.azureimagebuilder-${serviceShort}-rg' + +@description('Optional. The location to deploy resource group to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'apvmiaibmin' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +///////////////////////////// +// Template Deployment // +///////////////////////////// +var computeGalleryImageDefinitionName = 'sid-linux' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + deploymentsToPerform: iteration == 'init' ? 'All' : 'Only base' // Restricting to only infra on re-run as we don't want to back 2 images but only test idempotency + resourceGroupName: resourceGroupName + location: resourceLocation + computeGalleryName: 'gal${namePrefix}${serviceShort}' + computeGalleryImageDefinitionName: computeGalleryImageDefinitionName + assetsStorageAccountName: 'st${namePrefix}${serviceShort}' + computeGalleryImageDefinitions: [ + { + hyperVGeneration: 'V2' + name: 'sid-linux' + osType: 'Linux' + publisher: 'devops' + offer: 'devops_linux' + sku: 'devops_linux_az' + } + ] + imageTemplateImageSource: { + type: 'PlatformImage' + publisher: 'canonical' + offer: 'ubuntu-24_04-lts' + sku: 'server' + version: 'latest' + } + } + } +] diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/main.test.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/main.test.bicep new file mode 100644 index 00000000000..81918c04b01 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/main.test.bicep @@ -0,0 +1,136 @@ +targetScope = 'subscription' + +metadata name = 'Deploying all resources' +metadata description = 'This instance deploys the module with the conditions set up to deploy all resource and build the image.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-virtualmachineimages.azureimagebuilder-${serviceShort}-rg' + +@description('Optional. The location to deploy resource group to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'apvmiaiba' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +///////////////////////////// +// Template Deployment // +///////////////////////////// +var computeGalleryImageDefinitionName = 'sid-linux' +var assetsStorageAccountName = 'st${namePrefix}${serviceShort}' +var assetsStorageAccountContainerName = 'aibscripts' +var installPwshScriptName = 'Install-LinuxPowerShell.sh' +var initializeSoftwareScriptName = 'Initialize-LinuxSoftware.ps1' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + deploymentsToPerform: iteration == 'init' ? 'All' : 'Only base' // Restricting to only infra on re-run as we don't want to back 2 images but only test idempotency + resourceGroupName: resourceGroupName + location: resourceLocation + assetsStorageAccountName: assetsStorageAccountName + assetsStorageAccountContainerName: assetsStorageAccountContainerName + computeGalleryName: 'gal${namePrefix}${serviceShort}' + computeGalleryImageDefinitionName: computeGalleryImageDefinitionName + computeGalleryImageDefinitions: [ + { + hyperVGeneration: 'V2' + name: computeGalleryImageDefinitionName + osType: 'Linux' + publisher: 'devops' + offer: 'devops_linux' + sku: 'devops_linux_az' + } + ] + storageAccountFilesToUpload: [ + { + name: installPwshScriptName + value: loadTextContent('scripts/${installPwshScriptName}') + } + { + name: initializeSoftwareScriptName + value: loadTextContent('scripts/${initializeSoftwareScriptName}') + } + ] + imageTemplateImageSource: { + type: 'PlatformImage' + publisher: 'canonical' + offer: '0001-com-ubuntu-server-jammy' + sku: '22_04-lts-gen2' + version: 'latest' + } + imageTemplateCustomizationSteps: [ + { + type: 'Shell' + name: 'PowerShell installation' + scriptUri: 'https://${assetsStorageAccountName}.blob.${environment().suffixes.storage}/${assetsStorageAccountContainerName}/${installPwshScriptName}' + } + { + type: 'File' + name: 'Download ${initializeSoftwareScriptName}' + sourceUri: 'https://${assetsStorageAccountName}.blob.${environment().suffixes.storage}/${assetsStorageAccountContainerName}/${initializeSoftwareScriptName}' + destination: initializeSoftwareScriptName + } + { + type: 'Shell' + name: 'Software installation' + inline: [ + 'pwsh \'${initializeSoftwareScriptName}\'' + ] + } + ] + + // Windoes example + // var installPwshScriptName = 'Install-WindowsPowerShell.ps1' + // var initializeSoftwareScriptName = 'Initialize-WindowsSoftware.ps1' + // computeGalleryImageDefinitions: [ + // { + // hyperVGeneration: 'V2' + // name: 'sid-windows' + // osType: 'Windows' + // publisher: 'devops' + // offer: 'devops_windows' + // sku: 'devops_windows_az' + // } + // ] + // imageTemplateImageSource: { + // type: 'PlatformImage' + // publisher: 'microsoftwindowsdesktop' + // offer: 'windows-11' + // sku: 'win11-23h2-pro' + // version: 'latest' + // } + // imageTemplateCustomizationSteps: [ + // { + // type: 'PowerShell' + // name: 'PowerShell installation' + // scriptUri: 'https://${assetsStorageAccountName}.blob.${environment().suffixes.storage}/${assetsStorageAccountContainerName}/${installPwshScriptName}' + // runElevated: true + // } + // { + // type: 'File' + // name: 'Download ${initializeSoftwareScriptName}' + // sourceUri: 'https://${assetsStorageAccountName}.blob.${environment().suffixes.storage}/${assetsStorageAccountContainerName}/${initializeSoftwareScriptName}' + // destination: initializeSoftwareScriptName + // } + // { + // type: 'PowerShell' + // name: 'Software installation' + // inline: [ + // 'pwsh \'${initializeSoftwareScriptName}\'' + // ] + // runElevated: true + // } + // ] + } + } +] diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-LinuxSoftware.ps1 b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-LinuxSoftware.ps1 new file mode 100644 index 00000000000..ace80ec04cf --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-LinuxSoftware.ps1 @@ -0,0 +1,538 @@ +#region Functions +function LogInfo($message) { + Log 'Info' $message +} +function LogError($message) { + Log 'Error' $message +} +function LogWarning($message) { + Log 'Warning' $message +} + +function Log { + + <# + .SYNOPSIS + Creates a log file and stores logs based on categories with tab seperation + + .PARAMETER category + Category to put into the trace + + .PARAMETER message + Message to be loged + + .EXAMPLE + Log 'Info' 'Message' + + #> + + Param ( + [Parameter(Mandatory = $false)] + [string] $category = 'Info', + + [Parameter(Mandatory = $true)] + [string] $message + ) + + $date = Get-Date + $content = "[$date]`t$category`t`t$message`n" + Write-Verbose $Content -Verbose + + $FilePath = Join-Path ([System.IO.Path]::GetTempPath()) 'log.log' + if (-not (Test-Path $FilePath)) { + Write-Verbose "Log file not found, create new in path: [$FilePath]" -Verbose + $null = New-Item -ItemType 'File' -Path $FilePath -Force + } + Add-Content -Path $FilePath -Value $content -ErrorAction 'Stop' +} + +function Copy-FileAndFolderList { + + param( + [string] $sourcePath, + [string] $targetPath + ) + + $itemsFrom = Get-ChildItem $sourcePath + foreach ($item in $itemsFrom) { + if ($item.PSIsContainer) { + $subsourcePath = $sourcePath + '\' + $item.BaseName + $subtargetPath = $targetPath + '\' + $item.BaseName + $null = Copy-FileAndFolderList -sourcePath $subsourcePath -targetPath $subtargetPath + } else { + $sourceItemPath = $sourcePath + '\' + $item.Name + $targetItemPath = $targetPath + '\' + $item.Name + if (-not (Test-Path $targetItemPath)) { + # only copies non-existing files + if (-not (Test-Path $targetPath)) { + # if folder doesn't exist, creates it + $null = New-Item -ItemType 'directory' -Path $targetPath + } + $null = Copy-Item $sourceItemPath $targetItemPath + } else { + Write-Verbose "[$sourceItemPath] already exists" + } + } + } +} + +function Install-CustomModule { + + <# + .SYNOPSIS + Installes given PowerShell modules + + .DESCRIPTION + Installes given PowerShell modules + + .PARAMETER Module + Required. Modules to be installed, must be Object + @{ + Name = 'Name' + Version = '1.0.0' # Optional + } + + .PARAMETER InstalledModuleList + Optional. Modules that are already installed on the machine. Can be fetched via 'Get-Module -ListAvailable' + + .EXAMPLE + Install-CustomModule @{ Name = 'Pester' } C:\Modules + + Installes pester and saves it to C:\Modules + #> + + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Hashtable] $Module, + + [Parameter(Mandatory = $false)] + [object[]] $InstalledModuleList = @() + ) + + # Remove exsisting module in session + if (Get-Module $Module -ErrorAction 'SilentlyContinue') { + try { + Remove-Module $Module -Force + } catch { + LogError('Unable to remove module [{0}] because of exception [{1}]. Stack Trace: [{2}]' -f $Module.Name, $_.Exception, $_.ScriptStackTrace) + } + } + + # Install found module + $moduleImportInputObject = @{ + name = $Module.Name + Repository = 'PSGallery' + } + if ($Module.Version) { + $moduleImportInputObject['RequiredVersion'] = $Module.Version + } + + # Get all modules that match a certain name. In case of e.g. 'Az' it returns several. + $foundModules = Find-Module @moduleImportInputObject + + foreach ($foundModule in $foundModules) { + + # Check if already installed as required + if ($alreadyInstalled = $InstalledModule | Where-Object { $_.Name -eq $Module.Name }) { + if ($Module.Version) { + $alreadyInstalled = $alreadyInstalled | Where-Object { $_.Version -eq $Module.Version } + } else { + # Get latest in case of multiple + $alreadyInstalled = ($alreadyInstalled | Sort-Object -Property Version -Descending)[0] + } + LogInfo('[{0}] Module is already installed with version [{1}]' -f $alreadyInstalled.Name, $alreadyInstalled.Version) -Verbose + continue + } + + # Check if not to be excluded + if ($Module.ExcludeModules -and $Module.excludeModules.contains($foundModule.Name)) { + LogInfo('[{0}] Module is configured to be ignored.' -f $foundModule.Name) -Verbose + continue + } + + if ($PSCmdlet.ShouldProcess('Module [{0}]' -f $foundModule.Name, 'Install')) { + $dependenciesAlreadyAvailable = Get-AreDependenciesAvailable -InstalledModuleList $InstalledModuleList -Module $foundModule + if ($dependenciesAlreadyAvailable) { + LogInfo('[{0}] Install module with version [{1}] exluding dependencies.' -f $foundModule.Name, $foundModule.Version) -Verbose + Install-RawModule -ModuleName $foundModule.Name -ModuleVersion $foundModule.Version + } else { + LogInfo('[{0}] Install module with version [{1}] including dependencies' -f $foundModule.Name, $foundModule.Version) -Verbose + $foundModule | Install-Module -Force -SkipPublisherCheck -AllowClobber + } + } + + if ($installed = (Get-Module -Name $foundModule.Name -ListAvailable | Where-Object { $_.Version -eq $foundModule.Version })) { + + # Adding new module to list of 'already installed' modules + $InstalledModuleList += $installed + + $installPath = Split-Path (Split-Path (Split-Path $installed[0].Path)) + LogInfo('[{0}] Module was installed in path [{2}]' -f $installed[0].Name, $installed[0].Version, $installPath) -Verbose + } else { + LogError('Installation of module [{0}] failed' -f $foundModule.Name) + } + } +} + +function Install-RawModule { + <# + .SYNOPSIS + Install a module without any of its dependencies + + .DESCRIPTION + Modules are downloaded from the PSGallery and stored in the first path of the PSModulePath environment variable + + .PARAMETER ModuleName + Mandatory. The name of the module to install + + .PARAMETER ModuleVersion + Mandatory. The name of the module version to install + + .EXAMPLE + Install-RawModule -ModuleName 'Az.Compute' -ModuleVersion '4.27.0' + + Install module 'Az.Compute' in version '4.27.0' in the default PSModule installation path + #> + + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory)] + [string] $ModuleName, + + [Parameter(Mandatory)] + [string] $ModuleVersion + ) + + $url = "https://www.powershellgallery.com/api/v2/package/$ModuleName/$ModuleVersion" + + $downloadFolder = Join-Path ([System.IO.Path]::GetTempPath()) 'modulesToInstall' + $downloadPath = Join-Path $downloadFolder "$ModuleName.$ModuleVersion.zip" # Assuming [.zip] instead of [.nupkg] + $expandedRootPath = Join-Path $downloadFolder 'formattedModules' + $expandedPath = Join-Path $expandedRootPath (Split-Path $downloadPath -LeafBase) + $newModuleRootFolder = Join-Path $expandedRootPath $ModuleName + $newModuleRawVersionFolder = (Join-Path $newModuleRootFolder (Split-Path $expandedPath -Leaf)) + + if ($IsWindows) { $psModulesPath = ($env:PSModulePath -split ';')[0] } + else { $psModulesPath = ($env:PSModulePath -split ':')[0] } + + $finalVersionPath = Join-Path $psModulesPath $ModuleName $ModuleVersion + + # 1. Download nupkg package + if (-not (Test-Path $downloadFolder)) { + if ($PSCmdlet.ShouldProcess("Folder [$downloadFolder]", 'Create')) { + $null = New-Item $downloadFolder -ItemType 'Directory' + } + } + try { + if (-not (Test-Path $downloadPath)) { + if ($PSCmdlet.ShouldProcess("From url [$url] to path [$downloadPath]", 'Download')) { + (New-Object System.Net.WebClient).DownloadFile($Url, $downloadPath) + } + } + } catch { + LogError("Download FAILED: $_") + } + + if ($IsWindows) { + # Not supported in Linux + if ($PSCmdlet.ShouldProcess("File in path [$downloadFolder]", 'Unblock')) { + Unblock-File -Path $downloadPath + } + } + + # 2. Expand Achive + if (-not (Test-Path $expandedPath)) { + if ($PSCmdlet.ShouldProcess("File [$downloadPath] to path [$expandedPath]", 'Expand/Unzip')) { + $null = Expand-Archive -Path $downloadPath -DestinationPath $expandedPath -PassThru + } + } + + # 3. Remove files & folders - Optional + foreach ($fileOrFolderToRemove in @('PSGetModuleInfo.xml', '[Content_Types].xml', '_rels', 'package')) { + $filePath = Join-Path $expandedPath $fileOrFolderToRemove + if (Test-Path -LiteralPath $filePath) { + if ($PSCmdlet.ShouldProcess("Item [$filePath]", 'Remove')) { + $null = Remove-Item -LiteralPath $filePath -Force -Recurse -ErrorAction 'SilentlyContinue' + } + } + } + + # 4. Rename folder + $modulename, $moduleVersion = [regex]::Match((Split-Path $downloadPath -LeafBase), '([a-zA-Z.]+)\.([0-9.]+)').Captures.Groups.value[1, 2] + # Rename-Item -Path $expandedPath -NewName + if (-not (Test-Path $newModuleRootFolder)) { + if ($PSCmdlet.ShouldProcess("Folder [$newModuleRootFolder]", 'Create')) { + $null = New-Item -Path $newModuleRootFolder -ItemType 'Directory' + } + if ($PSCmdlet.ShouldProcess("All items from [$expandedPath] to path [$newModuleRootFolder]", 'Move')) { + $null = Move-Item -LiteralPath $expandedPath -Destination $newModuleRootFolder -Force + } + if ($PSCmdlet.ShouldProcess("Folder [$newModuleRawVersionFolder] to name [$ModuleVersion]", 'Rename')) { + $null = Rename-Item -Path (Join-Path $newModuleRootFolder (Split-Path $expandedPath -Leaf)) -NewName $ModuleVersion + } + } + + # 5. Move folder + if (-not (Test-Path $finalVersionPath)) { + if ($PSCmdlet.ShouldProcess("All items from [$newModuleRootFolder] to path [$psModulesPath]", 'Move')) { + $null = Move-Item -LiteralPath $newModuleRootFolder -Destination $psModulesPath -Force + } + } +} + +function Get-AreDependenciesAvailable { + <# + .SYNOPSIS + Check if all depenencies for a given module are already available in the required minimum version. + + .DESCRIPTION + Check if all depenencies for a given module are already available in the required minimum version. + Returns '$true' if they are, otherwise '$false' + + .PARAMETER InstalledModuleList + Optional. A list of already installed modules. + + .PARAMETER Module + Optional. The module to check the dependencies for + + .EXAMPLE + Get-AreDependenciesAvailable -InstalledModuleList (Get-Module -ListAvailable) -Module (Find-Module 'Az.Compute') + + Check if all dependencies of 'Az.Compute' are part of the already installed modules. + #> + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [object[]] $InstalledModuleList = @(), + + [Parameter(Mandatory = $false)] + [PSCustomObject] $Module + ) + + foreach ($depenency in $Module.dependencies) { + + $dependencyModuleName = $depenency.Name + $dependencyModuleMinimumVersion = [version] ($depenency.minimumVersion) + + $matchingModulesByName = $InstalledModuleList | Where-Object { $_.Name -eq $dependencyModuleName } + $matchingModules = $matchingModulesByName | Where-Object { ([version] $_.Version) -ge $dependencyModuleMinimumVersion } + + if ($matchingModules.Count -eq 0) { + return $false + } + } + + return $true +} +#endregion + + +$StartTime = Get-Date +$progressPreference = 'SilentlyContinue' +LogInfo('#############################################') +LogInfo('# Entering Initialize-LinuxSoftware.ps1 #') +LogInfo('#############################################') + +########################### +## Install Azure CLI ## +########################### +LogInfo('Install azure cli start') +curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash +LogInfo('Install azure cli end') + +############################### +## Install Extensions CLI # +############################### + +LogInfo('Install cli exentions start') +$Extensions = @( + 'azure-devops' +) +foreach ($extension in $Extensions) { + if ((az extension list-available -o json | ConvertFrom-Json).Name -notcontains $extension) { + Write-Verbose "Adding CLI extension '$extension'" + az extension add --name $extension + } +} +LogInfo('Install cli exentions end') + +########################## +## Install Az Bicep # +########################## +LogInfo('Install az bicep exention start') +az bicep install +LogInfo('Install az bicep exention end') + +######################### +## Install Kubectl # +######################### +LogInfo('Install kubectl start') +sudo az aks install-cli +LogInfo('Install kubectl end') + +######################## +## Install Docker # +######################## +LogInfo('Install docker start') +sudo apt-get update + +sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common +curl -fsSL 'https://download.docker.com/linux/ubuntu/gpg' | sudo apt-key add - + +LogInfo('Install docker - Add repository') +sudo add-apt-repository 'deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable' -y + +LogInfo('Install docker - adp update') +sudo apt-get update + +LogInfo('Install docker - adp-cache docker-ce policy') +apt-cache policy 'docker-ce' + +LogInfo('Install docker - adp update') +sudo apt-get update + +LogInfo('Install docker - adp-get install docker-ce') +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y 'docker-ce' + +LogInfo('Install docker - chmod') +sudo chmod 666 '/var/run/docker.sock' # All users can read and write but cannot execute the file/folder +LogInfo('Install docker end') + +########################### +## Install Terraform ## +########################### +LogInfo('Install Terraform start') +$terraformReleasesUrl = 'https://api.github.com/repos/hashicorp/terraform/releases/latest' +$latestTerraformVersion = (Invoke-WebRequest -Uri $terraformReleasesUrl -UseBasicParsing | ConvertFrom-Json).name.Replace('v', '') +LogInfo("Fetched latest available version: [$latestTerraformVersion]") + +LogInfo("Using version: [$latestTerraformVersion]") +sudo DEBIAN_FRONTEND=noninteractive apt-get install unzip +wget ('https://releases.hashicorp.com/terraform/{0}/terraform_{0}_linux_amd64.zip' -f $latestTerraformVersion) +unzip ('terraform_{0}_linux_amd64.zip' -f $latestTerraformVersion ) +sudo mv terraform /usr/local/bin/ +terraform --version +LogInfo('Install Terraform end') + +####################### +## Install AzCopy # +####################### +# Cleanup +sudo rm ./downloadazcopy-v10-linux* +sudo rm ./azcopy_linux_amd64_* +sudo rm /usr/bin/azcopy + +# Download +wget https://aka.ms/downloadazcopy-v10-linux -O 'downloadazcopy-v10-linux.tar.gz' + +# Expand (to azcopy_linux_amd64_x.x.x) +tar -xzvf downloadazcopy-v10-linux.tar.gz + +# Move +sudo cp ./azcopy_linux_amd64_*/azcopy /usr/bin/ + +################################## +## Install .NET (for Nuget) ## +################################## +# Source: https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#1804- +LogInfo('Install dotnet (for nuget) start') + +# .NET-Core SDK +sudo apt-get update +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y dotnet-sdk-8.0 + +# .NET-Core Runtime +sudo apt-get update +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y aspnetcore-runtime-8.0 + +LogInfo('Install dotnet (for nuget) end') + +########################### +## Install BICEP CLI ## +########################### +LogInfo('Install BICEP start') + +# Fetch the latest Bicep CLI binary +curl -Lo bicep 'https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64' +# Mark it as executable +chmod +x ./bicep +# Add bicep to your PATH (requires admin) +sudo mv ./bicep /usr/local/bin/bicep +LogInfo('Install BICEP end') + +############################### +## Install PowerShellGet ## +############################### +LogInfo('Install latest PowerShellGet start') +$null = Install-Module 'PowerShellGet' -Force +LogInfo('Install latest PowerShellGet end') + +LogInfo('Import PowerShellGet start') +$null = Import-PackageProvider PowerShellGet -Force +LogInfo('Import PowerShellGet end') + +#################################### +## Install PowerShell Modules ## +#################################### +$Modules = @( + @{ Name = 'Pester'; Version = '5.1.1' }, + @{ Name = 'PSScriptAnalyzer' }, + @{ Name = 'powershell-yaml' }, + @{ Name = 'Azure.*'; ExcludeModules = @('Azure.Storage') }, # Azure.Storage has AzureRM dependency + @{ Name = 'Logging' }, + @{ Name = 'PoshRSJob' }, + @{ Name = 'ThreadJob' }, + @{ Name = 'JWTDetails' }, + @{ Name = 'OMSIngestionAPI' }, + @{ Name = 'Az.*' }, + @{ Name = 'AzureAD' }, + @{ Name = 'ImportExcel' } +) +$count = 0 +LogInfo('Try installing:') +$modules | ForEach-Object { + LogInfo('- [{0}]. [{1}]' -f $count, $_.Name) + $count++ +} + +# Load already installed modules +$installedModules = Get-Module -ListAvailable + +LogInfo('Install-CustomModule start') +$count = 0 +Foreach ($Module in $Modules) { + LogInfo('=====================') + LogInfo('HANDLING MODULE [{0}] [{1}/{2}]' -f $Module.Name, $count, $Modules.Count) + LogInfo('=====================') + # Installing New Modules and Removing Old + $null = Install-CustomModule -Module $Module -InstalledModuleList $installedModules + $count++ +} +LogInfo('Install-CustomModule end') + + +######################################### +## Post Installation Configuration ## +######################################### +LogInfo('Copy PS modules to expected location start') +$targetPath = '/opt/microsoft/powershell/7/Modules' +$sourcePaths = @('/home/packer/.local/share/powershell/Modules', '/root/.local/share/powershell/Modules') +foreach ($sourcePath in $sourcePaths) { + if (Test-Path $sourcePath) { + LogInfo("Copying from [$sourcePath] to [$targetPath]") + $null = Copy-FileAndFolderList -sourcePath $sourcePath -targetPath $targetPath + } +} +LogInfo('Copy PS modules end') + +$elapsedTime = (Get-Date) - $StartTime +$totalTime = '{0:HH:mm:ss}' -f ([datetime]$elapsedTime.Ticks) +LogInfo("Execution took [$totalTime]") +LogInfo('############################################') +LogInfo('# Exiting Initialize-LinuxSoftware.ps1 #') +LogInfo('############################################') + +return 0 +#endregion diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-WindowsSoftware.ps1 b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-WindowsSoftware.ps1 new file mode 100644 index 00000000000..4672d2b3b6e --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Initialize-WindowsSoftware.ps1 @@ -0,0 +1,792 @@ +#requires -Version 6.0 + +#region Functions +function LogInfo($message) { + Log 'Info' $message +} + +function LogError($message) { + Log 'Error' $message +} + +function LogWarning($message) { + Log 'Warning' $message +} + +function Log { + + <# + .SYNOPSIS + Creates a log file and stores logs based on categories with tab seperation + + .PARAMETER category + Category to put into the trace + + .PARAMETER message + Message to be loged + + .EXAMPLE + Log 'Info' 'Message' + + #> + + Param ( + [Parameter(Mandatory = $false)] + [string] $category = 'Info', + + [Parameter(Mandatory = $true)] + [string] $message + ) + + $date = Get-Date + $content = "[$date]`t$category`t`t$message`n" + Write-Verbose $Content -Verbose + + $FilePath = Join-Path ([System.IO.Path]::GetTempPath()) 'log.log' + if (-not (Test-Path $FilePath)) { + Write-Verbose "Log file not found, create new in path: [$FilePath]" -Verbose + $null = New-Item -ItemType 'File' -Path $FilePath -Force + } + Add-Content -Path $FilePath -Value $content -ErrorAction 'Stop' +} + +function Install-CustomModule { + + <# + .SYNOPSIS + Installes given PowerShell modules + + .DESCRIPTION + Installes given PowerShell modules + + .PARAMETER Module + Required. Modules to be installed, must be Object + @{ + Name = 'Name' + Version = '1.0.0' # Optional + } + + .PARAMETER InstalledModuleList + Optional. Modules that are already installed on the machine. Can be fetched via 'Get-Module -ListAvailable' + + .EXAMPLE + Install-CustomModule @{ Name = 'Pester' } C:\Modules + + Installes pester and saves it to C:\Modules + #> + + [CmdletBinding(SupportsShouldProcess)] + Param ( + [Parameter(Mandatory = $true)] + [Hashtable] $Module, + + [Parameter(Mandatory = $false)] + [object[]] $InstalledModuleList = @() + ) + + # Remove exsisting module in session + if (Get-Module $Module -ErrorAction 'SilentlyContinue') { + try { + Remove-Module $Module -Force + } catch { + LogError('Unable to remove module [{0}] because of exception [{1}]. Stack Trace: [{2}]' -f $Module.Name, $_.Exception, $_.ScriptStackTrace) + } + } + + # Install found module + $moduleImportInputObject = @{ + name = $Module.Name + Repository = 'PSGallery' + } + if ($Module.Version) { + $moduleImportInputObject['RequiredVersion'] = $Module.Version + } + + # Get all modules that match a certain name. In case of e.g. 'Az' it returns several. + $foundModules = Find-Module @moduleImportInputObject + + foreach ($foundModule in $foundModules) { + + # Check if already installed as required + if ($alreadyInstalled = $InstalledModule | Where-Object { $_.Name -eq $Module.Name }) { + if ($Module.Version) { + $alreadyInstalled = $alreadyInstalled | Where-Object { $_.Version -eq $Module.Version } + } else { + # Get latest in case of multiple + $alreadyInstalled = ($alreadyInstalled | Sort-Object -Property Version -Descending)[0] + } + LogInfo('[{0}] Module is already installed with version [{1}]' -f $alreadyInstalled.Name, $alreadyInstalled.Version) -Verbose + continue + } + + # Check if not to be excluded + if ($Module.ExcludeModules -and $Module.excludeModules.contains($foundModule.Name)) { + LogInfo('[{0}] Module is configured to be ignored.' -f $foundModule.Name) -Verbose + continue + } + + if ($PSCmdlet.ShouldProcess('Module [{0}]' -f $foundModule.Name, 'Install')) { + $dependenciesAlreadyAvailable = Get-AreDependenciesAvailable -InstalledModuleList $InstalledModuleList -Module $foundModule + if ($dependenciesAlreadyAvailable) { + LogInfo('[{0}] Install module with version [{1}] exluding dependencies.' -f $foundModule.Name, $foundModule.Version) -Verbose + Install-RawModule -ModuleName $foundModule.Name -ModuleVersion $foundModule.Version + } else { + LogInfo('[{0}] Install module with version [{1}] including dependencies' -f $foundModule.Name, $foundModule.Version) -Verbose + $foundModule | Install-Module -Force -SkipPublisherCheck -AllowClobber + } + } + + if ($installed = (Get-Module -Name $foundModule.Name -ListAvailable | Where-Object { $_.Version -eq $foundModule.Version })) { + + # Adding new module to list of 'already installed' modules + $InstalledModuleList += $installed + + $installPath = Split-Path (Split-Path (Split-Path $installed[0].Path)) + LogInfo('[{0}] Module was installed in path [{2}]' -f $installed[0].Name, $installed[0].Version, $installPath) -Verbose + } else { + LogError('Installation of module [{0}] failed' -f $foundModule.Name) + } + } +} + +function Install-RawModule { + <# + .SYNOPSIS + Install a module without any of its dependencies + + .DESCRIPTION + Modules are downloaded from the PSGallery and stored in the first path of the PSModulePath environment variable + + .PARAMETER ModuleName + Mandatory. The name of the module to install + + .PARAMETER ModuleVersion + Mandatory. The name of the module version to install + + .EXAMPLE + Install-RawModule -ModuleName 'Az.Compute' -ModuleVersion '4.27.0' + + Install module 'Az.Compute' in version '4.27.0' in the default PSModule installation path + #> + + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory)] + [string] $ModuleName, + + [Parameter(Mandatory)] + [string] $ModuleVersion + ) + + $url = "https://www.powershellgallery.com/api/v2/package/$ModuleName/$ModuleVersion" + + $downloadFolder = Join-Path ([System.IO.Path]::GetTempPath()) 'modulesToInstall' + $downloadPath = Join-Path $downloadFolder "$ModuleName.$ModuleVersion.zip" # Assuming [.zip] instead of [.nupkg] + $expandedRootPath = Join-Path $downloadFolder 'formattedModules' + $expandedPath = Join-Path $expandedRootPath (Split-Path $downloadPath -LeafBase) + $newModuleRootFolder = Join-Path $expandedRootPath $ModuleName + $newModuleRawVersionFolder = (Join-Path $newModuleRootFolder (Split-Path $expandedPath -Leaf)) + + if ($IsWindows) { $psModulesPath = ($env:PSModulePath -split ';')[0] } + else { $psModulesPath = ($env:PSModulePath -split ':')[0] } + + $finalVersionPath = Join-Path $psModulesPath $ModuleName $ModuleVersion + + # 1. Download nupkg package + if (-not (Test-Path $downloadFolder)) { + if ($PSCmdlet.ShouldProcess("Folder [$downloadFolder]", 'Create')) { + $null = New-Item $downloadFolder -ItemType 'Directory' + } + } + try { + if (-not (Test-Path $downloadPath)) { + if ($PSCmdlet.ShouldProcess("From url [$url] to path [$downloadPath]", 'Download')) { + (New-Object System.Net.WebClient).DownloadFile($Url, $downloadPath) + } + } + } catch { + LogError("Download FAILED: $_") + } + + if ($IsWindows) { + # Not supported in Linux + if ($PSCmdlet.ShouldProcess("File in path [$downloadFolder]", 'Unblock')) { + $null = Unblock-File -Path $downloadPath + } + } + + + # 2. Expand Achive + if (-not (Test-Path $expandedPath)) { + if ($PSCmdlet.ShouldProcess("File [$downloadPath] to path [$expandedPath]", 'Expand/Unzip')) { + $null = Expand-Archive -Path $downloadPath -DestinationPath $expandedPath -PassThru + } + } + + # 3. Remove files & folders - Optional + foreach ($fileOrFolderToRemove in @('PSGetModuleInfo.xml', '[Content_Types].xml', '_rels', 'package')) { + $filePath = Join-Path $expandedPath $fileOrFolderToRemove + if (Test-Path -LiteralPath $filePath) { + if ($PSCmdlet.ShouldProcess("Item [$filePath]", 'Remove')) { + $null = Remove-Item -LiteralPath $filePath -Force -Recurse -ErrorAction 'SilentlyContinue' + } + } + } + + # 4. Rename folder + $modulename, $moduleVersion = [regex]::Match((Split-Path $downloadPath -LeafBase), '([a-zA-Z.]+)\.([0-9.]+)').Captures.Groups.value[1, 2] + # Rename-Item -Path $expandedPath -NewName + if (-not (Test-Path $newModuleRootFolder)) { + if ($PSCmdlet.ShouldProcess("Folder [$newModuleRootFolder]", 'Create')) { + $null = New-Item -Path $newModuleRootFolder -ItemType 'Directory' + } + if ($PSCmdlet.ShouldProcess("All items from [$expandedPath] to path [$newModuleRootFolder]", 'Move')) { + $null = Move-Item -LiteralPath $expandedPath -Destination $newModuleRootFolder -Force + } + if ($PSCmdlet.ShouldProcess("Folder [$newModuleRawVersionFolder] to name [$ModuleVersion]", 'Rename')) { + $null = Rename-Item -Path (Join-Path $newModuleRootFolder (Split-Path $expandedPath -Leaf)) -NewName $ModuleVersion + } + } + + # 5. Move folder + if (-not (Test-Path $finalVersionPath)) { + if ($PSCmdlet.ShouldProcess("All items from [$newModuleRootFolder] to path [$psModulesPath]", 'Move')) { + $null = Move-Item -LiteralPath $newModuleRootFolder -Destination $psModulesPath -Force + } + } +} + +function Get-AreDependenciesAvailable { + <# + .SYNOPSIS + Check if all depenencies for a given module are already available in the required minimum version. + + .DESCRIPTION + Check if all depenencies for a given module are already available in the required minimum version. + Returns '$true' if they are, otherwise '$false' + + .PARAMETER InstalledModuleList + Optional. A list of already installed modules. + + .PARAMETER Module + Optional. The module to check the dependencies for + + .EXAMPLE + Get-AreDependenciesAvailable -InstalledModuleList (Get-Module -ListAvailable) -Module (Find-Module 'Az.Compute') + + Check if all dependencies of 'Az.Compute' are part of the already installed modules. + #> + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $false)] + [object[]] $InstalledModuleList = @(), + + [Parameter(Mandatory = $false)] + [PSCustomObject] $Module + ) + + foreach ($depenency in $Module.dependencies) { + + $dependencyModuleName = $depenency.Name + $dependencyModuleMinimumVersion = [version] ($depenency.minimumVersion) + + $matchingModulesByName = $InstalledModuleList | Where-Object { $_.Name -eq $dependencyModuleName } + $matchingModules = $matchingModulesByName | Where-Object { ([version] $_.Version) -ge $dependencyModuleMinimumVersion } + + if ($matchingModules.Count -eq 0) { + return $false + } + } + + return $true +} + +function Set-PowerShellOutputRedirectionBugFix { + + [CmdletBinding(SupportsShouldProcess)] + param () + + $poshMajorVerion = $PSVersionTable.PSVersion.Major + + if ($poshMajorVerion -lt 4) { + try { + # http://www.leeholmes.com/blog/2008/07/30/workaround-the-os-handles-position-is-not-what-filestream-expected/ plus comments + $bindingFlags = [Reflection.BindingFlags] 'Instance,NonPublic,GetField' + $objectRef = $host.GetType().GetField('externalHostRef', $bindingFlags).GetValue($host) + $bindingFlags = [Reflection.BindingFlags] 'Instance,NonPublic,GetProperty' + $consoleHost = $objectRef.GetType().GetProperty('Value', $bindingFlags).GetValue($objectRef, @()) + [void] $consoleHost.GetType().GetProperty('IsStandardOutputRedirected', $bindingFlags).GetValue($consoleHost, @()) + $bindingFlags = [Reflection.BindingFlags] 'Instance,NonPublic,GetField' + $field = $consoleHost.GetType().GetField('standardOutputWriter', $bindingFlags) + + if ($PSCmdlet.ShouldProcess('OutputWriter field [Out]', 'Set')) { + $field.SetValue($consoleHost, [Console]::Out) + } + + [void] $consoleHost.GetType().GetProperty('IsStandardErrorRedirected', $bindingFlags).GetValue($consoleHost, @()) + $field2 = $consoleHost.GetType().GetField('standardErrorWriter', $bindingFlags) + + if ($PSCmdlet.ShouldProcess('OutputWriter field [Error]', 'Set')) { + $field2.SetValue($consoleHost, [Console]::Error) + } + } catch { + LogInfo( 'Unable to apply redirection fix.') + } + } +} + +function Get-Downloader { + param ( + [string]$url + ) + + $downloader = New-Object System.Net.WebClient + + $defaultCreds = [System.Net.CredentialCache]::DefaultCredentials + if ($null -ne $defaultCreds) { + $downloader.Credentials = $defaultCreds + } + + if ($env:chocolateyIgnoreProxy -eq 'true') { + Write-Debug 'Explicitly bypassing proxy due to user environment variable' + $downloader.Proxy = [System.Net.GlobalProxySelection]::GetEmptyWebProxy() + } else { + # check if a proxy is required + $explicitProxy = $env:chocolateyProxyLocation + $explicitProxyUser = $env:chocolateyProxyUser + $explicitProxyPassword = $env:chocolateyProxyPassword + if ($null -ne $explicitProxy -and $explicitProxy -ne '') { + # explicit proxy + $proxy = New-Object System.Net.WebProxy($explicitProxy, $true) + if ($null -ne $explicitProxyPassword -and $explicitProxyPassword -ne '') { + $passwd = ConvertTo-SecureString $explicitProxyPassword -AsPlainText -Force + $proxy.Credentials = New-Object System.Management.Automation.PSCredential ($explicitProxyUser, $passwd) + } + + Write-Debug "Using explicit proxy server '$explicitProxy'." + $downloader.Proxy = $proxy + + } elseif (-not $downloader.Proxy.IsBypassed($url)) { + # system proxy (pass through) + $creds = $defaultCreds + if ($null -eq $creds) { + Write-Debug 'Default credentials were null. Attempting backup method' + $cred = Get-Credential + $creds = $cred.GetNetworkCredential() + } + + $proxyaddress = $downloader.Proxy.GetProxy($url).Authority + Write-Debug "Using system proxy server '$proxyaddress'." + $proxy = New-Object System.Net.WebProxy($proxyaddress) + $proxy.Credentials = $creds + $downloader.Proxy = $proxy + } + } + + return $downloader +} + +function Get-DownloadString { + param ( + [string]$url + ) + $downloader = Get-Downloader $url + + return $downloader.DownloadString($url) +} + +function Get-DownloadedFile { + param ( + [string]$url, + [string]$file + ) + LogInfo( "Downloading $url to $file") + $downloader = Get-Downloader $url + + $downloader.DownloadFile($url, $file) +} + +function Set-SecurityProtocol { + + [CmdletBinding(SupportsShouldProcess)] + param ( + ) + + # Attempt to set highest encryption available for SecurityProtocol. + # PowerShell will not set this by default (until maybe .NET 4.6.x). This + # will typically produce a message for PowerShell v2 (just an info + # message though) + try { + # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) + # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't + # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is + # installed (.NET 4.5 is an in-place upgrade). + if ($PSCmdlet.ShouldProcess('Security protocol', 'Set')) { + [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 + } + } catch { + LogInfo( 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to do one or more of the following: (1) upgrade to .NET Framework 4.5+ and PowerShell v3, (2) specify internal Chocolatey package location (set $env:chocolateyDownloadUrl prior to install or host the package internally), (3) use the Download + PowerShell method of install. See https://chocolatey.org/install for all install options.') + } +} + +function Install-Choco { + + LogInfo( 'Install choco') + + LogInfo( 'Invoke install.ps1 content') + $chocTempDir = Join-Path ([System.IO.Path]::GetTempPath()) 'chocolatey' + $tempDir = Join-Path $chocTempDir 'chocInstall' + if (-not [System.IO.Directory]::Exists($tempDir)) { [void][System.IO.Directory]::CreateDirectory($tempDir) } + $file = Join-Path $tempDir 'chocolatey.zip' + + Set-PowerShellOutputRedirectionBugFix + + Set-SecurityProtocol + + LogInfo( 'Getting latest version of the Chocolatey package for download.') + $url = 'https://chocolatey.org/api/v2/Packages()?$filter=((Id%20eq%20%27chocolatey%27)%20and%20(not%20IsPrerelease))%20and%20IsLatestVersion' + [xml]$result = Get-DownloadString $url + $url = $result.feed.entry.content.src + + # Download the Chocolatey package + LogInfo("Getting Chocolatey from $url.") + Get-DownloadedFile $url $file + + # Determine unzipping method + # 7zip is the most compatible so use it by default + $7zaExe = Join-Path $tempDir '7za.exe' + $unzipMethod = '7zip' + if ($env:chocolateyUseWindowsCompression -eq 'true') { + LogInfo( 'Using built-in compression to unzip') + $unzipMethod = 'builtin' + } elseif (-Not (Test-Path ($7zaExe))) { + LogInfo( 'Downloading 7-Zip commandline tool prior to extraction.') + # download 7zip + Get-DownloadedFile 'https://chocolatey.org/7za.exe' "$7zaExe" + } + + # unzip the package + LogInfo("Extracting $file to $tempDir...") + if ($unzipMethod -eq '7zip') { + LogInfo('Unzip with 7zip') + $params = "x -o`"$tempDir`" -bd -y `"$file`"" + # use more robust Process as compared to Start-Process -Wait (which doesn't + # wait for the process to finish in PowerShell v3) + $process = New-Object System.Diagnostics.Process + $process.StartInfo = New-Object System.Diagnostics.ProcessStartInfo($7zaExe, $params) + $process.StartInfo.RedirectStandardOutput = $true + $process.StartInfo.UseShellExecute = $false + $process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden + $process.Start() | Out-Null + $process.BeginOutputReadLine() + $process.WaitForExit() + $exitCode = $process.ExitCode + $process.Dispose() + $errorMessage = "Unable to unzip package using 7zip. Perhaps try setting `$env:chocolateyUseWindowsCompression = 'true' and call install again. Error:" + switch ($exitCode) { + 0 { LogInfo('Processed zip'); break } + 1 { throw "$errorMessage Some files could not be extracted" } + 2 { throw "$errorMessage 7-Zip encountered a fatal error while extracting the files" } + 7 { throw "$errorMessage 7-Zip command line error" } + 8 { throw "$errorMessage 7-Zip out of memory" } + 255 { throw "$errorMessage Extraction cancelled by the user" } + default { throw "$errorMessage 7-Zip signalled an unknown error (code $exitCode)" } + } + } else { + LogInfo('Unzip without 7zip') + if ($PSVersionTable.PSVersion.Major -lt 5) { + try { + $shellApplication = New-Object -com shell.application + $zipPackage = $shellApplication.NameSpace($file) + $destinationFolder = $shellApplication.NameSpace($tempDir) + $destinationFolder.CopyHere($zipPackage.Items(), 0x10) + } catch { + throw "Unable to unzip package using built-in compression. Set `$env:chocolateyUseWindowsCompression = 'false' and call install again to use 7zip to unzip. Error: `n $_" + } + } else { + $null = Expand-Archive -Path $file -DestinationPath $tempDir -Force -PassThru + } + } + + # Call chocolatey install + LogInfo( 'Installing chocolatey on this machine') + $toolsFolder = Join-Path $tempDir 'tools' + $chocInstallPS1 = Join-Path $toolsFolder 'chocolateyInstall.ps1' + + & $chocInstallPS1 + + LogInfo( 'Ensuring chocolatey commands are on the path') + $chocInstallVariableName = 'ChocolateyInstall' + $chocoPath = [Environment]::GetEnvironmentVariable($chocInstallVariableName) + if ($null -eq $chocoPath -or $chocoPath -eq '') { + $chocoPath = "$env:ALLUSERSPROFILE\Chocolatey" + } + + if (-not (Test-Path ($chocoPath))) { + $chocoPath = "$env:SYSTEMDRIVE\ProgramData\Chocolatey" + } + + $chocoExePath = Join-Path $chocoPath 'bin' + + if ($($env:Path).ToLower().Contains($($chocoExePath).ToLower()) -eq $false) { + $env:Path = [Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) + } + + LogInfo( 'Ensuring chocolatey.nupkg is in the lib folder') + $chocoPkgDir = Join-Path $chocoPath 'lib\chocolatey' + $nupkg = Join-Path $chocoPkgDir 'chocolatey.nupkg' + if (-not [System.IO.Directory]::Exists($chocoPkgDir)) { [System.IO.Directory]::CreateDirectory($chocoPkgDir); } + Copy-Item "$file" "$nupkg" -Force -ErrorAction SilentlyContinue +} + + +function Uninstall-AzureRM { + <# + .SYNOPSIS + Removes AzureRM from system + + .EXAMPLE + Uninstall-AzureRM + Removes AzureRM from system + + #> + + LogInfo('Remove Modules from context start') + Get-Module 'AzureRM.*' | Remove-Module + LogInfo('Remaining AzureRM modules: {0}' -f ((Get-Module 'AzureRM.*').Name -join ' | ')) + LogInfo('Remove Modules from context end') + + # Uninstall AzureRm Modules + try { + Get-Module 'AzureRm.*' -ListAvailable | Uninstall-Module -Force + } catch { + LogError("Unable to remove AzureRM Module: $($_.Exception) found, $($_.ScriptStackTrace)") + } + + try { + $AzureRMModuleFolder = 'C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager' + if (Test-Path $AzureRMModuleFolder) { + $null = Remove-Item $AzureRMModuleFolder -Force -Recurse + LogInfo("Removed $AzureRMModuleFolder") + } + } catch { + LogError("Unable to remove $AzureRMModuleFolder") + } + + LogInfo('Remaining installed AzureRMModule: {0}' -f ((Get-Module 'AzureRM.*' -ListAvailable).Name -join ' | ')) +} + +function Copy-FileAndFolderList { + + param( + [string] $sourcePath, + [string] $targetPath + ) + + $itemsFrom = Get-ChildItem $sourcePath + foreach ($item in $itemsFrom) { + if ($item.PSIsContainer) { + $subsourcePath = $sourcePath + '\' + $item.BaseName + $subtargetPath = $targetPath + '\' + $item.BaseName + $null = Copy-FileAndFolderList -sourcePath $subsourcePath -targetPath $subtargetPath + } else { + $sourceItemPath = $sourcePath + '\' + $item.Name + $targetItemPath = $targetPath + '\' + $item.Name + if (-not (Test-Path $targetItemPath)) { + # only copies non-existing files + if (-not (Test-Path $targetPath)) { + # if folder doesn't exist, creates it + $null = New-Item -ItemType 'directory' -Path $targetPath + } + $null = Copy-Item $sourceItemPath $targetItemPath + } else { + Write-Verbose "[$sourceItemPath] already exists" + } + } + } +} +#endregion + +$StartTime = Get-Date +$progressPreference = 'SilentlyContinue' +LogInfo('###############################################') +LogInfo('# Entering Initialize-WindowsSoftware.ps1 #') +LogInfo('###############################################') + +LogInfo( 'Set Execution Policy') +Set-ExecutionPolicy Bypass -Scope Process -Force + +####################### +## Install Choco # +####################### +LogInfo('Install-Choco start') +$null = Install-Choco +LogInfo('Install-Choco end') + +########################## +## Install Azure CLI # +########################## +LogInfo('Install azure cli start') +$null = choco install azure-cli -y -v +LogInfo('Install azure cli end') + +############################### +## Install Extensions CLI # +############################### + +LogInfo('Install cli exentions start') +$Extensions = @( + 'azure-devops' +) +foreach ($extension in $Extensions) { + if ((az extension list-available -o json | ConvertFrom-Json).Name -notcontains $extension) { + Write-Verbose "Adding CLI extension '$extension'" + az extension add --name $extension + } +} +LogInfo('Install cli exentions end') + +########################## +## Install Az Bicep # +########################## +LogInfo('Install az bicep exention start') +az bicep install +LogInfo('Install az bicep exention end') + +######################## +## Install docker # +######################## +LogInfo('Install docker start') +choco install docker +LogInfo('Install docker end') + +######################### +## Install Kubectl # +######################### +LogInfo('Install kubectl start') +$null = choco install kubernetes-cli -y -v +LogInfo('Install kubectl end') + +######################## +## Install Docker # +######################## +LogInfo('Install docker start') +$null = choco install docker -y -v +# $null = choco install docker-desktop +LogInfo('Install docker end') + +################################# +## Install PowerShell Core # +################################# +LogInfo('Install powershell core start') +$null = choco install powershell-core -y -v +LogInfo('Install powershell core end') + +########################### +## Install Terraform ## +########################### +LogInfo('Install Terraform start') +$null = choco install terraform -y -v +LogInfo('Install Terraform end') + +####################### +## Install Nuget ## +####################### +LogInfo('Update Package Provider Nuget start') +$null = Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force +LogInfo('Update Package Provider Nuget end') + +####################### +## Install AzCopy # +####################### +LogInfo('Install az copy start') +Invoke-WebRequest -Uri 'https://aka.ms/downloadazcopy-v10-windows' -OutFile 'AzCopy.zip' -UseBasicParsing +$null = Expand-Archive './AzCopy.zip' './AzCopy' -Force -PassThru +Get-ChildItem './AzCopy/*/azcopy.exe' | Move-Item -Destination 'C:\Users\user\AzCopy\AzCopy.exe' +$userenv = [System.Environment]::GetEnvironmentVariable('Path', 'User') +[System.Environment]::SetEnvironmentVariable('PATH', $userenv + ';C:\Users\user\AzCopy', 'User') +LogInfo('Install az copy end') + +############################### +## Install PowerShellGet ## +############################### +LogInfo('Install latest PowerShellGet start') +$null = Install-Module 'PowerShellGet' -Force +LogInfo('Install latest PowerShellGet end') + +LogInfo('Import PowerShellGet start') +$null = Import-PackageProvider PowerShellGet -Force +LogInfo('Import PowerShellGet end') + +#################################### +## Install PowerShell Modules ## +#################################### +$Modules = @( + @{ Name = 'Pester' }, + @{ Name = 'PSScriptAnalyzer' }, + @{ Name = 'powershell-yaml' }, + @{ Name = 'Azure.*'; ExcludeModules = @('Azure.Storage') }, # Azure.Storage has AzureRM dependency + @{ Name = 'Logging' }, + @{ Name = 'PoshRSJob' }, + @{ Name = 'ThreadJob' }, + @{ Name = 'JWTDetails' }, + @{ Name = 'OMSIngestionAPI' }, + @{ Name = 'Az.*' }, + @{ Name = 'AzureAD' }, + @{ Name = 'ImportExcel' } +) +$count = 0 +LogInfo('Try installing:') +$modules | ForEach-Object { + LogInfo('- [{0}]. [{1}]' -f $count, $_.Name) + $count++ +} + +# Load already installed modules +$installedModules = Get-Module -ListAvailable + +LogInfo('Install-CustomModule start') +$count = 0 +Foreach ($Module in $Modules) { + LogInfo('=====================') + LogInfo('HANDLING MODULE [{0}] [{1}/{2}]' -f $Module.Name, $count, $Modules.Count) + LogInfo('=====================') + # Installing New Modules and Removing Old + $null = Install-CustomModule -Module $Module -InstalledModuleList $installedModules + $count++ +} +LogInfo('Install-CustomModule end') + +######################################### +## Post Installation Configuration ## +######################################### +LogInfo('Copy PS modules to expected location start') +$targetPath = 'C:\program files\powershell\7\Modules' +$sourcePaths = @('C:\Users\packer\Documents\PowerShell\Modules') +foreach ($sourcePath in $sourcePaths) { + if (Test-Path $sourcePath) { + LogInfo("Copying from [$sourcePath] to [$targetPath]") + $null = Copy-FileAndFolderList -sourcePath $sourcePath -targetPath $targetPath + } +} +LogInfo('Copy PS modules end') + +######################################### +## Post Installation Configuration ## +######################################### +if (Get-Module AzureRm* -ListAvailable) { + LogInfo('Un-install ARM start') + Uninstall-AzureRm + LogInfo('Un-install ARM end') +} + +$elapsedTime = (Get-Date) - $StartTime +$totalTime = '{0:HH:mm:ss}' -f ([datetime]$elapsedTime.Ticks) +LogInfo("Execution took [$totalTime]") +LogInfo('##############################################') +LogInfo('# Exiting Initialize-WindowsSoftware.ps1 #') +LogInfo('##############################################') + +return 0 +#endregion diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-LinuxPowerShell.sh b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-LinuxPowerShell.sh new file mode 100644 index 00000000000..e62a245487e --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-LinuxPowerShell.sh @@ -0,0 +1,44 @@ +# Source: https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.4 + +echo '###########################################' +echo '# Entering Install-LinuxPowerShell.sh #' +echo '###########################################' + +echo '1. Update the list of packages' +sudo apt-get update + +echo '2. Install pre-requisite packages' +sudo apt-get install -y wget apt-transport-https software-properties-common + +echo '3. Get the version of Ubuntu' +# source /etc/os-release +# echo "Found version $VERSION_ID" - empty +VERSION_ID='22.04' + +echo '4. Determine URL' +url=https://packages.microsoft.com/config/ubuntu/$VERSION_ID/packages-microsoft-prod.deb +echo " Found URL [$url]" + +echo '5. Download the Microsoft repository GPG keys' +wget -q $url + +echo '6. Register the Microsoft repository GPG keys' +sudo dpkg -i packages-microsoft-prod.deb + +echo '7. Delete the Microsoft repository keys file' +rm packages-microsoft-prod.deb + +echo '8. Update the list of products' +sudo apt-get update + +echo '9. Enable the "universe" repositories' +sudo add-apt-repository universe -y + +echo '10. Install PowerShell' +sudo apt-get install -y powershell + +echo '##########################################' +echo '# Exiting Install-LinuxPowerShell.sh #' +echo '##########################################' + +exit 0 diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-WindowsPowerShell.ps1 b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-WindowsPowerShell.ps1 new file mode 100644 index 00000000000..2ecdce36eb4 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployAll/scripts/Install-WindowsPowerShell.ps1 @@ -0,0 +1,43 @@ +Write-Verbose '##############################################' -Verbose +Write-Verbose '# Entering Install-WindowsPowerShell.ps1 #' -Verbose +Write-Verbose '##############################################' -Verbose + +$psVersion = '7.4.4' +$ps7Url = "https://github.com/PowerShell/PowerShell/releases/download/v$psVersion/PowerShell-$psVersion-win-x64.zip" +$downloadFolder = (Get-Location).Path +$downloadLoc = Join-Path $downloadFolder (Split-Path $ps7Url -Leaf) + +if (-not (Test-Path $downloadLoc)) { + Write-Verbose "Download to [$downloadLoc]" -Verbose + (New-Object System.Net.WebClient).DownloadFile($ps7Url, $downloadLoc) + Unblock-File $downloadLoc + Write-Verbose 'Downloaded' -Verbose +} else { + Write-Verbose "Already downloaded to [$downloadLoc]" -Verbose +} + +$installLoc = "$env:ProgramFiles\PowerShell\7" + +if (-not (Test-Path $installLoc)) { + Write-Verbose "Install to [$installLoc]" -Verbose + Expand-Archive -Path $downloadLoc -DestinationPath $installLoc + Write-Verbose 'Installed' -Verbose +} else { + Write-Verbose "Already installed in [$installLoc]" -Verbose +} + +if ($Env:PATH -notlike "*$installLoc*") { + Write-Verbose 'Set environment variable' -Verbose + [Environment]::SetEnvironmentVariable('PATH', $Env:PATH + ";$installLoc", [EnvironmentVariableTarget]::Machine) + $env:Path += ";$installLoc" + Write-Verbose 'Environment variable set' -Verbose +} else { + Write-Verbose 'Environment variable already set' -Verbose +} + +Write-Verbose 'Try run PS-Core' -Verbose +pwsh -Command 'Write-Host "Hello from the inside"' + +Write-Verbose '#############################################' -Verbose +Write-Verbose '# Exiting Install-WindowsPowerShell.ps1 #' -Verbose +Write-Verbose '#############################################' -Verbose diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/dependencies.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/dependencies.bicep new file mode 100644 index 00000000000..ca5c99c459d --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/dependencies.bicep @@ -0,0 +1,259 @@ +targetScope = 'subscription' + +@description('Required. The name of the Resource Group.') +param resourceGroupName string + +@description('Required. The name of the Resource Group to deploy the Image Template resources into.') +param imageTemplateResourceGroupName string + +// User Assigned Identity (MSI) Parameters +@description('Required. The name of the Managed Identity used by deployment scripts.') +param deploymentScriptManagedIdentityName string + +@description('Required. The name of the Managed Identity used by the Azure Image Builder.') +param imageManagedIdentityName string + +// Azure Compute Gallery Parameters +@description('Required. The name of the Azure Compute Gallery.') +param computeGalleryName string + +// Storage Account Parameters +@description('Required. The name of the storage account. Only needed if you want to upload scripts to be used during image baking.') +param assetsStorageAccountName string + +@description('Required. The name of the storage account.') +param deploymentScriptStorageAccountName string + +// Virtual Network Parameters +@description('Required. The name of the Virtual Network.') +param virtualNetworkName string + +// Shared Parameters +@description('Optional. The location to deploy into.') +param location string = deployment().location + +var addressPrefix = '10.0.0.0/16' + +// The Image Definitions in the Azure Compute Gallery +var computeGalleryImageDefinitionsVar = [ + { + hyperVGeneration: 'V2' + name: 'sid-linux' + osType: 'Linux' + publisher: 'devops' + offer: 'devops_linux' + sku: 'devops_linux_az' + } +] +var assetsStorageAccountContainerName = 'aibscripts' + +// Role required for deployment script to be able to use a storage account via private networking +resource storageFileDataPrivilegedContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Priveleged Contributor + scope: tenant() +} +resource contributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor + scope: tenant() +} + +// Resource Groups +resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = { + name: resourceGroupName + location: location +} + +// Always deployed as both an infra element & needed as a staging resource group for image building +module imageTemplateRg 'br/public:avm/res/resources/resource-group:0.2.4' = { + name: '${deployment().name}-image-rg' + params: { + name: imageTemplateResourceGroupName + location: location + } +} + +// User Assigned Identity (MSI) +module dsMsi 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.2' = { + name: '${deployment().name}-ds-msi' + scope: rg + params: { + name: deploymentScriptManagedIdentityName + location: location + } +} + +module imageMSI 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.2' = { + name: '${deployment().name}-image-msi' + scope: rg + params: { + name: imageManagedIdentityName + location: location + } +} + +// MSI Subscription contributor assignment +resource imageMSI_rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().subscriptionId, imageManagedIdentityName, contributorRole.id) + properties: { + principalId: imageMSI.outputs.principalId + roleDefinitionId: contributorRole.id + principalType: 'ServicePrincipal' + } +} + +// Azure Compute Gallery +module azureComputeGallery 'br/public:avm/res/compute/gallery:0.4.0' = { + name: '${deployment().name}-acg' + scope: rg + params: { + name: computeGalleryName + images: computeGalleryImageDefinitionsVar + location: location + } +} + +// Image Template Virtual Network +module vnet 'br/public:avm/res/network/virtual-network:0.1.6' = { + name: '${deployment().name}-vnet' + scope: rg + params: { + name: virtualNetworkName + addressPrefixes: [ + addressPrefix + ] + subnets: [ + { + name: 'subnet-it' + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + } + { + name: 'subnet-ds' + addressPrefix: cidrSubnet(addressPrefix, 24, 1) + privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET - temp + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + delegations: [ + { + name: 'Microsoft.ContainerInstance.containerGroups' + properties: { + serviceName: 'Microsoft.ContainerInstance/containerGroups' + } + } + ] + } + ] + location: location + } +} + +// Assets Storage Account +module assetsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' = { + name: '${deployment().name}-files-sa' + scope: rg + params: { + name: assetsStorageAccountName + allowSharedKeyAccess: false // Keys not needed if MSI is granted access + location: location + networkAcls: { + defaultAction: 'Allow' + } + blobServices: { + containers: [ + { + name: assetsStorageAccountContainerName + publicAccess: 'None' + roleAssignments: [ + { + // Allow Infra MSI to access storage account container to upload files - DO NOT REMOVE + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalId: dsMsi.outputs.principalId + principalType: 'ServicePrincipal' + } + { + // Allow image MSI to access storage account container to read files - DO NOT REMOVE + roleDefinitionIdOrName: 'Storage Blob Data Reader' + principalId: imageMSI.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + } + ] + } + } +} + +// Deployment scripts & their storage account +module dsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' = { + name: '${deployment().name}-ds-sa' + scope: rg + params: { + name: deploymentScriptStorageAccountName + allowSharedKeyAccess: true // May not be disabled to allow deployment script to access storage account files + roleAssignments: [ + { + // Allow MSI to leverage the storage account for private networking of container instance + // ref: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-bicep#access-private-virtual-network + roleDefinitionIdOrName: storageFileDataPrivilegedContributorRole.id // Storage File Data Priveleged Contributor + principalId: dsMsi.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + location: location + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ + { + // Allow deployment script to use storage account for private networking of container instance + action: 'Allow' + id: vnet.outputs.subnetResourceIds[1] // subnet-ds + } + ] + } + } +} + +@description('The image definitions used in the Azure Compute Gallery.') +output computeGalleryImageDefinitions array = computeGalleryImageDefinitionsVar + +@description('The name of the created Resource Group.') +output resourceGroupName string = rg.name + +@description('The name of the created Azure Compute Gallery') +output computeGalleryName string = azureComputeGallery.outputs.name + +@description('The name of the created Virtual Network') +output virtualNetworkName string = vnet.outputs.name + +@description('The name of the Storage Account Container hosting the customization files used by the Azure Image Builder.') +output assetsStorageAccountContainerName string = assetsStorageAccountContainerName + +@description('The name of the create Storage Account hosting the customization files used by the Azure Image Builder.') +output assetsStorageAccountName string = assetsStorageAccount.outputs.name + +@description('The name of the User-Assigned-Identity used by the Deployment Scripts.') +output deploymentScriptManagedIdentityName string = dsMsi.outputs.name + +@description('The name of the Storage Account used by the Deployment Scripts.') +output deploymentScriptStorageAccountName string = dsStorageAccount.outputs.name + +@description('The name of the subnet used by the Azure Image Builder.') +output imageSubnetName string = last(split(vnet.outputs.subnetResourceIds[0], '/')) + +@description('The name of the subnet used by the Deployment Scripts.') +output deploymentScriptSubnetName string = last(split(vnet.outputs.subnetResourceIds[1], '/')) + +@description('The name of the User-Assigned-Identity used by the Azure Image Builder.') +output imageManagedIdentityName string = imageMSI.outputs.name + +@description('The name of the Resource Group used by the Azure Image Builder.') +output imageTemplateResourceGroupName string = imageTemplateRg.outputs.name diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/main.test.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/main.test.bicep new file mode 100644 index 00000000000..849d005a0eb --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/main.test.bicep @@ -0,0 +1,90 @@ +targetScope = 'subscription' + +metadata name = 'Deploying only the assets & image' +metadata description = 'This instance deploys the module with the conditions set up to only update the assets on the assets storage account and build the image, assuming all dependencies are setup.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-virtualmachineimages.azureimagebuilder-${serviceShort}-rg' + +@description('Optional. The location to deploy resource group to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'apvmiaiboaai' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module nestedDependencies 'dependencies.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + // managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + computeGalleryName: 'dep${namePrefix}gal${serviceShort}' + deploymentScriptManagedIdentityName: 'dep-${namePrefix}-ds-msi-${serviceShort}' + imageManagedIdentityName: 'dep-${namePrefix}-it-msi-${serviceShort}' + resourceGroupName: resourceGroupName + imageTemplateResourceGroupName: '${resourceGroupName}-image-build' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + deploymentScriptStorageAccountName: 'dep${namePrefix}dsst${serviceShort}' + assetsStorageAccountName: 'dep${namePrefix}ast${serviceShort}' + location: resourceLocation + } +} + +///////////////////////////// +// Template Deployment // +///////////////////////////// +var exampleScriptName = 'exampleScript.sh' + +// No idempotency test as we don't want to bake 2 images +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + deploymentsToPerform: 'Only assets & image' + resourceGroupName: nestedDependencies.outputs.resourceGroupName + location: resourceLocation + computeGalleryName: nestedDependencies.outputs.computeGalleryName + computeGalleryImageDefinitionName: nestedDependencies.outputs.computeGalleryImageDefinitions[0].name + computeGalleryImageDefinitions: nestedDependencies.outputs.computeGalleryImageDefinitions + virtualNetworkName: nestedDependencies.outputs.virtualNetworkName + assetsStorageAccountContainerName: nestedDependencies.outputs.assetsStorageAccountContainerName + assetsStorageAccountName: nestedDependencies.outputs.assetsStorageAccountName + deploymentScriptManagedIdentityName: nestedDependencies.outputs.deploymentScriptManagedIdentityName + deploymentScriptStorageAccountName: nestedDependencies.outputs.deploymentScriptStorageAccountName + deploymentScriptSubnetName: nestedDependencies.outputs.deploymentScriptSubnetName + imageManagedIdentityName: nestedDependencies.outputs.imageManagedIdentityName + imageSubnetName: nestedDependencies.outputs.imageSubnetName + imageTemplateResourceGroupName: nestedDependencies.outputs.imageTemplateResourceGroupName + imageTemplateCustomizationSteps: [ + { + type: 'Shell' + name: 'Example script' + scriptUri: 'https://${nestedDependencies.outputs.assetsStorageAccountName}.blob.${az.environment().suffixes.storage}/${nestedDependencies.outputs.assetsStorageAccountContainerName}/${exampleScriptName}' + } + ] + imageTemplateImageSource: { + type: 'PlatformImage' + publisher: 'canonical' + offer: 'ubuntu-24_04-lts' + sku: 'server' + version: 'latest' + } + storageAccountFilesToUpload: [ + { + name: exampleScriptName + value: loadTextContent('scripts/${exampleScriptName}') + } + ] + } +} diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/scripts/exampleScript.sh b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/scripts/exampleScript.sh new file mode 100644 index 00000000000..22cd6c612ff --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyAssetsAndImage/scripts/exampleScript.sh @@ -0,0 +1,4 @@ +echo '###############################################' +echo '# Hey there. Just checking the place out. #' +echo '###############################################' + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyBase/main.test.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyBase/main.test.bicep new file mode 100644 index 00000000000..1735445bded --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyBase/main.test.bicep @@ -0,0 +1,59 @@ +targetScope = 'subscription' + +metadata name = 'Deploying only the base services' +metadata description = 'This instance deploys the module with the conditions set up to only deploy the base resources, that is everything but the image.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-virtualmachineimages.azureimagebuilder-${serviceShort}-rg' + +@description('Optional. The location to deploy resource group to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'apvmiaibob' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +///////////////////////////// +// Template Deployment // +///////////////////////////// +var computeGalleryImageDefinitionName = 'sid-linux' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + deploymentsToPerform: 'Only base' + resourceGroupName: resourceGroupName + location: resourceLocation + assetsStorageAccountName: 'st${namePrefix}${serviceShort}' + imageManagedIdentityName: 'msi-it-${namePrefix}-${serviceShort}' + computeGalleryName: 'gal${namePrefix}${serviceShort}' + computeGalleryImageDefinitionName: computeGalleryImageDefinitionName + computeGalleryImageDefinitions: [ + { + hyperVGeneration: 'V2' + name: computeGalleryImageDefinitionName + osType: 'Linux' + publisher: 'devops' + offer: 'devops_linux' + sku: 'devops_linux_az' + } + ] + imageTemplateImageSource: { + type: 'PlatformImage' + publisher: 'canonical' + offer: 'ubuntu-24_04-lts' + sku: 'server' + version: 'latest' + } + } + } +] diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/dependencies.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/dependencies.bicep new file mode 100644 index 00000000000..fe793a0e85b --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/dependencies.bicep @@ -0,0 +1,303 @@ +targetScope = 'subscription' + +@description('Required. The name of the Resource Group.') +param resourceGroupName string + +@description('Required. The name of the Resource Group to deploy the Image Template resources into.') +param imageTemplateResourceGroupName string + +// User Assigned Identity (MSI) Parameters +@description('Required. The name of the Managed Identity used by deployment scripts.') +param deploymentScriptManagedIdentityName string + +@description('Required. The name of the Managed Identity used by the Azure Image Builder.') +param imageManagedIdentityName string + +// Azure Compute Gallery Parameters +@description('Required. The name of the Azure Compute Gallery.') +param computeGalleryName string + +// Storage Account Parameters +@description('Required. The name of the storage account. Only needed if you want to upload scripts to be used during image baking.') +param assetsStorageAccountName string + +@description('Required. The name of the storage account.') +param deploymentScriptStorageAccountName string + +@description('Required. The name of the Deployment Script to the Storage Upload.') +param storageDeploymentScriptName string + +// Virtual Network Parameters +@description('Required. The name of the Virtual Network.') +param virtualNetworkName string + +// Shared Parameters +@description('Optional. The location to deploy into.') +param location string = deployment().location + +var exampleScriptName = 'exampleScript.sh' +var addressPrefix = '10.0.0.0/16' + +// The Image Definitions in the Azure Compute Gallery +var computeGalleryImageDefinitionsVar = [ + { + hyperVGeneration: 'V2' + name: 'sid-linux' + osType: 'Linux' + publisher: 'devops' + offer: 'devops_linux' + sku: 'devops_linux_az' + } +] +var assetsStorageAccountContainerName = 'aibscripts' + +// Role required for deployment script to be able to use a storage account via private networking +resource storageFileDataPrivilegedContributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Priveleged Contributor + scope: tenant() +} +resource contributorRole 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor + scope: tenant() +} + +// Resource Groups +resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = { + name: resourceGroupName + location: location +} + +// Always deployed as both an infra element & needed as a staging resource group for image building +module imageTemplateRg 'br/public:avm/res/resources/resource-group:0.2.4' = { + name: '${deployment().name}-image-rg' + params: { + name: imageTemplateResourceGroupName + location: location + } +} + +// User Assigned Identity (MSI) +module dsMsi 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.2' = { + name: '${deployment().name}-ds-msi' + scope: rg + params: { + name: deploymentScriptManagedIdentityName + location: location + } +} + +module imageMSI 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.2' = { + name: '${deployment().name}-image-msi' + scope: rg + params: { + name: imageManagedIdentityName + location: location + } +} + +// MSI Subscription contributor assignment +resource imageMSI_rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(subscription().subscriptionId, imageManagedIdentityName, contributorRole.id) + properties: { + principalId: imageMSI.outputs.principalId + roleDefinitionId: contributorRole.id + principalType: 'ServicePrincipal' + } +} + +// Azure Compute Gallery +module azureComputeGallery 'br/public:avm/res/compute/gallery:0.4.0' = { + name: '${deployment().name}-acg' + scope: rg + params: { + name: computeGalleryName + images: computeGalleryImageDefinitionsVar + location: location + } +} + +// Image Template Virtual Network +module vnet 'br/public:avm/res/network/virtual-network:0.1.6' = { + name: '${deployment().name}-vnet' + scope: rg + params: { + name: virtualNetworkName + addressPrefixes: [ + addressPrefix + ] + subnets: [ + { + name: 'subnet-it' + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + } + { + name: 'subnet-ds' + addressPrefix: cidrSubnet(addressPrefix, 24, 1) + privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET - temp + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + delegations: [ + { + name: 'Microsoft.ContainerInstance.containerGroups' + properties: { + serviceName: 'Microsoft.ContainerInstance/containerGroups' + } + } + ] + } + ] + location: location + } +} + +// Assets Storage Account +module assetsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' = { + name: '${deployment().name}-files-sa' + scope: rg + params: { + name: assetsStorageAccountName + allowSharedKeyAccess: false // Keys not needed if MSI is granted access + location: location + networkAcls: { + defaultAction: 'Allow' + } + blobServices: { + containers: [ + { + name: assetsStorageAccountContainerName + publicAccess: 'None' + roleAssignments: [ + { + // Allow Infra MSI to access storage account container to upload files - DO NOT REMOVE + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalId: dsMsi.outputs.principalId + principalType: 'ServicePrincipal' + } + { + // Allow image MSI to access storage account container to read files - DO NOT REMOVE + roleDefinitionIdOrName: 'Storage Blob Data Reader' + principalId: imageMSI.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + } + ] + } + } +} + +// Deployment scripts & their storage account +module dsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' = { + name: '${deployment().name}-ds-sa' + scope: rg + params: { + name: deploymentScriptStorageAccountName + allowSharedKeyAccess: true // May not be disabled to allow deployment script to access storage account files + roleAssignments: [ + { + // Allow MSI to leverage the storage account for private networking of container instance + // ref: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-bicep#access-private-virtual-network + roleDefinitionIdOrName: storageFileDataPrivilegedContributorRole.id // Storage File Data Priveleged Contributor + principalId: dsMsi.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + location: location + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + virtualNetworkRules: [ + { + // Allow deployment script to use storage account for private networking of container instance + action: 'Allow' + id: vnet.outputs.subnetResourceIds[1] // subnet-ds + } + ] + } + } +} + +// Upload storage account files +module storageAccount_upload 'br/public:avm/res/resources/deployment-script:0.3.1' = { + name: '${deployment().name}-storage-upload-ds' + scope: resourceGroup(resourceGroupName) + params: { + name: storageDeploymentScriptName + kind: 'AzurePowerShell' + azPowerShellVersion: '12.0' + managedIdentities: { + userAssignedResourcesIds: [ + resourceId( + subscription().subscriptionId, + resourceGroupName, + 'Microsoft.ManagedIdentity/userAssignedIdentities', + deploymentScriptManagedIdentityName + ) + ] + } + scriptContent: loadTextContent('../../../../../../utilities/e2e-template-assets/scripts/Set-StorageContainerContentByEnvVar.ps1') + environmentVariables: [ + { + name: '__SCRIPT__${replace(replace(exampleScriptName, '-', '__'), '.', '_') }' // May only be alphanumeric characters & underscores. The upload will replace '_' with '.' and '__' with '-'. E.g., Install__LinuxPowerShell_sh will be Install-LinuxPowerShell.sh + value: loadTextContent('scripts/${exampleScriptName}') + } + ] + + arguments: ' -StorageAccountName "${assetsStorageAccountName}" -TargetContainer "${assetsStorageAccountContainerName}"' + timeout: 'PT30M' + cleanupPreference: 'Always' + location: location + storageAccountResourceId: dsStorageAccount.outputs.resourceId + subnetResourceIds: [ + vnet.outputs.subnetResourceIds[1] // subnet-ds + ] + } +} + +@description('The image definitions used in the Azure Compute Gallery.') +output computeGalleryImageDefinitions array = computeGalleryImageDefinitionsVar + +@description('The name of the created Resource Group.') +output resourceGroupName string = rg.name + +@description('The name of the created Azure Compute Gallery') +output computeGalleryName string = azureComputeGallery.outputs.name + +@description('The name of the created Virtual Network') +output virtualNetworkName string = vnet.outputs.name + +@description('The name of the Storage Account Container hosting the customization files used by the Azure Image Builder.') +output assetsStorageAccountContainerName string = assetsStorageAccountContainerName + +@description('The name of the create Storage Account hosting the customization files used by the Azure Image Builder.') +output assetsStorageAccountName string = assetsStorageAccount.outputs.name + +@description('The name of the User-Assigned-Identity used by the Deployment Scripts.') +output deploymentScriptManagedIdentityName string = dsMsi.outputs.name + +@description('The name of the Storage Account used by the Deployment Scripts.') +output deploymentScriptStorageAccountName string = dsStorageAccount.outputs.name + +@description('The name of the subnet used by the Azure Image Builder.') +output imageSubnetName string = last(split(vnet.outputs.subnetResourceIds[0], '/')) + +@description('The name of the subnet used by the Deployment Scripts.') +output deploymentScriptSubnetName string = last(split(vnet.outputs.subnetResourceIds[1], '/')) + +@description('The name of the User-Assigned-Identity used by the Azure Image Builder.') +output imageManagedIdentityName string = imageMSI.outputs.name + +@description('The name of the Resource Group used by the Azure Image Builder.') +output imageTemplateResourceGroupName string = imageTemplateRg.outputs.name + +@description('The name of the script uploaded to the Assets Storage Account to use in the Azure Image Builder customization steps.') +output exampleScriptName string = exampleScriptName diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/main.test.bicep b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/main.test.bicep new file mode 100644 index 00000000000..c777fa31dac --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/main.test.bicep @@ -0,0 +1,87 @@ +targetScope = 'subscription' + +metadata name = 'Deploying only the image' +metadata description = 'This instance deploys the module with the conditions set up to only deploy and bake the image, assuming all dependencies are setup.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-virtualmachineimages.azureimagebuilder-${serviceShort}-rg' + +@description('Optional. The location to deploy resource group to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'apvmiaiboi' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +@description('Generated. Do not provide a value! This date value is used to generate a SAS token to access the modules.') +param baseTime string = utcNow() + +var formattedTime = replace(replace(replace(baseTime, ':', ''), '-', ''), ' ', '') + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +module nestedDependencies 'dependencies.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + // managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + computeGalleryName: 'dep${namePrefix}gal${serviceShort}' + deploymentScriptManagedIdentityName: 'dep-${namePrefix}-ds-msi-${serviceShort}' + imageManagedIdentityName: 'dep-${namePrefix}-it-msi-${serviceShort}' + resourceGroupName: resourceGroupName + imageTemplateResourceGroupName: '${resourceGroupName}-image-build' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + deploymentScriptStorageAccountName: 'dep${namePrefix}dsst${serviceShort}' + storageDeploymentScriptName: 'dep-${namePrefix}-ds-${serviceShort}-${formattedTime}' + assetsStorageAccountName: 'dep${namePrefix}ast${serviceShort}' + location: resourceLocation + } +} + +///////////////////////////// +// Template Deployment // +///////////////////////////// + +// No idempotency test as we don't want to bake 2 images +module testDeployment '../../../main.bicep' = { + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}' + params: { + deploymentsToPerform: 'Only image' + resourceGroupName: nestedDependencies.outputs.resourceGroupName + location: resourceLocation + computeGalleryName: nestedDependencies.outputs.computeGalleryName + computeGalleryImageDefinitions: nestedDependencies.outputs.computeGalleryImageDefinitions + computeGalleryImageDefinitionName: nestedDependencies.outputs.computeGalleryImageDefinitions[0].name + virtualNetworkName: nestedDependencies.outputs.virtualNetworkName + deploymentScriptManagedIdentityName: nestedDependencies.outputs.deploymentScriptManagedIdentityName + deploymentScriptStorageAccountName: nestedDependencies.outputs.deploymentScriptStorageAccountName + deploymentScriptSubnetName: nestedDependencies.outputs.deploymentScriptSubnetName + imageManagedIdentityName: nestedDependencies.outputs.imageManagedIdentityName + imageSubnetName: nestedDependencies.outputs.imageSubnetName + imageTemplateResourceGroupName: nestedDependencies.outputs.imageTemplateResourceGroupName + imageTemplateImageSource: { + type: 'PlatformImage' + publisher: 'canonical' + offer: 'ubuntu-24_04-lts' + sku: 'server' + version: 'latest' + } + imageTemplateCustomizationSteps: [ + { + type: 'Shell' + name: 'Example script' + scriptUri: 'https://${nestedDependencies.outputs.assetsStorageAccountName}.blob.${az.environment().suffixes.storage}/${nestedDependencies.outputs.assetsStorageAccountContainerName}/${nestedDependencies.outputs.exampleScriptName}' + } + ] + } +} diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/scripts/exampleScript.sh b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/scripts/exampleScript.sh new file mode 100644 index 00000000000..22cd6c612ff --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/tests/e2e/deployOnlyImage/scripts/exampleScript.sh @@ -0,0 +1,4 @@ +echo '###############################################' +echo '# Hey there. Just checking the place out. #' +echo '###############################################' + diff --git a/avm/ptn/virtual-machine-images/azure-image-builder/version.json b/avm/ptn/virtual-machine-images/azure-image-builder/version.json new file mode 100644 index 00000000000..83083db6945 --- /dev/null +++ b/avm/ptn/virtual-machine-images/azure-image-builder/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} \ No newline at end of file diff --git a/avm/utilities/e2e-template-assets/scripts/Wait-ForImageBuild.ps1 b/avm/utilities/e2e-template-assets/scripts/Wait-ForImageBuild.ps1 new file mode 100644 index 00000000000..bce2306a216 --- /dev/null +++ b/avm/utilities/e2e-template-assets/scripts/Wait-ForImageBuild.ps1 @@ -0,0 +1,97 @@ +<# +.SYNOPSIS +Fetch the latest build status for the provided image template + +.DESCRIPTION +Fetch the latest build status for the provided image template + +.PARAMETER ResourceGroupName +Required. The name of the Resource Group containing the image template + +.PARAMETER ImageTemplateName +Required. The name of the image template to query to build status for. E.g. 'lin_it-2022-02-20-16-17-38' + +.EXAMPLE +. 'Wait-ForImageBuild.ps1' -ResourceGroupName' 'myRG' -ImageTemplateName 'lin_it-2022-02-20-16-17-38' + +Check the current build status of Image Template 'lin_it-2022-02-20-16-17-38' in Resource Group 'myRG' +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string] $ResourceGroupName, + + [Parameter(Mandatory)] + [string] $ImageTemplateName +) + +begin { + Write-Debug ('[{0} entered]' -f $MyInvocation.MyCommand) +} + +process { + # Logic + # ----- + $context = Get-AzContext + $subscriptionId = $context.Subscription.Id + $currentRetry = 1 + $maximumRetries = 720 + $timeToWait = 15 + $maxTimeCalc = '{0:hh\:mm\:ss}' -f [timespan]::fromseconds($maximumRetries * $timeToWait) + do { + + # Runnning fetch in retry as it happened that the status was not available + $statusFetchRetryCount = 3 + $statusFetchCurrentRetry = 1 + do { + $path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.VirtualMachineImages/imageTemplates/{2}?api-version=2020-02-14' -f $subscriptionId, $ResourceGroupName, $ImageTemplateName + $requestInputObject = @{ + Method = 'GET' + Path = $path + } + + $response = ((Invoke-AzRestMethod @requestInputObject).Content | ConvertFrom-Json).properties + + if ($response.lastRunStatus) { + $latestStatus = $response.lastRunStatus + break + } + Start-Sleep 5 + $statusFetchCurrentRetry++ + } while ($statusFetchCurrentRetry -le $statusFetchRetryCount) + + if (-not $latestStatus) { + Write-Verbose ('Image Build failed with error: [{0}]' -f $response.provisioningError.message) -Verbose + $latestStatus = 'failed' + } + + + if ($latestStatus -eq 'failed' -or $latestStatus.runState.ToLower() -eq 'failed') { + $failedMessage = 'Image Template [{0}] build failed with status [{1}]. API reply: [{2}]' -f $ImageTemplateName, $latestStatus.runState, $response.lastRunStatus.message + Write-Verbose $failedMessage -Verbose + throw $failedMessage + } + + if ($latestStatus.runState.ToLower() -notIn @('running', 'new')) { + break + } + + $currTimeCalc = '{0:hh\:mm\:ss}' -f [timespan]::fromseconds($currentRetry * $timeToWait) + + Write-Verbose ('[{0}] Waiting 15 seconds [{1}|{2}]' -f (Get-Date -Format 'HH:mm:ss'), $currTimeCalc, $maxTimeCalc) -Verbose + $currentRetry++ + Start-Sleep $timeToWait + } while ($currentRetry -le $maximumRetries) + + if ($latestStatus) { + $duration = New-TimeSpan -Start $latestStatus.startTime -End $latestStatus.endTime + Write-Verbose ('It took [{0}] minutes and [{1}] seconds to build and distribute the image.' -f $duration.Minutes, $duration.Seconds) -Verbose + } else { + Write-Warning "Timeout at [$currTimeCalc]. Note, the Azure Image Builder may still succeed." + } + return $latestStatus +} + +end { + Write-Debug ('[{0} existed]' -f $MyInvocation.MyCommand) +} diff --git a/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 b/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 index b2723eaaf1c..455ba34ee06 100644 --- a/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 +++ b/avm/utilities/pipelines/e2eValidation/resourceRemoval/helper/Invoke-ResourceRemoval.ps1 @@ -147,6 +147,64 @@ function Invoke-ResourceRemoval { } break } + 'Microsoft.VirtualMachineImages/imageTemplates' { + # Note: If you ever run into the issue that you cannot remove the image template because of an issue with the MSI (e.g., because the below logic was not executed in the pipeline), you can follow these manual steps: + # 1. Unassign the existing MSI (az image builder identity remove --resource-group --name --user-assigned --yes) + # 2. Trigger image template removal (will fail, but remove the cached 'running' state) + # 3. Assign a new MSI (az image builder identity assign --resource-group --name --user-assigned ) + # 4. Trigger image template removal again, which removes the resource for good + + $resourceGroupName = $ResourceId.Split('/')[4] + $resourceName = Split-Path $ResourceId -Leaf + + # Remove resource + if ($PSCmdlet.ShouldProcess("Image Template [$resourceName]", 'Remove')) { + + $removeRequestInputObject = @{ + Method = 'DELETE' + Path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.VirtualMachineImages/imageTemplates/{2}?api-version=2022-07-01' -f $subscriptionId, $resourceGroupName, $resourceName + } + $removalResponse = Invoke-AzRestMethod @removeRequestInputObject + if ($removalResponse.StatusCode -notlike '2*') { + $responseContent = $removalResponse.Content | ConvertFrom-Json + throw ('{0} : {1}' -f $responseContent.error.code, $responseContent.error.message) + } + + # Wait for template to be removed. If we don't wait, it can happen that its MSI is removed too soon, locking the resource from deletion + $retryCount = 1 + $retryLimit = 240 + $retryInterval = 15 + do { + $getRequestInputObject = @{ + Method = 'GET' + Path = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.VirtualMachineImages/imageTemplates/{2}?api-version=2022-07-01' -f $subscriptionId, $resourceGroupName, $resourceName + } + $getReponse = Invoke-AzRestMethod @getRequestInputObject + + if ($getReponse.StatusCode -eq 400) { + # Invalid request + throw ($imageTgetReponseemplate.Content | ConvertFrom-Json).error.message + } elseif ($getReponse.StatusCode -eq 404) { + # Resource not found, removal was successful + $templateExists = $false + } elseif ($getReponse.StatusCode -eq '200') { + # Resource still around - try again + $templateExists = $true + Write-Verbose (' [⏱️] Waiting {0} seconds for Image Template to be removed. [{1}/{2}]' -f $retryInterval, $retryCount, $retryLimit) -Verbose + Start-Sleep -Seconds $retryInterval + $retryCount++ + } else { + throw ('Failed request. Response: [{0}]' -f ($getReponse | Out-String)) + } + } while ($templateExists -and $retryCount -lt $retryLimit) + + if ($retryCount -ge $retryLimit) { + Write-Warning (' [!] Image Template [{0}] was not removed after {1} seconds. Continuing with resource removal.' -f $resourceName, ($retryCount * $retryInterval)) + break + } + } + break + } 'Microsoft.MachineLearningServices/workspaces' { $subscriptionId = $ResourceId.Split('/')[2] $resourceGroupName = $ResourceId.Split('/')[4] From 7876f362de2c57cd4e43b514539ffb126c8ced1a Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Wed, 18 Sep 2024 05:45:40 +0200 Subject: [PATCH 20/46] fix: Add key vault to cognitive service - avm/res/cognitive-services/account (#3222) ## Description Follow-up to: #1932 cc: @Agazoth | Pipeline | | -------- | [![avm.res.cognitive-services.account](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml/badge.svg?branch=users%2Falsehr%2FAgazoth%2FAddKVToCognitiveService&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.cognitive-services.account.yml) ## Type of Change - [ ] Update to CI Environment or utlities (Non-module effecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Axel B. Andersen Co-authored-by: Javier Cevallos --- avm/res/cognitive-services/account/README.md | 137 ++++++++++-- avm/res/cognitive-services/account/main.bicep | 55 +++++ avm/res/cognitive-services/account/main.json | 207 +++++++++++++++++- .../account/modules/keyVaultExport.bicep | 62 ++++++ .../default-with-key-vault/dependencies.bicep | 21 ++ .../default-with-key-vault/main.test.bicep | 63 ++++++ 6 files changed, 530 insertions(+), 15 deletions(-) create mode 100644 avm/res/cognitive-services/account/modules/keyVaultExport.bicep create mode 100644 avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/dependencies.bicep create mode 100644 avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/main.test.bicep diff --git a/avm/res/cognitive-services/account/README.md b/avm/res/cognitive-services/account/README.md index 32e22ce46f5..7509f836a88 100644 --- a/avm/res/cognitive-services/account/README.md +++ b/avm/res/cognitive-services/account/README.md @@ -20,6 +20,7 @@ This module deploys a Cognitive Service. | `Microsoft.CognitiveServices/accounts` | [2023-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.CognitiveServices/2023-05-01/accounts) | | `Microsoft.CognitiveServices/accounts/deployments` | [2023-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.CognitiveServices/2023-05-01/accounts/deployments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | +| `Microsoft.KeyVault/vaults/secrets` | [2023-07-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.KeyVault/2023-07-01/vaults/secrets) | | `Microsoft.Network/privateEndpoints` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints/privateDnsZoneGroups) | @@ -33,13 +34,14 @@ The following section provides usage examples for the module, which were used to - [Using `AIServices` with `deployments` in parameter set and private endpoints](#example-1-using-aiservices-with-deployments-in-parameter-set-and-private-endpoints) - [Using `AIServices` with `deployments` in parameter set](#example-2-using-aiservices-with-deployments-in-parameter-set) -- [Using only defaults](#example-3-using-only-defaults) -- [Using large parameter set](#example-4-using-large-parameter-set) -- [Using `OpenAI` and `deployments` in parameter set with private endpoint](#example-5-using-openai-and-deployments-in-parameter-set-with-private-endpoint) -- [As Speech Service](#example-6-as-speech-service) -- [Using Customer-Managed-Keys with System-Assigned identity](#example-7-using-customer-managed-keys-with-system-assigned-identity) -- [Using Customer-Managed-Keys with User-Assigned identity](#example-8-using-customer-managed-keys-with-user-assigned-identity) -- [WAF-aligned](#example-9-waf-aligned) +- [Storing keys of service in key vault](#example-3-storing-keys-of-service-in-key-vault) +- [Using only defaults](#example-4-using-only-defaults) +- [Using large parameter set](#example-5-using-large-parameter-set) +- [Using `OpenAI` and `deployments` in parameter set with private endpoint](#example-6-using-openai-and-deployments-in-parameter-set-with-private-endpoint) +- [As Speech Service](#example-7-as-speech-service) +- [Using Customer-Managed-Keys with System-Assigned identity](#example-8-using-customer-managed-keys-with-system-assigned-identity) +- [Using Customer-Managed-Keys with User-Assigned identity](#example-9-using-customer-managed-keys-with-user-assigned-identity) +- [WAF-aligned](#example-10-waf-aligned) ### Example 1: _Using `AIServices` with `deployments` in parameter set and private endpoints_ @@ -237,7 +239,71 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 3: _Using only defaults_ +### Example 3: _Storing keys of service in key vault_ + +This instance deploys the module and stores its keys in a key vault. + + +

+ +via Bicep module + +```bicep +module account 'br/public:avm/res/cognitive-services/account:' = { + name: 'accountDeployment' + params: { + // Required parameters + kind: 'SpeechServices' + name: 'csakv001' + // Non-required parameters + location: '' + secretsExportConfiguration: { + accessKey1Name: 'csakv001-accessKey1' + accessKey2Name: 'csakv001-accessKey2' + keyVaultResourceId: '' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "kind": { + "value": "SpeechServices" + }, + "name": { + "value": "csakv001" + }, + // Non-required parameters + "location": { + "value": "" + }, + "secretsExportConfiguration": { + "value": { + "accessKey1Name": "csakv001-accessKey1", + "accessKey2Name": "csakv001-accessKey2", + "keyVaultResourceId": "" + } + } + } +} +``` + +
+

+ +### Example 4: _Using only defaults_ This instance deploys the module with the minimum set of required parameters. @@ -289,7 +355,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 4: _Using large parameter set_ +### Example 5: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -581,7 +647,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 5: _Using `OpenAI` and `deployments` in parameter set with private endpoint_ +### Example 6: _Using `OpenAI` and `deployments` in parameter set with private endpoint_ This instance deploys the module with the AI model deployment feature and private endpoint. @@ -689,7 +755,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 6: _As Speech Service_ +### Example 7: _As Speech Service_ This instance deploys the module as a Speech Service. @@ -803,7 +869,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 7: _Using Customer-Managed-Keys with System-Assigned identity_ +### Example 8: _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. @@ -885,7 +951,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 8: _Using Customer-Managed-Keys with User-Assigned identity_ +### Example 9: _Using Customer-Managed-Keys with User-Assigned identity_ This instance deploys the module using Customer-Managed-Keys using a User-Assigned Identity to access the Customer-Managed-Key secret. @@ -973,7 +1039,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = {

-### Example 9: _WAF-aligned_ +### Example 10: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -1146,6 +1212,7 @@ module account 'br/public:avm/res/cognitive-services/account:' = { | [`restore`](#parameter-restore) | bool | Restore a soft-deleted cognitive service at deployment time. Will fail if no such soft-deleted resource exists. | | [`restrictOutboundNetworkAccess`](#parameter-restrictoutboundnetworkaccess) | bool | Restrict outbound network access. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`secretsExportConfiguration`](#parameter-secretsexportconfiguration) | object | Key vault reference and secret settings for the module's secrets export. | | [`sku`](#parameter-sku) | string | SKU of the Cognitive Services resource. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region. | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`userOwnedStorage`](#parameter-userownedstorage) | array | The storage accounts for this resource. | @@ -2142,6 +2209,47 @@ The principal type of the assigned principal ID. ] ``` +### Parameter: `secretsExportConfiguration` + +Key vault reference and secret settings for the module's secrets export. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`keyVaultResourceId`](#parameter-secretsexportconfigurationkeyvaultresourceid) | string | The key vault name where to store the keys and connection strings generated by the modules. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`accessKey1Name`](#parameter-secretsexportconfigurationaccesskey1name) | string | The name for the accessKey1 secret to create. | +| [`accessKey2Name`](#parameter-secretsexportconfigurationaccesskey2name) | string | The name for the accessKey2 secret to create. | + +### Parameter: `secretsExportConfiguration.keyVaultResourceId` + +The key vault name where to store the keys and connection strings generated by the modules. + +- Required: Yes +- Type: string + +### Parameter: `secretsExportConfiguration.accessKey1Name` + +The name for the accessKey1 secret to create. + +- Required: No +- Type: string + +### Parameter: `secretsExportConfiguration.accessKey2Name` + +The name for the accessKey2 secret to create. + +- Required: No +- Type: string + ### Parameter: `sku` SKU of the Cognitive Services resource. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region. @@ -2192,6 +2300,7 @@ The storage accounts for this resource. | :-- | :-- | :-- | | `endpoint` | string | The service endpoint of the cognitive services account. | | `endpoints` | | All endpoints available for the cognitive services account, types depends on the cognitive service kind. | +| `exportedSecrets` | | A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name. | | `location` | string | The location the resource was deployed into. | | `name` | string | The name of the cognitive services account. | | `resourceGroupName` | string | The resource group the cognitive services account was deployed into. | diff --git a/avm/res/cognitive-services/account/main.bicep b/avm/res/cognitive-services/account/main.bicep index 0080ac6d3a0..eb12753477d 100644 --- a/avm/res/cognitive-services/account/main.bicep +++ b/avm/res/cognitive-services/account/main.bicep @@ -123,6 +123,9 @@ param enableTelemetry bool = true @description('Optional. Array of deployments about cognitive service accounts to create.') param deployments deploymentsType +@description('Optional. Key vault reference and secret settings for the module\'s secrets export.') +param secretsExportConfiguration secretsExportConfigurationType? + var formattedUserAssignedIdentities = reduce( map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), {}, @@ -468,6 +471,36 @@ resource cognitiveService_roleAssignments 'Microsoft.Authorization/roleAssignmen } ] +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!, 'accessKey1Name') + ? [ + { + name: secretsExportConfiguration!.accessKey1Name + value: cognitiveService.listKeys().key1 + } + ] + : [], + contains(secretsExportConfiguration!, 'accessKey2Name') + ? [ + { + name: secretsExportConfiguration!.accessKey2Name + value: cognitiveService.listKeys().key2 + } + ] + : [] + ) + } +} + @description('The name of the cognitive services account.') output name string = cognitiveService.name @@ -489,6 +522,11 @@ output systemAssignedMIPrincipalId string = cognitiveService.?identity.?principa @description('The location the resource was deployed into.') output location string = cognitiveService.location +@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 // // ================ // @@ -706,3 +744,20 @@ type endpointsType = { @description('The endpoint URI.') endpoint: string? }? + +type secretsExportConfigurationType = { + @description('Required. The key vault name where to store the keys and connection strings generated by the modules.') + keyVaultResourceId: string + + @description('Optional. The name for the accessKey1 secret to create.') + accessKey1Name: string? + + @description('Optional. The name for the accessKey2 secret to create.') + accessKey2Name: string? +} + +import { secretSetType } from 'modules/keyVaultExport.bicep' +type secretsOutputType = { + @description('An exported secret\'s references.') + *: secretSetType +} diff --git a/avm/res/cognitive-services/account/main.json b/avm/res/cognitive-services/account/main.json index 2767bbb995c..27a5d3ba80c 100644 --- a/avm/res/cognitive-services/account/main.json +++ b/avm/res/cognitive-services/account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "14735378599748380229" + "templateHash": "259342811715055071" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service.", @@ -573,6 +573,63 @@ } }, "nullable": true + }, + "secretsExportConfigurationType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The key vault name where to store the keys and connection strings generated by the modules." + } + }, + "accessKey1Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name for the accessKey1 secret to create." + } + }, + "accessKey2Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name for the accessKey2 secret to create." + } + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/secretSetType", + "metadata": { + "description": "An exported secret's references." + } + } + }, + "secretSetType": { + "type": "object", + "properties": { + "secretResourceId": { + "type": "string", + "metadata": { + "description": "The resourceId of the exported secret." + } + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The secret URI of the exported secret." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "modules/keyVaultExport.bicep" + } + } } }, "parameters": { @@ -783,6 +840,13 @@ "metadata": { "description": "Optional. Array of deployments about cognitive service accounts to create." } + }, + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Key vault reference and secret settings for the module's secrets export." + } } }, "variables": { @@ -1692,6 +1756,140 @@ "dependsOn": [ "cognitiveService" ] + }, + "secretsExport": { + "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[split(coalesce(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '//'), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '////'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(coalesce(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '//'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', parameters('secretsExportConfiguration').accessKey1Name, 'value', listKeys(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2023-05-01').key1)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', parameters('secretsExportConfiguration').accessKey2Name, 'value', listKeys(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2023-05-01').key2)), createArray()))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "986606208324987345" + } + }, + "definitions": { + "secretSetType": { + "type": "object", + "properties": { + "secretResourceId": { + "type": "string", + "metadata": { + "description": "The resourceId of the exported secret." + } + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The secret URI of the exported secret." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "secretToSetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret to set." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret to set." + } + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Key Vault to set the ecrets in." + } + }, + "secretsToSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretToSetType" + }, + "metadata": { + "description": "Required. The secrets to set in the Key Vault." + } + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "secrets": { + "copy": { + "name": "secrets", + "count": "[length(parameters('secretsToSet'))]" + }, + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]", + "properties": { + "value": "[parameters('secretsToSet')[copyIndex()].value]" + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetType" + }, + "metadata": { + "description": "The references to the secrets exported to the provided Key Vault." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]", + "input": { + "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]", + "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]" + } + } + } + } + } + }, + "dependsOn": [ + "cognitiveService" + ] } }, "outputs": { @@ -1743,6 +1941,13 @@ "description": "The location the resource was deployed into." }, "value": "[reference('cognitiveService', '2023-05-01', 'full').location]" + }, + "exportedSecrets": { + "$ref": "#/definitions/secretsOutputType", + "metadata": { + "description": "A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name." + }, + "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]" } } } \ No newline at end of file diff --git a/avm/res/cognitive-services/account/modules/keyVaultExport.bicep b/avm/res/cognitive-services/account/modules/keyVaultExport.bicep new file mode 100644 index 00000000000..d537d2407e3 --- /dev/null +++ b/avm/res/cognitive-services/account/modules/keyVaultExport.bicep @@ -0,0 +1,62 @@ +// ============== // +// Parameters // +// ============== // + +@description('Required. The name of the Key Vault to set the ecrets in.') +param keyVaultName string + +@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 // +// =========== // + +@description('The references to the secrets exported to the provided Key Vault.') +output secretsSet secretSetType[] = [ + #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 + } +] + +// =============== // +// Definitions // +// =============== // + +@export() +type secretSetType = { + @description('The resourceId of the exported secret.') + secretResourceId: string + + @description('The secret URI of the exported secret.') + secretUri: string +} + +type secretToSetType = { + @description('Required. The name of the secret to set.') + name: string + + @description('Required. The value of the secret to set.') + @secure() + value: string +} diff --git a/avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/dependencies.bicep b/avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/dependencies.bicep new file mode 100644 index 00000000000..61c051d86d7 --- /dev/null +++ b/avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/dependencies.bicep @@ -0,0 +1,21 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param keyVaultName string + +resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = { + name: keyVaultName + location: location + properties: { + sku: { + family: 'A' + name: 'standard' + } + enableRbacAuthorization: true + tenantId: subscription().tenantId + } +} + +@description('The name of the Key Vault created.') +output keyVaultResourceId string = keyVault.id diff --git a/avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/main.test.bicep b/avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/main.test.bicep new file mode 100644 index 00000000000..faeae8df018 --- /dev/null +++ b/avm/res/cognitive-services/account/tests/e2e/default-with-key-vault/main.test.bicep @@ -0,0 +1,63 @@ +targetScope = 'subscription' + +metadata name = 'Storing keys of service in key vault' +metadata description = 'This instance deploys the module and stores its keys in a key vault.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-cognitiveservices.accounts-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'csakv' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + keyVaultName: 'dep-${namePrefix}-kv-${serviceShort}' + location: resourceLocation + } +} + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + kind: 'SpeechServices' + location: resourceLocation + secretsExportConfiguration: { + keyVaultResourceId: nestedDependencies.outputs.keyVaultResourceId + accessKey1Name: '${namePrefix}${serviceShort}001-accessKey1' + accessKey2Name: '${namePrefix}${serviceShort}001-accessKey2' + } + } + } +] From 180cc1a06900161acf80790d3728cd9012f3645d Mon Sep 17 00:00:00 2001 From: John Date: Wed, 18 Sep 2024 20:16:59 +0200 Subject: [PATCH 21/46] feat: Added new resource module Managed DevOps Pools - `avm/res/dev-ops-infrastructure/pool` (#2894) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR introduces the new Managed DevOps Pools as requested in https://github.com/Azure/Azure-Verified-Modules/issues/1217. Adding @AlexanderSehr and @surajguptha for code review ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.dev-ops-infrastructure.pool](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.dev-ops-infrastructure.pool.yml/badge.svg?branch=johnlokerse%2Fimplement-mdp-module&event=workflow_dispatch)](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.dev-ops-infrastructure.pool.yml) | ## Type of Change - [x] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Máté Barabás --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/avm_module_issue.yml | 1 + .../avm.res.dev-ops-infrastructure.pool.yml | 88 ++ avm/res/dev-ops-infrastructure/pool/README.md | 1320 +++++++++++++++++ .../dev-ops-infrastructure/pool/main.bicep | 489 ++++++ avm/res/dev-ops-infrastructure/pool/main.json | 1013 +++++++++++++ .../tests/e2e/defaults/dependencies.bicep | 27 + .../pool/tests/e2e/defaults/main.test.bicep | 78 + .../pool/tests/e2e/max/dependencies.bicep | 89 ++ .../pool/tests/e2e/max/main.test.bicep | 171 +++ .../tests/e2e/waf-aligned/dependencies.bicep | 89 ++ .../tests/e2e/waf-aligned/main.test.bicep | 127 ++ .../dev-ops-infrastructure/pool/version.json | 7 + 13 files changed, 3500 insertions(+) create mode 100644 .github/workflows/avm.res.dev-ops-infrastructure.pool.yml create mode 100644 avm/res/dev-ops-infrastructure/pool/README.md create mode 100644 avm/res/dev-ops-infrastructure/pool/main.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/main.json create mode 100644 avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/dev-ops-infrastructure/pool/version.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f67f52ec145..fff88d76bc1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -62,6 +62,7 @@ /avm/res/desktop-virtualization/host-pool/ @Azure/avm-res-desktopvirtualization-hostpool-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/desktop-virtualization/scaling-plan/ @Azure/avm-res-desktopvirtualization-scalingplan-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/desktop-virtualization/workspace/ @Azure/avm-res-desktopvirtualization-workspace-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/res/dev-ops-infrastructure/pool/ @Azure/avm-res-devopsinfrastructure-pool-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/dev-test-lab/lab/ @Azure/avm-res-devtestlab-lab-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/digital-twins/digital-twins-instance/ @Azure/avm-res-digitaltwins-digitaltwinsinstance-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/document-db/database-account/ @Azure/avm-res-documentdb-databaseaccount-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index d30ee744be1..feded8d6000 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -97,6 +97,7 @@ body: - "avm/res/desktop-virtualization/host-pool" - "avm/res/desktop-virtualization/scaling-plan" - "avm/res/desktop-virtualization/workspace" + - "avm/res/dev-ops-infrastructure/pool" - "avm/res/dev-test-lab/lab" - "avm/res/digital-twins/digital-twins-instance" - "avm/res/document-db/database-account" diff --git a/.github/workflows/avm.res.dev-ops-infrastructure.pool.yml b/.github/workflows/avm.res.dev-ops-infrastructure.pool.yml new file mode 100644 index 00000000000..dac40abeb8e --- /dev/null +++ b/.github/workflows/avm.res.dev-ops-infrastructure.pool.yml @@ -0,0 +1,88 @@ +name: "avm.res.dev-ops-infrastructure.pool" + +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" + - ".github/workflows/avm.res.dev-ops-infrastructure.pool.yml" + - "avm/res/dev-ops-infrastructure/pool/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/dev-ops-infrastructure/pool" + workflowPath: ".github/workflows/avm.res.dev-ops-infrastructure.pool.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 diff --git a/avm/res/dev-ops-infrastructure/pool/README.md b/avm/res/dev-ops-infrastructure/pool/README.md new file mode 100644 index 00000000000..4cd888f4b44 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/README.md @@ -0,0 +1,1320 @@ +# Managed DevOps Pool `[Microsoft.DevOpsInfrastructure/pools]` + +This module deploys the Managed DevOps Pool resource. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.DevOpsInfrastructure/pools` | [2024-04-04-preview](https://learn.microsoft.com/en-us/azure/templates) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | + +## Notes + +The Managed DevOps Pool resource requires external permissions in Azure DevOps. Make sure that the deployment principal has permission in Azure DevOps: [Managed DevOps Pools - Verify Azure DevOps Permissions](https://learn.microsoft.com/en-us/azure/devops/managed-devops-pools/prerequisites?view=azure-devops&tabs=azure-portal#verify-azure-devops-permissions) + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/res/dev-ops-infrastructure/pool:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [WAF-aligned](#example-3-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = { + name: 'poolDeployment' + params: { + // Required parameters + agentProfile: { + kind: 'Stateless' + } + concurrency: 1 + devCenterProjectResourceId: '' + fabricProfileSkuName: 'Standard_DS2_v2' + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + name: 'mdpmin001' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: '' + } + ] + } + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "agentProfile": { + "value": { + "kind": "Stateless" + } + }, + "concurrency": { + "value": 1 + }, + "devCenterProjectResourceId": { + "value": "" + }, + "fabricProfileSkuName": { + "value": "Standard_DS2_v2" + }, + "images": { + "value": [ + { + "wellKnownImageName": "windows-2022/latest" + } + ] + }, + "name": { + "value": "mdpmin001" + }, + "organizationProfile": { + "value": { + "kind": "AzureDevOps", + "organizations": [ + { + "url": "" + } + ] + } + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using large parameter set_ + +This instance deploys the module with most of its features enabled. + + +

+ +via Bicep module + +```bicep +module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = { + name: 'poolDeployment' + params: { + // Required parameters + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + daysData: [ + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + {} + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + ] + timeZone: 'Central Europe Standard Time' + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: '' + fabricProfileSkuName: 'Standard_D2_v2' + images: [ + { + aliases: [ + 'windows-2022' + ] + buffer: '*' + wellKnownImageName: 'windows-2022/latest' + } + ] + name: 'mdpmax001' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + parallelism: 1 + projects: [ + '' + ] + url: '' + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + // Non-required parameters + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + ] + storageProfile: { + dataDisks: [ + { + caching: 'ReadWrite' + diskSizeGiB: 100 + driveLetter: 'B' + storageAccountType: 'Standard_LRS' + } + ] + osDiskStorageAccountType: 'Standard' + } + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "agentProfile": { + "value": { + "kind": "Stateless", + "resourcePredictions": { + "daysData": [ + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {}, + {}, + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {} + ], + "timeZone": "Central Europe Standard Time" + }, + "resourcePredictionsProfile": { + "kind": "Automatic", + "predictionPreference": "Balanced" + } + } + }, + "concurrency": { + "value": 1 + }, + "devCenterProjectResourceId": { + "value": "" + }, + "fabricProfileSkuName": { + "value": "Standard_D2_v2" + }, + "images": { + "value": [ + { + "aliases": [ + "windows-2022" + ], + "buffer": "*", + "wellKnownImageName": "windows-2022/latest" + } + ] + }, + "name": { + "value": "mdpmax001" + }, + "organizationProfile": { + "value": { + "kind": "AzureDevOps", + "organizations": [ + { + "parallelism": 1, + "projects": [ + "" + ], + "url": "" + } + ], + "permissionProfile": { + "kind": "CreatorOnly" + } + } + }, + // Non-required parameters + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + } + ] + }, + "storageProfile": { + "value": { + "dataDisks": [ + { + "caching": "ReadWrite", + "diskSizeGiB": 100, + "driveLetter": "B", + "storageAccountType": "Standard_LRS" + } + ], + "osDiskStorageAccountType": "Standard" + } + }, + "subnetResourceId": { + "value": "" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ +### Example 3: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = { + name: 'poolDeployment' + params: { + // Required parameters + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + daysData: [ + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + {} + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + ] + timeZone: 'Central Europe Standard Time' + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: '' + fabricProfileSkuName: 'Standard_D2_v2' + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + name: 'mdpwaf001' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + parallelism: 1 + projects: [ + '' + ] + url: '' + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + // Non-required parameters + location: '' + subnetResourceId: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "agentProfile": { + "value": { + "kind": "Stateless", + "resourcePredictions": { + "daysData": [ + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {}, + {}, + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {} + ], + "timeZone": "Central Europe Standard Time" + }, + "resourcePredictionsProfile": { + "kind": "Automatic", + "predictionPreference": "Balanced" + } + } + }, + "concurrency": { + "value": 1 + }, + "devCenterProjectResourceId": { + "value": "" + }, + "fabricProfileSkuName": { + "value": "Standard_D2_v2" + }, + "images": { + "value": [ + { + "wellKnownImageName": "windows-2022/latest" + } + ] + }, + "name": { + "value": "mdpwaf001" + }, + "organizationProfile": { + "value": { + "kind": "AzureDevOps", + "organizations": [ + { + "parallelism": 1, + "projects": [ + "" + ], + "url": "" + } + ], + "permissionProfile": { + "kind": "CreatorOnly" + } + } + }, + // Non-required parameters + "location": { + "value": "" + }, + "subnetResourceId": { + "value": "" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`agentProfile`](#parameter-agentprofile) | object | Defines how the machine will be handled once it executed a job. | +| [`concurrency`](#parameter-concurrency) | int | Defines how many resources can there be created at any given time. | +| [`devCenterProjectResourceId`](#parameter-devcenterprojectresourceid) | string | The resource id of the DevCenter Project the pool belongs to. | +| [`fabricProfileSkuName`](#parameter-fabricprofileskuname) | string | The Azure SKU name of the machines in the pool. | +| [`images`](#parameter-images) | array | The VM images of the machines in the pool. | +| [`name`](#parameter-name) | string | Name of the pool. It needs to be globally unique. | +| [`organizationProfile`](#parameter-organizationprofile) | object | Defines the organization in which the pool will be used. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | The geo-location where the resource lives. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`managedIdentities`](#parameter-managedidentities) | object | The managed service identities assigned to this resource. | +| [`osProfile`](#parameter-osprofile) | object | The OS profile of the agents in the pool. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`storageProfile`](#parameter-storageprofile) | object | The storage profile of the machines in the pool. | +| [`subnetResourceId`](#parameter-subnetresourceid) | string | The subnet id on which to put all machines created in the pool. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | + +### Parameter: `agentProfile` + +Defines how the machine will be handled once it executed a job. + +- Required: Yes +- Type: object + +### Parameter: `concurrency` + +Defines how many resources can there be created at any given time. + +- Required: Yes +- Type: int + +### Parameter: `devCenterProjectResourceId` + +The resource id of the DevCenter Project the pool belongs to. + +- Required: Yes +- Type: string + +### Parameter: `fabricProfileSkuName` + +The Azure SKU name of the machines in the pool. + +- Required: Yes +- Type: string + +### Parameter: `images` + +The VM images of the machines in the pool. + +- Required: Yes +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`wellKnownImageName`](#parameter-imageswellknownimagename) | string | The image to use from a well-known set of images made available to customers. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`aliases`](#parameter-imagesaliases) | array | List of aliases to reference the image by. | +| [`buffer`](#parameter-imagesbuffer) | string | The percentage of the buffer to be allocated to this image. | +| [`resourceId`](#parameter-imagesresourceid) | string | The resource id of the image. | + +### Parameter: `images.wellKnownImageName` + +The image to use from a well-known set of images made available to customers. + +- Required: Yes +- Type: string + +### Parameter: `images.aliases` + +List of aliases to reference the image by. + +- Required: No +- Type: array + +### Parameter: `images.buffer` + +The percentage of the buffer to be allocated to this image. + +- Required: No +- Type: string + +### Parameter: `images.resourceId` + +The resource id of the image. + +- Required: No +- Type: string + +### Parameter: `name` + +Name of the pool. It needs to be globally unique. + +- Required: Yes +- Type: string + +### Parameter: `organizationProfile` + +Defines the organization in which the pool will be used. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-organizationprofilekind) | string | Azure DevOps organization profile. | +| [`organizations`](#parameter-organizationprofileorganizations) | array | The list of Azure DevOps organizations the pool should be present in.. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`permissionProfile`](#parameter-organizationprofilepermissionprofile) | object | The type of permission which determines which accounts are admins on the Azure DevOps pool. | + +### Parameter: `organizationProfile.kind` + +Azure DevOps organization profile. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDevOps' + ] + ``` + +### Parameter: `organizationProfile.organizations` + +The list of Azure DevOps organizations the pool should be present in.. + +- Required: Yes +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`url`](#parameter-organizationprofileorganizationsurl) | string | The Azure DevOps organization URL in which the pool should be created. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`parallelism`](#parameter-organizationprofileorganizationsparallelism) | int | How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool. | +| [`projects`](#parameter-organizationprofileorganizationsprojects) | array | List of projects in which the pool should be created. | + +### Parameter: `organizationProfile.organizations.url` + +The Azure DevOps organization URL in which the pool should be created. + +- Required: Yes +- Type: string + +### Parameter: `organizationProfile.organizations.parallelism` + +How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool. + +- Required: No +- Type: int + +### Parameter: `organizationProfile.organizations.projects` + +List of projects in which the pool should be created. + +- Required: No +- Type: array + +### Parameter: `organizationProfile.permissionProfile` + +The type of permission which determines which accounts are admins on the Azure DevOps pool. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`groups`](#parameter-organizationprofilepermissionprofilegroups) | array | Group email addresses. | +| [`kind`](#parameter-organizationprofilepermissionprofilekind) | string | Determines who has admin permissions to the Azure DevOps pool. | +| [`users`](#parameter-organizationprofilepermissionprofileusers) | array | User email addresses. | + +### Parameter: `organizationProfile.permissionProfile.groups` + +Group email addresses. + +- Required: No +- Type: array + +### Parameter: `organizationProfile.permissionProfile.kind` + +Determines who has admin permissions to the Azure DevOps pool. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CreatorOnly' + 'Inherit' + 'SpecificAccounts' + ] + ``` + +### Parameter: `organizationProfile.permissionProfile.users` + +User email addresses. + +- Required: No +- Type: array + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | string | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| [`eventHubName`](#parameter-diagnosticsettingseventhubname) | string | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | string | A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. | +| [`logCategoriesAndGroups`](#parameter-diagnosticsettingslogcategoriesandgroups) | array | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. | +| [`marketplacePartnerResourceId`](#parameter-diagnosticsettingsmarketplacepartnerresourceid) | string | The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`metricCategories`](#parameter-diagnosticsettingsmetriccategories) | array | The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. | +| [`name`](#parameter-diagnosticsettingsname) | string | The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | string | Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | string | Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | string | Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. | +| [`categoryGroup`](#parameter-diagnosticsettingslogcategoriesandgroupscategorygroup) | string | Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. | +| [`enabled`](#parameter-diagnosticsettingslogcategoriesandgroupsenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | string | Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-diagnosticsettingsmetriccategoriesenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `location` + +The geo-location where the resource lives. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `lock` + +The lock settings of the service. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-lockkind) | string | Specify the type of lock. | +| [`name`](#parameter-lockname) | string | Specify the name of lock. | + +### Parameter: `lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `managedIdentities` + +The managed service identities assigned to this resource. + +- Required: No +- Type: object +- Example: + ```Bicep + { + systemAssigned: true, + userAssignedResourceIds: [ + '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity' + ] + } + { + systemAssigned: true + } + ``` + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`systemAssigned`](#parameter-managedidentitiessystemassigned) | bool | Enables system assigned managed identity on the resource. | +| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | array | The resource ID(s) to assign to the resource. | + +### Parameter: `managedIdentities.systemAssigned` + +Enables system assigned managed identity on the resource. + +- Required: No +- Type: bool + +### Parameter: `managedIdentities.userAssignedResourceIds` + +The resource ID(s) to assign to the resource. + +- Required: No +- Type: array + +### Parameter: `osProfile` + +The OS profile of the agents in the pool. + +- Required: No +- Type: object +- Default: + ```Bicep + { + logonType: 'Interactive' + secretsManagementSettings: { + keyExportable: false + observedCertificates: [] + } + } + ``` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`logonType`](#parameter-osprofilelogontype) | string | The logon type of the machine. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`secretsManagementSettings`](#parameter-osprofilesecretsmanagementsettings) | object | The secret management settings of the machines in the pool. | + +### Parameter: `osProfile.logonType` + +The logon type of the machine. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Interactive' + 'Service' + ] + ``` + +### Parameter: `osProfile.secretsManagementSettings` + +The secret management settings of the machines in the pool. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`keyExportable`](#parameter-osprofilesecretsmanagementsettingskeyexportable) | bool | The secret management settings of the machines in the pool. | +| [`observedCertificates`](#parameter-osprofilesecretsmanagementsettingsobservedcertificates) | array | The list of certificates to install on all machines in the pool. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`certificateStoreLocation`](#parameter-osprofilesecretsmanagementsettingscertificatestorelocation) | string | Where to store certificates on the machine. | + +### Parameter: `osProfile.secretsManagementSettings.keyExportable` + +The secret management settings of the machines in the pool. + +- Required: Yes +- Type: bool + +### Parameter: `osProfile.secretsManagementSettings.observedCertificates` + +The list of certificates to install on all machines in the pool. + +- Required: Yes +- Type: array + +### Parameter: `osProfile.secretsManagementSettings.certificateStoreLocation` + +Where to store certificates on the machine. + +- Required: No +- Type: string + +### Parameter: `roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-roleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | string | The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-roleassignmentscondition) | string | The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". | +| [`conditionVersion`](#parameter-roleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | string | The description of the role assignment. | +| [`name`](#parameter-roleassignmentsname) | string | The name (as GUID) of the role assignment. If not provided, a GUID will be generated. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.condition` + +The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.name` + +The name (as GUID) of the role assignment. If not provided, a GUID will be generated. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `storageProfile` + +The storage profile of the machines in the pool. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`dataDisks`](#parameter-storageprofiledatadisks) | array | A list of empty data disks to attach. | +| [`osDiskStorageAccountType`](#parameter-storageprofileosdiskstorageaccounttype) | string | The Azure SKU name of the machines in the pool. | + +### Parameter: `storageProfile.dataDisks` + +A list of empty data disks to attach. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`caching`](#parameter-storageprofiledatadiskscaching) | string | The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/. | +| [`diskSizeGiB`](#parameter-storageprofiledatadisksdisksizegib) | int | The initial disk size in gigabytes. | +| [`driveLetter`](#parameter-storageprofiledatadisksdriveletter) | string | The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed. | +| [`storageAccountType`](#parameter-storageprofiledatadisksstorageaccounttype) | string | The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS. | + +### Parameter: `storageProfile.dataDisks.caching` + +The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'None' + 'ReadOnly' + 'ReadWrite' + ] + ``` + +### Parameter: `storageProfile.dataDisks.diskSizeGiB` + +The initial disk size in gigabytes. + +- Required: No +- Type: int + +### Parameter: `storageProfile.dataDisks.driveLetter` + +The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed. + +- Required: No +- Type: string + +### Parameter: `storageProfile.dataDisks.storageAccountType` + +The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Premium_LRS' + 'Premium_ZRS' + 'Standard_LRS' + 'StandardSSD_LRS' + 'StandardSSD_ZRS' + ] + ``` + +### Parameter: `storageProfile.osDiskStorageAccountType` + +The Azure SKU name of the machines in the pool. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Premium' + 'Standard' + 'StandardSSD' + ] + ``` + +### Parameter: `subnetResourceId` + +The subnet id on which to put all machines created in the pool. + +- Required: No +- Type: string + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the Managed DevOps Pool resource was deployed into. | +| `name` | string | The name of the Managed DevOps Pool. | +| `resourceGroupName` | string | The name of the resource group the Managed DevOps Pool resource was deployed into. | +| `resourceId` | string | The resource ID of the Managed DevOps Pool. | +| `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/dev-ops-infrastructure/pool/main.bicep b/avm/res/dev-ops-infrastructure/pool/main.bicep new file mode 100644 index 00000000000..c64d2bbce53 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/main.bicep @@ -0,0 +1,489 @@ +metadata name = 'Managed DevOps Pool' +metadata description = 'This module deploys the Managed DevOps Pool resource.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the pool. It needs to be globally unique.') +param name string + +@description('Required. The Azure SKU name of the machines in the pool.') +param fabricProfileSkuName string + +@minValue(1) +@maxValue(10000) +@description('Required. Defines how many resources can there be created at any given time.') +param concurrency int + +@description('Required. The VM images of the machines in the pool.') +param images imageType + +@description('Optional. The geo-location where the resource lives.') +param location string = resourceGroup().location + +@description('Required. The resource id of the DevCenter Project the pool belongs to.') +param devCenterProjectResourceId string + +@description('Optional. The subnet id on which to put all machines created in the pool.') +param subnetResourceId string? + +@description('Required. Defines how the machine will be handled once it executed a job.') +param agentProfile agentProfileType + +@description('Optional. The OS profile of the agents in the pool.') +param osProfile osProfileType = { + logonType: 'Interactive' + secretsManagementSettings: { + keyExportable: false + observedCertificates: [] + } +} + +@description('Optional. The storage profile of the machines in the pool.') +param storageProfile storageProfileType + +@description('Required. Defines the organization in which the pool will be used.') +param organizationProfile organizationProfileType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. The managed service identities assigned to this resource.') +@metadata({ + example: ''' + { + systemAssigned: true, + userAssignedResourceIds: [ + '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity' + ] + } + { + systemAssigned: true + } + ''' +}) +param managedIdentities managedIdentitiesType + +var builtInRoleNames = { + 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)) + }) +] + +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' : 'None') + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null + } + : null + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.res.devopsinfrastructure-pool.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + 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' + } + } + } + } +} + +resource managedDevOpsPool 'Microsoft.DevOpsInfrastructure/pools@2024-04-04-preview' = { + name: name + location: location + tags: tags + identity: identity + properties: { + agentProfile: agentProfile + devCenterProjectResourceId: devCenterProjectResourceId + fabricProfile: { + sku: { + name: fabricProfileSkuName + } + networkProfile: !empty(subnetResourceId) + ? { + subnetId: subnetResourceId! + } + : null + osProfile: osProfile + storageProfile: storageProfile + kind: 'Vmss' + images: images + } + maximumConcurrency: concurrency + organizationProfile: organizationProfile + } +} + +resource managedDevOpsPool_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' + } + scope: managedDevOpsPool +} + +resource managedDevOpsPool_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (formattedRoleAssignments ?? []): { + name: roleAssignment.?name ?? guid( + managedDevOpsPool.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: managedDevOpsPool + } +] + +resource managedDevOpsPool_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: managedDevOpsPool + } +] + +@description('The name of the Managed DevOps Pool.') +output name string = managedDevOpsPool.name + +@description('The resource ID of the Managed DevOps Pool.') +output resourceId string = managedDevOpsPool.id + +@description('The name of the resource group the Managed DevOps Pool resource was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the Managed DevOps Pool resource was deployed into.') +output location string = managedDevOpsPool.location + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string? = managedDevOpsPool.?identity.?principalId + +type osProfileType = { + @description('Required. The logon type of the machine.') + logonType: ('Interactive' | 'Service') + + @description('Optional. The secret management settings of the machines in the pool.') + secretsManagementSettings: { + @description('Required. The secret management settings of the machines in the pool.') + keyExportable: bool + + @description('Required. The list of certificates to install on all machines in the pool.') + observedCertificates: string[] + + @description('Optional. Where to store certificates on the machine.') + certificateStoreLocation: string? + }? +} + +type storageProfileType = { + @description('Optional. The Azure SKU name of the machines in the pool.') + osDiskStorageAccountType: ('Premium' | 'StandardSSD' | 'Standard')? + + @description('Optional. A list of empty data disks to attach.') + dataDisks: { + @description('Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/.') + caching: ('None' | 'ReadOnly' | 'ReadWrite')? + + @description('Optional. The initial disk size in gigabytes.') + diskSizeGiB: int? + + @description('Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed.') + driveLetter: string? + + @description('Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS.') + storageAccountType: ('Premium_LRS' | 'Premium_ZRS' | 'StandardSSD_LRS' | 'StandardSSD_ZRS' | 'Standard_LRS')? + }[]? +}? + +type imageType = { + @description('Optional. List of aliases to reference the image by.') + aliases: string[]? + + @description('Optional. The percentage of the buffer to be allocated to this image.') + buffer: string? + + @description('Required. The image to use from a well-known set of images made available to customers.') + wellKnownImageName: string + + @description('Optional. The resource id of the image.') + resourceId: string? +}[] + +type organizationProfileType = { + @description('Required. Azure DevOps organization profile.') + kind: 'AzureDevOps' + + @description('Optional. The type of permission which determines which accounts are admins on the Azure DevOps pool.') + permissionProfile: { + @description('Optional. Determines who has admin permissions to the Azure DevOps pool.') + kind: ('CreatorOnly' | 'Inherit' | 'SpecificAccounts')? + + @description('Optional. Group email addresses.') + groups: string[]? + + @description('Optional. User email addresses.') + users: string[]? + }? + + @description('Required. The list of Azure DevOps organizations the pool should be present in..') + organizations: { + @description('Required. The Azure DevOps organization URL in which the pool should be created.') + url: string + + @description('Optional. List of projects in which the pool should be created.') + projects: string[]? + + @description('Optional. How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool.') + @minValue(1) + @maxValue(10000) + parallelism: int? + }[] +} + +type dataDiskType = { + @description('Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/.') + caching: ('None' | 'ReadOnly' | 'ReadWrite')? + + @description('Optional. The initial disk size in gigabytes.') + diskSizeGiB: int? + + @description('Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed.') + driveLetter: string? + + @description('Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS.') + storageAccountType: ('Premium_LRS' | 'Premium_ZRS' | 'StandardSSD_LRS' | 'StandardSSD_ZRS' | 'Standard_LRS')? +}[]? + +type resourcePredictionsProfileAutomaticType = { + @description('Required. The stand-by agent scheme is determined based on historical demand.') + kind: 'Automatic' + + @description('Required. Determines the balance between cost and performance.') + predictionPreference: 'Balanced' | 'MostCostEffective' | 'MoreCostEffective' | 'MorePerformance' | 'BestPerformance' +} + +type resourcePredictionsProfileManualType = { + @description('Required. Customer provides the stand-by agent scheme.') + kind: 'Manual' +} + +type agentStatefulType = { + @description('Required. Stateful profile meaning that the machines will be returned to the pool after running a job.') + kind: 'Stateful' + + @description('Required. How long should stateful machines be kept around. The maximum is one week.') + maxAgentLifetime: string + + @description('Required. How long should the machine be kept around after it ran a workload when there are no stand-by agents. The maximum is one week.') + gracePeriodTimeSpan: string + + @description('Optional. Defines pool buffer/stand-by agents.') + resourcePredictions: object? + + @discriminator('kind') + @description('Optional. Determines how the stand-by scheme should be provided.') + resourcePredictionsProfile: (resourcePredictionsProfileAutomaticType | resourcePredictionsProfileManualType)? +} + +type agentStatelessType = { + @description('Required. Stateless profile meaning that the machines will be cleaned up after running a job.') + kind: 'Stateless' + + @description('Optional. Defines pool buffer/stand-by agents.') + resourcePredictions: { + @description('Required. The time zone in which the daysData is provided. To see the list of available time zones, see: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones or via PowerShell command `(Get-TimeZone -ListAvailable).StandardName`.') + timeZone: string + + @description('Optional. The number of agents needed at a specific time.') + @metadata({ + example: ''' + [ + { // Monday + '09:00': 5 + '22:00': 0 + } + {} // Tuesday + {} // Wednesday + {} // Thursday + { // Friday + '09:00': 5 + '22:00': 0 + } + {} // Saturday + {} // Sunday + ] + ''' + }) + daysData: object[]? + }? + + @discriminator('kind') + @description('Optional. Determines how the stand-by scheme should be provided.') + resourcePredictionsProfile: (resourcePredictionsProfileAutomaticType | resourcePredictionsProfileManualType)? +} + +@discriminator('kind') +type agentProfileType = agentStatefulType | agentStatelessType + +type roleAssignmentType = { + @description('Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated.') + name: string? + + @description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs.') + categoryGroup: string? + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics.') + category: string + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') + eventHubAuthorizationRuleResourceId: string? + + @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? + +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource.') + userAssignedResourceIds: string[]? +}? diff --git a/avm/res/dev-ops-infrastructure/pool/main.json b/avm/res/dev-ops-infrastructure/pool/main.json new file mode 100644 index 00000000000..38edaf771a1 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/main.json @@ -0,0 +1,1013 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "9138966756251042726" + }, + "name": "Managed DevOps Pool", + "description": "This module deploys the Managed DevOps Pool resource.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "osProfileType": { + "type": "object", + "properties": { + "logonType": { + "type": "string", + "allowedValues": [ + "Interactive", + "Service" + ], + "metadata": { + "description": "Required. The logon type of the machine." + } + }, + "secretsManagementSettings": { + "type": "object", + "properties": { + "keyExportable": { + "type": "bool", + "metadata": { + "description": "Required. The secret management settings of the machines in the pool." + } + }, + "observedCertificates": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of certificates to install on all machines in the pool." + } + }, + "certificateStoreLocation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Where to store certificates on the machine." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The secret management settings of the machines in the pool." + } + } + } + }, + "storageProfileType": { + "type": "object", + "properties": { + "osDiskStorageAccountType": { + "type": "string", + "allowedValues": [ + "Premium", + "Standard", + "StandardSSD" + ], + "nullable": true, + "metadata": { + "description": "Optional. The Azure SKU name of the machines in the pool." + } + }, + "dataDisks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/." + } + }, + "diskSizeGiB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The initial disk size in gigabytes." + } + }, + "driveLetter": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed." + } + }, + "storageAccountType": { + "type": "string", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of empty data disks to attach." + } + } + }, + "nullable": true + }, + "imageType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of aliases to reference the image by." + } + }, + "buffer": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The percentage of the buffer to be allocated to this image." + } + }, + "wellKnownImageName": { + "type": "string", + "metadata": { + "description": "Required. The image to use from a well-known set of images made available to customers." + } + }, + "resourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the image." + } + } + } + } + }, + "organizationProfileType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "AzureDevOps" + ], + "metadata": { + "description": "Required. Azure DevOps organization profile." + } + }, + "permissionProfile": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "CreatorOnly", + "Inherit", + "SpecificAccounts" + ], + "nullable": true, + "metadata": { + "description": "Optional. Determines who has admin permissions to the Azure DevOps pool." + } + }, + "groups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Group email addresses." + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. User email addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The type of permission which determines which accounts are admins on the Azure DevOps pool." + } + }, + "organizations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "metadata": { + "description": "Required. The Azure DevOps organization URL in which the pool should be created." + } + }, + "projects": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of projects in which the pool should be created." + } + }, + "parallelism": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 10000, + "metadata": { + "description": "Optional. How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool." + } + } + } + }, + "metadata": { + "description": "Required. The list of Azure DevOps organizations the pool should be present in.." + } + } + } + }, + "dataDiskType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/." + } + }, + "diskSizeGiB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The initial disk size in gigabytes." + } + }, + "driveLetter": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed." + } + }, + "storageAccountType": { + "type": "string", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS." + } + } + } + }, + "nullable": true + }, + "resourcePredictionsProfileAutomaticType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Automatic" + ], + "metadata": { + "description": "Required. The stand-by agent scheme is determined based on historical demand." + } + }, + "predictionPreference": { + "type": "string", + "allowedValues": [ + "Balanced", + "BestPerformance", + "MoreCostEffective", + "MorePerformance", + "MostCostEffective" + ], + "metadata": { + "description": "Required. Determines the balance between cost and performance." + } + } + } + }, + "resourcePredictionsProfileManualType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Manual" + ], + "metadata": { + "description": "Required. Customer provides the stand-by agent scheme." + } + } + } + }, + "agentStatefulType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Stateful" + ], + "metadata": { + "description": "Required. Stateful profile meaning that the machines will be returned to the pool after running a job." + } + }, + "maxAgentLifetime": { + "type": "string", + "metadata": { + "description": "Required. How long should stateful machines be kept around. The maximum is one week." + } + }, + "gracePeriodTimeSpan": { + "type": "string", + "metadata": { + "description": "Required. How long should the machine be kept around after it ran a workload when there are no stand-by agents. The maximum is one week." + } + }, + "resourcePredictions": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Defines pool buffer/stand-by agents." + } + }, + "resourcePredictionsProfile": { + "type": "object", + "discriminator": { + "propertyName": "kind", + "mapping": { + "Automatic": { + "$ref": "#/definitions/resourcePredictionsProfileAutomaticType" + }, + "Manual": { + "$ref": "#/definitions/resourcePredictionsProfileManualType" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Determines how the stand-by scheme should be provided." + } + } + } + }, + "agentStatelessType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Stateless" + ], + "metadata": { + "description": "Required. Stateless profile meaning that the machines will be cleaned up after running a job." + } + }, + "resourcePredictions": { + "type": "object", + "properties": { + "timeZone": { + "type": "string", + "metadata": { + "description": "Required. The time zone in which the daysData is provided. To see the list of available time zones, see: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones or via PowerShell command `(Get-TimeZone -ListAvailable).StandardName`." + } + }, + "daysData": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "example": " [\n { // Monday\n '09:00': 5\n '22:00': 0\n }\n {} // Tuesday\n {} // Wednesday\n {} // Thursday\n { // Friday\n '09:00': 5\n '22:00': 0\n }\n {} // Saturday\n {} // Sunday\n ]\n ", + "description": "Optional. The number of agents needed at a specific time." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Defines pool buffer/stand-by agents." + } + }, + "resourcePredictionsProfile": { + "type": "object", + "discriminator": { + "propertyName": "kind", + "mapping": { + "Automatic": { + "$ref": "#/definitions/resourcePredictionsProfileAutomaticType" + }, + "Manual": { + "$ref": "#/definitions/resourcePredictionsProfileManualType" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Determines how the stand-by scheme should be provided." + } + } + } + }, + "agentProfileType": { + "type": "object", + "discriminator": { + "propertyName": "kind", + "mapping": { + "Stateful": { + "$ref": "#/definitions/agentStatefulType" + }, + "Stateless": { + "$ref": "#/definitions/agentStatelessType" + } + } + } + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the pool. It needs to be globally unique." + } + }, + "fabricProfileSkuName": { + "type": "string", + "metadata": { + "description": "Required. The Azure SKU name of the machines in the pool." + } + }, + "concurrency": { + "type": "int", + "minValue": 1, + "maxValue": 10000, + "metadata": { + "description": "Required. Defines how many resources can there be created at any given time." + } + }, + "images": { + "$ref": "#/definitions/imageType", + "metadata": { + "description": "Required. The VM images of the machines in the pool." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The geo-location where the resource lives." + } + }, + "devCenterProjectResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the DevCenter Project the pool belongs to." + } + }, + "subnetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subnet id on which to put all machines created in the pool." + } + }, + "agentProfile": { + "$ref": "#/definitions/agentProfileType", + "metadata": { + "description": "Required. Defines how the machine will be handled once it executed a job." + } + }, + "osProfile": { + "$ref": "#/definitions/osProfileType", + "defaultValue": { + "logonType": "Interactive", + "secretsManagementSettings": { + "keyExportable": false, + "observedCertificates": [] + } + }, + "metadata": { + "description": "Optional. The OS profile of the agents in the pool." + } + }, + "storageProfile": { + "$ref": "#/definitions/storageProfileType", + "metadata": { + "description": "Optional. The storage profile of the machines in the pool." + } + }, + "organizationProfile": { + "$ref": "#/definitions/organizationProfileType", + "metadata": { + "description": "Required. Defines the organization in which the pool will be used." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "example": " {\n systemAssigned: true,\n userAssignedResourceIds: [\n '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity'\n ]\n }\n {\n systemAssigned: true\n }\n ", + "description": "Optional. The managed service identities assigned to this resource." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "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')]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.devopsinfrastructure-pool.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "managedDevOpsPool": { + "type": "Microsoft.DevOpsInfrastructure/pools", + "apiVersion": "2024-04-04-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "agentProfile": "[parameters('agentProfile')]", + "devCenterProjectResourceId": "[parameters('devCenterProjectResourceId')]", + "fabricProfile": { + "sku": { + "name": "[parameters('fabricProfileSkuName')]" + }, + "networkProfile": "[if(not(empty(parameters('subnetResourceId'))), createObject('subnetId', parameters('subnetResourceId')), null())]", + "osProfile": "[parameters('osProfile')]", + "storageProfile": "[parameters('storageProfile')]", + "kind": "Vmss", + "images": "[parameters('images')]" + }, + "maximumConcurrency": "[parameters('concurrency')]", + "organizationProfile": "[parameters('organizationProfile')]" + } + }, + "managedDevOpsPool_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.DevOpsInfrastructure/pools/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "managedDevOpsPool" + ] + }, + "managedDevOpsPool_roleAssignments": { + "copy": { + "name": "managedDevOpsPool_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DevOpsInfrastructure/pools/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.DevOpsInfrastructure/pools', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "managedDevOpsPool" + ] + }, + "managedDevOpsPool_diagnosticSettings": { + "copy": { + "name": "managedDevOpsPool_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.DevOpsInfrastructure/pools/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "managedDevOpsPool" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Managed DevOps Pool." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Managed DevOps Pool." + }, + "value": "[resourceId('Microsoft.DevOpsInfrastructure/pools', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Managed DevOps Pool resource was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the Managed DevOps Pool resource was deployed into." + }, + "value": "[reference('managedDevOpsPool', '2024-04-04-preview', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('managedDevOpsPool', '2024-04-04-preview', 'full'), 'identity'), 'principalId')]" + } + } +} \ No newline at end of file diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 00000000000..367ae50af80 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,27 @@ +@description('Required. The name of the Dev Center.') +param devCenterName string + +@description('Required. The name of the Dev Center Project.') +param devCenterProjectName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource devCenter 'Microsoft.DevCenter/devcenters@2024-02-01' = { + name: devCenterName + location: location +} + +resource devCenterProject 'Microsoft.DevCenter/projects@2024-02-01' = { + name: devCenterProjectName + location: location + properties: { + devCenterId: devCenter.id + } +} + +@description('The resource ID of the created DevCenter.') +output devCenterResourceId string = devCenter.id + +@description('The resource ID of the created DevCenter Project.') +output devCenterProjectResourceId string = devCenterProject.id diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep new file mode 100644 index 00000000000..c21a95f5054 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,78 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'mdpmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@secure() +param azureDevOpsOrganizationName string = '' + +// ============ // +// Dependencies // +// ============ // +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' + devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' + } +} + +// ================= // +// General resources // +// ================= // +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // +@batchSize(1) +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 + agentProfile: { + kind: 'Stateless' + } + concurrency: 1 + devCenterProjectResourceId: nestedDependencies.outputs.devCenterProjectResourceId + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + fabricProfileSkuName: 'Standard_DS2_v2' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/${azureDevOpsOrganizationName}' + } + ] + } + } + } +] diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep new file mode 100644 index 00000000000..11333fc560e --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep @@ -0,0 +1,89 @@ +@description('Required. The name of the Dev Center.') +param devCenterName string + +@description('Required. The name of the Dev Center Project.') +param devCenterProjectName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the virtual network to create.') +param virtualNetworkName string + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal.') +param devOpsInfrastructureObjectID string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '192.168.1.0' + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource devCenter 'Microsoft.DevCenter/devcenters@2024-02-01' = { + name: devCenterName + location: location +} + +resource devCenterProject 'Microsoft.DevCenter/projects@2024-02-01' = { + name: devCenterProjectName + location: location + properties: { + devCenterId: devCenter.id + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + cidrSubnet(addressPrefix, 24, 0) + ] + } + subnets: [ + { + name: 'default' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + delegations: [ + { + name: 'Microsoft.DevOpsInfrastructure/pools' + properties: { + serviceName: 'Microsoft.DevOpsInfrastructure/pools' + } + } + ] + } + } + ] + } +} + +// Network Contributor role assignment +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(subscription().subscriptionId, 'DevOpsInfrastructure', 'Network Contributor', 'max') + properties: { + principalId: devOpsInfrastructureObjectID // DevOpsInfrastructure service principal + #disable-next-line use-resource-id-functions + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalType: 'ServicePrincipal' + } + scope: virtualNetwork +} + +@description('The resource ID of the created DevCenter.') +output devCenterResourceId string = devCenter.id + +@description('The resource ID of the created DevCenter Project.') +output devCenterProjectResourceId string = devCenterProject.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created subnet.') +output subnetResourceId string = first(virtualNetwork.properties.subnets)!.id diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep new file mode 100644 index 00000000000..7416f2934cf --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep @@ -0,0 +1,171 @@ +targetScope = 'subscription' + +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module with most of its features enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'mdpmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@secure() +param azureDevOpsOrganizationName string = '' + +@description('Required. Name of the Azure DevOps Max Project. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsProjectName\'.') +@secure() +param azureDevOpsProjectName string = '' + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-DevOpsInfrastructureObjectID\'.') +@secure() +param devOpsInfrastructureObjectID string = '' + +// ============ // +// Dependencies // +// ============ // +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' + devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + devOpsInfrastructureObjectID: devOpsInfrastructureObjectID + } +} + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +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 + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + timeZone: 'Central Europe Standard Time' + daysData: [ + // Monday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Tuesday + {} + // Wednesday + {} + // Thursday + {} + // Friday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Saturday + {} + // Sunday + {} + ] + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: nestedDependencies.outputs.devCenterProjectResourceId + images: [ + { + aliases: [ + 'windows-2022' + ] + buffer: '*' + wellKnownImageName: 'windows-2022/latest' + } + ] + fabricProfileSkuName: 'Standard_D2_v2' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/${azureDevOpsOrganizationName}' + parallelism: 1 + projects: [ + azureDevOpsProjectName + ] + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + storageProfile: { + osDiskStorageAccountType: 'Standard' + dataDisks: [ + { + caching: 'ReadWrite' + diskSizeGiB: 100 + driveLetter: 'B' + storageAccountType: 'Standard_LRS' + } + ] + } + subnetResourceId: nestedDependencies.outputs.subnetResourceId + roleAssignments: [ + { + roleDefinitionIdOrName: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'Owner' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +] diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 00000000000..0afb813bacf --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,89 @@ +@description('Required. The name of the Dev Center.') +param devCenterName string + +@description('Required. The name of the Dev Center Project.') +param devCenterProjectName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the virtual network to create.') +param virtualNetworkName string + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal.') +param devOpsInfrastructureObjectID string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '192.168.1.0' + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource devCenter 'Microsoft.DevCenter/devcenters@2024-02-01' = { + name: devCenterName + location: location +} + +resource devCenterProject 'Microsoft.DevCenter/projects@2024-02-01' = { + name: devCenterProjectName + location: location + properties: { + devCenterId: devCenter.id + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + cidrSubnet(addressPrefix, 24, 0) + ] + } + subnets: [ + { + name: 'default' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + delegations: [ + { + name: 'Microsoft.DevOpsInfrastructure/pools' + properties: { + serviceName: 'Microsoft.DevOpsInfrastructure/pools' + } + } + ] + } + } + ] + } +} + +// Network Contributor role assignment +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(subscription().subscriptionId, 'DevOpsInfrastructure', 'Network Contributor', 'waf') + properties: { + principalId: devOpsInfrastructureObjectID // DevOpsInfrastructure service principal + #disable-next-line use-resource-id-functions + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalType: 'ServicePrincipal' + } + scope: virtualNetwork +} + +@description('The resource ID of the created DevCenter.') +output devCenterResourceId string = devCenter.id + +@description('The resource ID of the created DevCenter Project.') +output devCenterProjectResourceId string = devCenterProject.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created subnet.') +output subnetResourceId string = first(virtualNetwork.properties.subnets)!.id diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 00000000000..fdf015444fc --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,127 @@ +targetScope = 'subscription' + +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'mdpwaf' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@secure() +param azureDevOpsOrganizationName string = '' + +@description('Required. Name of the Azure DevOps WAF Project. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsProjectName\'.') +@secure() +param azureDevOpsProjectName string = '' + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-DevOpsInfrastructureObjectID\'.') +@secure() +param devOpsInfrastructureObjectID string = '' + +// ============ // +// Dependencies // +// ============ // +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' + devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + devOpsInfrastructureObjectID: devOpsInfrastructureObjectID + } +} + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +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 + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + timeZone: 'Central Europe Standard Time' + daysData: [ + // Monday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Tuesday + {} + // Wednesday + {} + // Thursday + {} + // Friday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Saturday + {} + // Sunday + {} + ] + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: nestedDependencies.outputs.devCenterProjectResourceId + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + fabricProfileSkuName: 'Standard_D2_v2' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/${azureDevOpsOrganizationName}' + projects: [ + azureDevOpsProjectName + ] + parallelism: 1 + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + } + } +] diff --git a/avm/res/dev-ops-infrastructure/pool/version.json b/avm/res/dev-ops-infrastructure/pool/version.json new file mode 100644 index 00000000000..8def869edeb --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} From 7e27b6c397704335dd03db6a9542432de0e6ee44 Mon Sep 17 00:00:00 2001 From: hundredacres Date: Thu, 19 Sep 2024 02:53:02 -0700 Subject: [PATCH 22/46] fix: Remove ORPHANED status from module and update resource API version - `avm/res/network/front-door` (#3320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Remove ORPHANED file from module. Update resource API version. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.network.front-door](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml/badge.svg?branch=avm%2Fissues%2F1269)](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.res.network.front-door.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Máté Barabás Co-authored-by: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Co-authored-by: JFolberth --- avm/res/network/front-door/ORPHANED.md | 4 ---- avm/res/network/front-door/README.md | 7 +------ avm/res/network/front-door/main.bicep | 2 +- avm/res/network/front-door/main.json | 4 ++-- 4 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 avm/res/network/front-door/ORPHANED.md diff --git a/avm/res/network/front-door/ORPHANED.md b/avm/res/network/front-door/ORPHANED.md deleted file mode 100644 index ef8fa911d2b..00000000000 --- a/avm/res/network/front-door/ORPHANED.md +++ /dev/null @@ -1,4 +0,0 @@ -⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ - -- Only security and bug fixes are being handled by the AVM core team at present. -- If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! \ No newline at end of file diff --git a/avm/res/network/front-door/README.md b/avm/res/network/front-door/README.md index 50e01165f62..23561de6630 100644 --- a/avm/res/network/front-door/README.md +++ b/avm/res/network/front-door/README.md @@ -1,10 +1,5 @@ # Azure Front Doors `[Microsoft.Network/frontDoors]` -> ⚠️THIS MODULE IS CURRENTLY ORPHANED.⚠️ -> -> - Only security and bug fixes are being handled by the AVM core team at present. -> - If interested in becoming the module owner of this orphaned module (must be Microsoft FTE), please look for the related "orphaned module" GitHub issue [here](https://aka.ms/AVM/OrphanedModules)! - This module deploys an Azure Front Door. ## Navigation @@ -22,7 +17,7 @@ This module deploys an Azure Front Door. | `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.Network/frontDoors` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2020-05-01/frontDoors) | +| `Microsoft.Network/frontDoors` | [2021-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2021-06-01/frontDoors) | ## Usage examples diff --git a/avm/res/network/front-door/main.bicep b/avm/res/network/front-door/main.bicep index 6e47218f83e..4ea7e31e76e 100644 --- a/avm/res/network/front-door/main.bicep +++ b/avm/res/network/front-door/main.bicep @@ -101,7 +101,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } } -resource frontDoor 'Microsoft.Network/frontDoors@2020-05-01' = { +resource frontDoor 'Microsoft.Network/frontDoors@2021-06-01' = { name: name location: 'global' tags: tags diff --git a/avm/res/network/front-door/main.json b/avm/res/network/front-door/main.json index 9a418299482..6e1554fd90d 100644 --- a/avm/res/network/front-door/main.json +++ b/avm/res/network/front-door/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1188225161775362575" + "templateHash": "3109919695748867092" }, "name": "Azure Front Doors", "description": "This module deploys an Azure Front Door.", @@ -380,7 +380,7 @@ }, "frontDoor": { "type": "Microsoft.Network/frontDoors", - "apiVersion": "2020-05-01", + "apiVersion": "2021-06-01", "name": "[parameters('name')]", "location": "global", "tags": "[parameters('tags')]", From 62198b03cde2790b5e69d44b79d1d072f8a2456c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20H=C3=A9zser?= Date: Fri, 20 Sep 2024 00:50:35 +0200 Subject: [PATCH 23/46] fix: `avm/ptn/deployment/import-image-to-acr` fixes bugs handling newImageName (#3329) ## Description Fixes bugs handling newImageName Closes #3326 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.deployment-script.import-image-to-acr](https://github.com/ReneHezser/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml/badge.svg?branch=import-image-fixes)](https://github.com/ReneHezser/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [x] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/deployment-script/import-image-to-acr/README.md | 6 +++--- avm/ptn/deployment-script/import-image-to-acr/main.bicep | 6 ++++-- avm/ptn/deployment-script/import-image-to-acr/main.json | 6 +++--- .../import-image-to-acr/tests/e2e/max/main.test.bicep | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/avm/ptn/deployment-script/import-image-to-acr/README.md b/avm/ptn/deployment-script/import-image-to-acr/README.md index 42038de2c6d..9015f8760ed 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/README.md +++ b/avm/ptn/deployment-script/import-image-to-acr/README.md @@ -115,7 +115,7 @@ module importImageToAcr 'br/public:avm/ptn/deployment-script/import-image-to-acr cleanupPreference: 'OnExpiration' location: '' managedIdentities: '' - newImageName: 'your-image-name:tag' + newImageName: 'application/your-image-name:tag' overwriteExistingImage: true storageAccountResourceId: '' subnetResourceIds: '' @@ -163,7 +163,7 @@ module importImageToAcr 'br/public:avm/ptn/deployment-script/import-image-to-acr "value": "" }, "newImageName": { - "value": "your-image-name:tag" + "value": "application/your-image-name:tag" }, "overwriteExistingImage": { "value": true @@ -395,7 +395,7 @@ The new image name in the ACR. You can use this to import a publically available - Required: No - Type: string -- Default: `[last(split(parameters('image'), '/'))]` +- Default: `[string(skip(parameters('image'), add(indexOf(parameters('image'), '/'), 1)))]` - Example: `your-image-name:tag` ### Parameter: `overwriteExistingImage` diff --git a/avm/ptn/deployment-script/import-image-to-acr/main.bicep b/avm/ptn/deployment-script/import-image-to-acr/main.bicep index 64be560c364..61534011ce2 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/main.bicep +++ b/avm/ptn/deployment-script/import-image-to-acr/main.bicep @@ -47,7 +47,7 @@ param sourceRegistryPassword string = '' @metadata({ example: 'your-image-name:tag' }) -param newImageName string = last(split(image, '/')) +param newImageName string = string(skip(image, indexOf(image, '/') + 1)) @description('Optional. The image will be overwritten if it already exists in the ACR with the same tag. Default is false.') param overwriteExistingImage bool = false @@ -234,7 +234,9 @@ output deploymentScriptOutput string[] = imageImport.outputs.deploymentScriptLog @description('An array of the imported images.') output importedImage importedImageType = { originalImage: image - acrHostedImage: '${acr.properties.loginServer}${string(skip(image, indexOf(image,'/')))}' + acrHostedImage: empty(newImageName) + ? '${acr.properties.loginServer}${string(skip(image, indexOf(image,'/')))}' + : '${acr.properties.loginServer}/${newImageName}' } // ================ // diff --git a/avm/ptn/deployment-script/import-image-to-acr/main.json b/avm/ptn/deployment-script/import-image-to-acr/main.json index 51751e27f8d..4f8a4504ae2 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/main.json +++ b/avm/ptn/deployment-script/import-image-to-acr/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15179702678978782456" + "templateHash": "13166673091432959100" }, "name": "import-image-to-acr", "description": "This modules deployes an image to an Azure Container Registry.", @@ -127,7 +127,7 @@ }, "newImageName": { "type": "string", - "defaultValue": "[last(split(parameters('image'), '/'))]", + "defaultValue": "[string(skip(parameters('image'), add(indexOf(parameters('image'), '/'), 1)))]", "metadata": { "example": "your-image-name:tag", "description": "Optional. The new image name in the ACR. You can use this to import a publically available image with a custom name for later updating from e.g., your build pipeline." @@ -910,7 +910,7 @@ }, "value": { "originalImage": "[parameters('image')]", - "acrHostedImage": "[format('{0}{1}', reference('acr').loginServer, string(skip(parameters('image'), indexOf(parameters('image'), '/'))))]" + "acrHostedImage": "[if(empty(parameters('newImageName')), format('{0}{1}', reference('acr').loginServer, string(skip(parameters('image'), indexOf(parameters('image'), '/')))), format('{0}/{1}', reference('acr').loginServer, parameters('newImageName')))]" } } } diff --git a/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep b/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep index 3c7e75c684d..8cd3fd3db15 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep +++ b/avm/ptn/deployment-script/import-image-to-acr/tests/e2e/max/main.test.bicep @@ -69,7 +69,7 @@ module testDeployment '../../../main.bicep' = [ // commented out, as the user is not available in the test environment // sourceRegistryUsername: 'username' // sourceRegistryPassword: keyVault.getSecret(dependencies.outputs.keyVaultSecretName) - newImageName: 'your-image-name:tag' + newImageName: 'application/your-image-name:tag' cleanupPreference: 'OnExpiration' assignRbacRole: true managedIdentities: { userAssignedResourcesIds: [dependencies.outputs.managedIdentityResourceId] } From c447185e556f0586ae0e78c254886d9144978bd2 Mon Sep 17 00:00:00 2001 From: Fabio Masciotra Date: Fri, 20 Sep 2024 01:18:35 +0200 Subject: [PATCH 24/46] feat: experimental use of 'discriminator' for vpng-gw module (#2667) ## Description Closes https://github.com/Azure/bicep-registry-modules/issues/1869 Reviewed the code with the use of 'discriminator' for the UDT. Added the following test-cases: 1. BGP: VPN Active Passive with BGP config but no APIPA 2. BGPAA: VPN Active Active with BGP config but no APIPA 3. BGPAPIPA: VPN Active Passive with BGP config and APIPA 4. BGPAAAPIPA: VPN Active Active with BGP config and APIPA Plus other test-cases without BGP ## Pipeline Reference [![avm.res.network.virtual-network-gateway](https://github.com/fabmas/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml/badge.svg?branch=vnetgw-draft)](https://github.com/fabmas/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml) ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .../network/virtual-network-gateway/README.md | 833 ++++++++++++++++-- .../virtual-network-gateway/main.bicep | 135 ++- .../network/virtual-network-gateway/main.json | 200 ++++- .../tests/e2e/aadvpn/main.test.bicep | 6 +- .../e2e/activeActiveBGP/dependencies.bicep | 49 ++ .../tests/e2e/activeActiveBGP/main.test.bicep | 83 ++ .../activeActiveBgpAPIPA/dependencies.bicep | 49 ++ .../e2e/activeActiveBgpAPIPA/main.test.bicep | 85 ++ .../e2e/activeActiveNoBGP/dependencies.bicep | 49 ++ .../e2e/activeActiveNoBGP/main.test.bicep | 83 ++ .../e2e/activePassiveBGP/dependencies.bicep | 49 ++ .../e2e/activePassiveBGP/main.test.bicep | 85 ++ .../e2e/activePassiveNoBGP/dependencies.bicep | 49 ++ .../e2e/activePassiveNoBGP/main.test.bicep | 83 ++ .../tests/e2e/defaults/main.test.bicep | 3 + .../tests/e2e/expressRoute/main.test.bicep | 3 + .../tests/e2e/max/main.test.bicep | 7 +- .../tests/e2e/vpn-no-az/main.test.bicep | 3 + .../tests/e2e/vpn/main.test.bicep | 4 +- .../tests/e2e/waf-aligned/main.test.bicep | 7 +- .../virtual-network-gateway/version.json | 2 +- 21 files changed, 1699 insertions(+), 168 deletions(-) create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/dependencies.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/main.test.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/dependencies.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/main.test.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/dependencies.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/main.test.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/dependencies.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/main.test.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/dependencies.bicep create mode 100644 avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/main.test.bicep diff --git a/avm/res/network/virtual-network-gateway/README.md b/avm/res/network/virtual-network-gateway/README.md index 83cde965b03..644177396ea 100644 --- a/avm/res/network/virtual-network-gateway/README.md +++ b/avm/res/network/virtual-network-gateway/README.md @@ -31,12 +31,17 @@ The following section provides usage examples for the module, which were used to >**Note**: To reference the module, please use the following syntax `br/public:avm/res/network/virtual-network-gateway:`. - [AAD-VPN](#example-1-aad-vpn) -- [Using only defaults](#example-2-using-only-defaults) -- [ExpressRoute](#example-3-expressroute) -- [Using large parameter set](#example-4-using-large-parameter-set) -- [Using SKU without Availability Zones](#example-5-using-sku-without-availability-zones) -- [VPN](#example-6-vpn) -- [WAF-aligned](#example-7-waf-aligned) +- [VPN Active Active with BGP settings](#example-2-vpn-active-active-with-bgp-settings) +- [VPN Active Active with BGP settings](#example-3-vpn-active-active-with-bgp-settings) +- [VPN Active Active without BGP settings](#example-4-vpn-active-active-without-bgp-settings) +- [VPN Active Passive with BGP settings](#example-5-vpn-active-passive-with-bgp-settings) +- [VPN Active Passive without BGP settings](#example-6-vpn-active-passive-without-bgp-settings) +- [Using only defaults](#example-7-using-only-defaults) +- [ExpressRoute](#example-8-expressroute) +- [Using large parameter set](#example-9-using-large-parameter-set) +- [Using SKU without Availability Zones](#example-10-using-sku-without-availability-zones) +- [VPN](#example-11-vpn) +- [WAF-aligned](#example-12-waf-aligned) ### Example 1: _AAD-VPN_ @@ -52,32 +57,635 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + clusterMode: 'activePassiveNoBgp' + } + gatewayType: 'Vpn' + name: 'nvgavpn001' + skuName: 'VpnGw2AZ' + vNetResourceId: '' + // Non-required parameters + domainNameLabel: [ + 'dm-nvgavpn' + ] + location: '' + publicIpZones: [ + 1 + 2 + 3 + ] + vpnClientAadConfiguration: { + aadAudience: '41b23e61-6c1e-4545-b367-cd054e0ed4b4' + aadIssuer: '' + aadTenant: '' + vpnAuthenticationTypes: [ + 'AAD' + ] + vpnClientProtocols: [ + 'OpenVPN' + ] + } + vpnType: 'RouteBased' + } +} +``` + + +

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activePassiveNoBgp" + } + }, + "gatewayType": { + "value": "Vpn" + }, + "name": { + "value": "nvgavpn001" + }, + "skuName": { + "value": "VpnGw2AZ" + }, + "vNetResourceId": { + "value": "" + }, + // Non-required parameters + "domainNameLabel": { + "value": [ + "dm-nvgavpn" + ] + }, + "location": { + "value": "" + }, + "publicIpZones": { + "value": [ + 1, + 2, + 3 + ] + }, + "vpnClientAadConfiguration": { + "value": { + "aadAudience": "41b23e61-6c1e-4545-b367-cd054e0ed4b4", + "aadIssuer": "", + "aadTenant": "", + "vpnAuthenticationTypes": [ + "AAD" + ], + "vpnClientProtocols": [ + "OpenVPN" + ] + } + }, + "vpnType": { + "value": "RouteBased" + } + } +} +``` + +
+

+ +### Example 2: _VPN Active Active with BGP settings_ + +This instance deploys the module with the VPN Active Active with BGP settings. + + +

+ +via Bicep module + +```bicep +module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:' = { + name: 'virtualNetworkGatewayDeployment' + params: { + // Required parameters + clusterSettings: { + clusterMode: 'activeActiveBgp' + } + gatewayType: 'Vpn' + name: 'nvgaab001' + skuName: 'VpnGw2AZ' + vNetResourceId: '' + // Non-required parameters + allowRemoteVnetTraffic: true + disableIPSecReplayProtection: true + domainNameLabel: [ + 'dm-nvgaab' + ] + enableBgpRouteTranslationForNat: true + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: '' + location: '' + publicIpZones: [ + 1 + 2 + 3 + ] + vpnGatewayGeneration: 'Generation2' + vpnType: 'RouteBased' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activeActiveBgp" + } + }, + "gatewayType": { + "value": "Vpn" + }, + "name": { + "value": "nvgaab001" + }, + "skuName": { + "value": "VpnGw2AZ" + }, + "vNetResourceId": { + "value": "" + }, + // Non-required parameters + "allowRemoteVnetTraffic": { + "value": true + }, + "disableIPSecReplayProtection": { + "value": true + }, + "domainNameLabel": { + "value": [ + "dm-nvgaab" + ] + }, + "enableBgpRouteTranslationForNat": { + "value": true + }, + "enablePrivateIpAddress": { + "value": true + }, + "gatewayDefaultSiteLocalNetworkGatewayId": { + "value": "" + }, + "location": { + "value": "" + }, + "publicIpZones": { + "value": [ + 1, + 2, + 3 + ] + }, + "vpnGatewayGeneration": { + "value": "Generation2" + }, + "vpnType": { + "value": "RouteBased" + } + } +} +``` + +
+

+ +### Example 3: _VPN Active Active with BGP settings_ + +This instance deploys the module with the VPN Active Active with APIPA BGP settings. + + +

+ +via Bicep module + +```bicep +module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:' = { + name: 'virtualNetworkGatewayDeployment' + params: { + // Required parameters + clusterSettings: { + clusterMode: 'activeActiveBgp' + customBgpIpAddresses: [ + '169.254.21.4' + '169.254.21.5' + ] + secondCustomBgpIpAddresses: [ + '169.254.22.4' + '169.254.22.5' + ] + } + gatewayType: 'Vpn' + name: 'nvgaaa001' + skuName: 'VpnGw2AZ' + vNetResourceId: '' + // Non-required parameters + allowRemoteVnetTraffic: true + disableIPSecReplayProtection: true + domainNameLabel: [ + 'dm-nvgaaa' + ] + enableBgpRouteTranslationForNat: true + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: '' + location: '' + publicIpZones: [ + 1 + 2 + 3 + ] + vpnGatewayGeneration: 'Generation2' + vpnType: 'RouteBased' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activeActiveBgp", + "customBgpIpAddresses": [ + "169.254.21.4", + "169.254.21.5" + ], + "secondCustomBgpIpAddresses": [ + "169.254.22.4", + "169.254.22.5" + ] + } + }, + "gatewayType": { + "value": "Vpn" + }, + "name": { + "value": "nvgaaa001" + }, + "skuName": { + "value": "VpnGw2AZ" + }, + "vNetResourceId": { + "value": "" + }, + // Non-required parameters + "allowRemoteVnetTraffic": { + "value": true + }, + "disableIPSecReplayProtection": { + "value": true + }, + "domainNameLabel": { + "value": [ + "dm-nvgaaa" + ] + }, + "enableBgpRouteTranslationForNat": { + "value": true + }, + "enablePrivateIpAddress": { + "value": true + }, + "gatewayDefaultSiteLocalNetworkGatewayId": { + "value": "" + }, + "location": { + "value": "" + }, + "publicIpZones": { + "value": [ + 1, + 2, + 3 + ] + }, + "vpnGatewayGeneration": { + "value": "Generation2" + }, + "vpnType": { + "value": "RouteBased" + } + } +} +``` + +
+

+ +### Example 4: _VPN Active Active without BGP settings_ + +This instance deploys the module with the VPN Active Active without BGP settings. + + +

+ +via Bicep module + +```bicep +module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:' = { + name: 'virtualNetworkGatewayDeployment' + params: { + // Required parameters + clusterSettings: { + clusterMode: 'activeActiveNoBgp' + } + gatewayType: 'Vpn' + name: 'nvgaa001' + skuName: 'VpnGw2AZ' + vNetResourceId: '' + // Non-required parameters + allowRemoteVnetTraffic: true + disableIPSecReplayProtection: true + domainNameLabel: [ + 'dm-nvgaa' + ] + enableBgpRouteTranslationForNat: true + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: '' + location: '' + publicIpZones: [ + 1 + 2 + 3 + ] + vpnGatewayGeneration: 'Generation2' + vpnType: 'RouteBased' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activeActiveNoBgp" + } + }, + "gatewayType": { + "value": "Vpn" + }, + "name": { + "value": "nvgaa001" + }, + "skuName": { + "value": "VpnGw2AZ" + }, + "vNetResourceId": { + "value": "" + }, + // Non-required parameters + "allowRemoteVnetTraffic": { + "value": true + }, + "disableIPSecReplayProtection": { + "value": true + }, + "domainNameLabel": { + "value": [ + "dm-nvgaa" + ] + }, + "enableBgpRouteTranslationForNat": { + "value": true + }, + "enablePrivateIpAddress": { + "value": true + }, + "gatewayDefaultSiteLocalNetworkGatewayId": { + "value": "" + }, + "location": { + "value": "" + }, + "publicIpZones": { + "value": [ + 1, + 2, + 3 + ] + }, + "vpnGatewayGeneration": { + "value": "Generation2" + }, + "vpnType": { + "value": "RouteBased" + } + } +} +``` + +
+

+ +### Example 5: _VPN Active Passive with BGP settings_ + +This instance deploys the module with the VPN Active Passive with APIPA BGP settings. + + +

+ +via Bicep module + +```bicep +module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:' = { + name: 'virtualNetworkGatewayDeployment' + params: { + // Required parameters + clusterSettings: { + asn: 65815 + clusterMode: 'activePassiveBgp' + customBgpIpAddresses: [ + '169.254.21.4' + '169.254.21.5' + ] + } gatewayType: 'Vpn' - name: 'nvngavpn001' + name: 'nvgapb001' skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters - activeActive: false + allowRemoteVnetTraffic: true + disableIPSecReplayProtection: true domainNameLabel: [ - 'dm-nvngavpn' + 'dm-nvgapb' ] + enableBgpRouteTranslationForNat: true + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: '' location: '' publicIpZones: [ 1 2 3 ] - vpnClientAadConfiguration: { - aadAudience: '41b23e61-6c1e-4545-b367-cd054e0ed4b4' - aadIssuer: '' - aadTenant: '' - vpnAuthenticationTypes: [ - 'AAD' + vpnGatewayGeneration: 'Generation2' + vpnType: 'RouteBased' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "clusterSettings": { + "value": { + "asn": 65815, + "clusterMode": "activePassiveBgp", + "customBgpIpAddresses": [ + "169.254.21.4", + "169.254.21.5" + ] + } + }, + "gatewayType": { + "value": "Vpn" + }, + "name": { + "value": "nvgapb001" + }, + "skuName": { + "value": "VpnGw2AZ" + }, + "vNetResourceId": { + "value": "" + }, + // Non-required parameters + "allowRemoteVnetTraffic": { + "value": true + }, + "disableIPSecReplayProtection": { + "value": true + }, + "domainNameLabel": { + "value": [ + "dm-nvgapb" ] - vpnClientProtocols: [ - 'OpenVPN' + }, + "enableBgpRouteTranslationForNat": { + "value": true + }, + "enablePrivateIpAddress": { + "value": true + }, + "gatewayDefaultSiteLocalNetworkGatewayId": { + "value": "" + }, + "location": { + "value": "" + }, + "publicIpZones": { + "value": [ + 1, + 2, + 3 ] + }, + "vpnGatewayGeneration": { + "value": "Generation2" + }, + "vpnType": { + "value": "RouteBased" + } + } +} +``` + +
+

+ +### Example 6: _VPN Active Passive without BGP settings_ + +This instance deploys the module with the VPN Active Passive without BGP settings. + + +

+ +via Bicep module + +```bicep +module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:' = { + name: 'virtualNetworkGatewayDeployment' + params: { + // Required parameters + clusterSettings: { + clusterMode: 'activePassiveNoBgp' } + gatewayType: 'Vpn' + name: 'nvgap001' + skuName: 'VpnGw2AZ' + vNetResourceId: '' + // Non-required parameters + allowRemoteVnetTraffic: true + disableIPSecReplayProtection: true + domainNameLabel: [ + 'dm-nvgap' + ] + enableBgpRouteTranslationForNat: true + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: '' + location: '' + publicIpZones: [ + 1 + 2 + 3 + ] + vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } } @@ -96,11 +704,16 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activePassiveNoBgp" + } + }, "gatewayType": { "value": "Vpn" }, "name": { - "value": "nvngavpn001" + "value": "nvgap001" }, "skuName": { "value": "VpnGw2AZ" @@ -109,14 +722,26 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "value": "" }, // Non-required parameters - "activeActive": { - "value": false + "allowRemoteVnetTraffic": { + "value": true + }, + "disableIPSecReplayProtection": { + "value": true }, "domainNameLabel": { "value": [ - "dm-nvngavpn" + "dm-nvgap" ] }, + "enableBgpRouteTranslationForNat": { + "value": true + }, + "enablePrivateIpAddress": { + "value": true + }, + "gatewayDefaultSiteLocalNetworkGatewayId": { + "value": "" + }, "location": { "value": "" }, @@ -127,18 +752,8 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, - "vpnClientAadConfiguration": { - "value": { - "aadAudience": "41b23e61-6c1e-4545-b367-cd054e0ed4b4", - "aadIssuer": "", - "aadTenant": "", - "vpnAuthenticationTypes": [ - "AAD" - ], - "vpnClientProtocols": [ - "OpenVPN" - ] - } + "vpnGatewayGeneration": { + "value": "Generation2" }, "vpnType": { "value": "RouteBased" @@ -150,7 +765,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:

-### Example 2: _Using only defaults_ +### Example 7: _Using only defaults_ This instance deploys the module with the minimum set of required parameters. @@ -164,6 +779,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + clusterMode: 'activeActiveNoBgp' + } gatewayType: 'Vpn' name: 'nvgmin001' skuName: 'VpnGw2AZ' @@ -192,6 +810,11 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activeActiveNoBgp" + } + }, "gatewayType": { "value": "Vpn" }, @@ -222,7 +845,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:

-### Example 3: _ExpressRoute_ +### Example 8: _ExpressRoute_ This instance deploys the module with the ExpressRoute set of required parameters. @@ -236,6 +859,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + clusterMode: 'activePassiveBgp' + } gatewayType: 'ExpressRoute' name: 'nvger001' skuName: 'ErGw1AZ' @@ -268,6 +894,11 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activePassiveBgp" + } + }, "gatewayType": { "value": "ExpressRoute" }, @@ -306,7 +937,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:

-### Example 4: _Using large parameter set_ +### Example 9: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -320,12 +951,23 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + activeGatewayPipName: 'nvgmax001-pip2' + clusterMode: 'activeActiveBgp' + customBgpIpAddresses: [ + '169.254.21.4' + '169.254.21.5' + ] + secondCustomBgpIpAddresses: [ + '169.254.22.4' + '169.254.22.5' + ] + } gatewayType: 'Vpn' name: 'nvgmax001' skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters - activeActive: true allowRemoteVnetTraffic: true diagnosticSettings: [ { @@ -435,6 +1077,20 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "activeGatewayPipName": "nvgmax001-pip2", + "clusterMode": "activeActiveBgp", + "customBgpIpAddresses": [ + "169.254.21.4", + "169.254.21.5" + ], + "secondCustomBgpIpAddresses": [ + "169.254.22.4", + "169.254.22.5" + ] + } + }, "gatewayType": { "value": "Vpn" }, @@ -448,9 +1104,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "value": "" }, // Non-required parameters - "activeActive": { - "value": true - }, "allowRemoteVnetTraffic": { "value": true }, @@ -580,7 +1233,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:

-### Example 5: _Using SKU without Availability Zones_ +### Example 10: _Using SKU without Availability Zones_ This instance deploys the module with a SKU that does not support Availability Zones. @@ -594,6 +1247,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + clusterMode: 'activePassiveNoBgp' + } gatewayType: 'Vpn' name: 'nvgnaz001' skuName: 'VpnGw1' @@ -617,6 +1273,11 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activePassiveNoBgp" + } + }, "gatewayType": { "value": "Vpn" }, @@ -640,7 +1301,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:

-### Example 6: _VPN_ +### Example 11: _VPN_ This instance deploys the module with the VPN set of required parameters. @@ -654,12 +1315,14 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + clusterMode: 'activeActiveNoBgp' + } gatewayType: 'Vpn' name: 'nvgvpn001' skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters - activeActive: true allowRemoteVnetTraffic: true disableIPSecReplayProtection: true domainNameLabel: [ @@ -693,6 +1356,11 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "clusterMode": "activeActiveNoBgp" + } + }, "gatewayType": { "value": "Vpn" }, @@ -706,9 +1374,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "value": "" }, // Non-required parameters - "activeActive": { - "value": true - }, "allowRemoteVnetTraffic": { "value": true }, @@ -752,7 +1417,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway:

-### Example 7: _WAF-aligned_ +### Example 12: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -766,12 +1431,23 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: name: 'virtualNetworkGatewayDeployment' params: { // Required parameters + clusterSettings: { + asn: 65515 + clusterMode: 'activeActiveBgp' + customBgpIpAddresses: [ + '169.254.21.4' + '169.254.21.5' + ] + secondCustomBgpIpAddresses: [ + '169.254.22.4' + '169.254.22.5' + ] + } gatewayType: 'Vpn' name: 'nvgmwaf001' skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters - activeActive: true allowRemoteVnetTraffic: true diagnosticSettings: [ { @@ -862,6 +1538,20 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "contentVersion": "1.0.0.0", "parameters": { // Required parameters + "clusterSettings": { + "value": { + "asn": 65515, + "clusterMode": "activeActiveBgp", + "customBgpIpAddresses": [ + "169.254.21.4", + "169.254.21.5" + ], + "secondCustomBgpIpAddresses": [ + "169.254.22.4", + "169.254.22.5" + ] + } + }, "gatewayType": { "value": "Vpn" }, @@ -875,9 +1565,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "value": "" }, // Non-required parameters - "activeActive": { - "value": true - }, "allowRemoteVnetTraffic": { "value": true }, @@ -992,6 +1679,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: | Parameter | Type | Description | | :-- | :-- | :-- | +| [`clusterSettings`](#parameter-clustersettings) | object | Specifies one of the following four configurations: Active-Active with (clusterMode = activeActiveBgp) or without (clusterMode = activeActiveNoBgp) BGP, Active-Passive with (clusterMode = activePassiveBgp) or without (clusterMode = activePassiveNoBgp) BGP. | | [`gatewayType`](#parameter-gatewaytype) | string | Specifies the gateway type. E.g. VPN, ExpressRoute. | | [`name`](#parameter-name) | string | Specifies the Virtual Network Gateway name. | | [`skuName`](#parameter-skuname) | string | The SKU of the Gateway. | @@ -1001,17 +1689,13 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: | Parameter | Type | Description | | :-- | :-- | :-- | -| [`activeActive`](#parameter-activeactive) | bool | Value to specify if the Gateway should be deployed in active-active or active-passive configuration. | -| [`activeGatewayPipName`](#parameter-activegatewaypipname) | string | Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it's not provided, a '-pip' suffix will be appended to the gateway's name. | | [`allowRemoteVnetTraffic`](#parameter-allowremotevnettraffic) | bool | Configure this gateway to accept traffic from other Azure Virtual Networks. This configuration does not support connectivity to Azure Virtual WAN. | | [`allowVirtualWanTraffic`](#parameter-allowvirtualwantraffic) | bool | Configures this gateway to accept traffic from remote Virtual WAN networks. | -| [`asn`](#parameter-asn) | int | ASN value. | | [`clientRevokedCertThumbprint`](#parameter-clientrevokedcertthumbprint) | string | Thumbprint of the revoked certificate. This would revoke VPN client certificates matching this thumbprint from connecting to the VNet. | | [`clientRootCertData`](#parameter-clientrootcertdata) | string | Client root certificate data used to authenticate VPN clients. Cannot be configured if vpnClientAadConfiguration is provided. | | [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | | [`disableIPSecReplayProtection`](#parameter-disableipsecreplayprotection) | bool | disableIPSecReplayProtection flag. Used for VPN Gateways. | | [`domainNameLabel`](#parameter-domainnamelabel) | array | DNS name(s) of the Public IP resource(s). If you enabled active-active configuration, you need to provide 2 DNS names, if you want to use this feature. A region specific suffix will be appended to it, e.g.: your-DNS-name.westeurope.cloudapp.azure.com. | -| [`enableBgp`](#parameter-enablebgp) | bool | Value to specify if BGP is enabled or not. | | [`enableBgpRouteTranslationForNat`](#parameter-enablebgproutetranslationfornat) | bool | EnableBgpRouteTranslationForNat flag. Can only be used when "natRules" are enabled on the Virtual Network Gateway. | | [`enableDnsForwarding`](#parameter-enablednsforwarding) | bool | Whether DNS forwarding is enabled or not and is only supported for Express Route Gateways. The DNS forwarding feature flag must be enabled on the current subscription. | | [`enablePrivateIpAddress`](#parameter-enableprivateipaddress) | bool | Whether private IP needs to be enabled on this gateway for connections or not. Used for configuring a Site-to-Site VPN connection over ExpressRoute private peering. | @@ -1031,6 +1715,13 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: | [`vpnGatewayGeneration`](#parameter-vpngatewaygeneration) | string | The generation for this VirtualNetworkGateway. Must be None if virtualNetworkGatewayType is not VPN. | | [`vpnType`](#parameter-vpntype) | string | Specifies the VPN type. | +### Parameter: `clusterSettings` + +Specifies one of the following four configurations: Active-Active with (clusterMode = activeActiveBgp) or without (clusterMode = activeActiveNoBgp) BGP, Active-Passive with (clusterMode = activePassiveBgp) or without (clusterMode = activePassiveNoBgp) BGP. + +- Required: Yes +- Type: object + ### Parameter: `gatewayType` Specifies the gateway type. E.g. VPN, ExpressRoute. @@ -1088,22 +1779,6 @@ Virtual Network resource ID. - Required: Yes - Type: string -### Parameter: `activeActive` - -Value to specify if the Gateway should be deployed in active-active or active-passive configuration. - -- Required: No -- Type: bool -- Default: `True` - -### Parameter: `activeGatewayPipName` - -Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it's not provided, a '-pip' suffix will be appended to the gateway's name. - -- Required: No -- Type: string -- Default: `[format('{0}-pip2', parameters('name'))]` - ### Parameter: `allowRemoteVnetTraffic` Configure this gateway to accept traffic from other Azure Virtual Networks. This configuration does not support connectivity to Azure Virtual WAN. @@ -1120,14 +1795,6 @@ Configures this gateway to accept traffic from remote Virtual WAN networks. - Type: bool - Default: `False` -### Parameter: `asn` - -ASN value. - -- Required: No -- Type: int -- Default: `65815` - ### Parameter: `clientRevokedCertThumbprint` Thumbprint of the revoked certificate. This would revoke VPN client certificates matching this thumbprint from connecting to the VNet. @@ -1306,14 +1973,6 @@ DNS name(s) of the Public IP resource(s). If you enabled active-active configura - Type: array - Default: `[]` -### Parameter: `enableBgp` - -Value to specify if BGP is enabled or not. - -- Required: No -- Type: bool -- Default: `True` - ### Parameter: `enableBgpRouteTranslationForNat` EnableBgpRouteTranslationForNat flag. Can only be used when "natRules" are enabled on the Virtual Network Gateway. diff --git a/avm/res/network/virtual-network-gateway/main.bicep b/avm/res/network/virtual-network-gateway/main.bicep index 6b73abca5d2..15b0247f692 100644 --- a/avm/res/network/virtual-network-gateway/main.bicep +++ b/avm/res/network/virtual-network-gateway/main.bicep @@ -11,9 +11,6 @@ param location string = resourceGroup().location @description('Optional. Specifies the name of the Public IP used by the Virtual Network Gateway. If it\'s not provided, a \'-pip\' suffix will be appended to the gateway\'s name.') param gatewayPipName string = '${name}-pip1' -@description('Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it\'s not provided, a \'-pip\' suffix will be appended to the gateway\'s name.') -param activeGatewayPipName string = '${name}-pip2' - @description('Optional. Resource ID of the Public IP Prefix object. This is only needed if you want your Public IPs created in a PIP Prefix.') param publicIPPrefixResourceId string = '' @@ -70,14 +67,8 @@ param vpnType string = 'RouteBased' @description('Required. Virtual Network resource ID.') param vNetResourceId string -@description('Optional. Value to specify if the Gateway should be deployed in active-active or active-passive configuration.') -param activeActive bool = true - -@description('Optional. Value to specify if BGP is enabled or not.') -param enableBgp bool = true - -@description('Optional. ASN value.') -param asn int = 65815 +@description('Required. Specifies one of the following four configurations: Active-Active with (clusterMode = activeActiveBgp) or without (clusterMode = activeActiveNoBgp) BGP, Active-Passive with (clusterMode = activePassiveBgp) or without (clusterMode = activePassiveNoBgp) BGP.') +param clusterSettings clusterSettingType @description('Optional. The IP address range from which VPN clients will receive an IP address when connected. Range specified must not overlap with on-premise network.') param vpnClientAddressPoolPrefix string = '' @@ -140,25 +131,53 @@ param vpnClientAadConfiguration object = {} // Other Variables var gatewayPipAllocationMethod = skuName == 'Basic' ? 'Dynamic' : 'Static' -var isActiveActiveValid = gatewayType != 'ExpressRoute' ? activeActive : false -var virtualGatewayPipNameVar = isActiveActiveValid +var isExpressRoute = gatewayType == 'ExpressRoute' + +var vpnTypeVar = !isExpressRoute ? vpnType : 'PolicyBased' + +var isBgp = (clusterSettings.clusterMode == 'activeActiveBgp' || clusterSettings.clusterMode == 'activePassiveBgp') && !isExpressRoute + +var isActiveActive = (clusterSettings.clusterMode == 'activeActiveNoBgp' || clusterSettings.clusterMode == 'activeActiveBgp') && !isExpressRoute + +var activeGatewayPipNameVar = isActiveActive ? (clusterSettings.?activeGatewayPipName ?? '${name}-pip2') : null + +var virtualGatewayPipNameVar = isActiveActive ? [ gatewayPipName - activeGatewayPipName + activeGatewayPipNameVar ] : [ gatewayPipName ] -var vpnTypeVar = gatewayType != 'ExpressRoute' ? vpnType : 'PolicyBased' +// Potential BGP configurations (active-active vs active-passive) +var bgpSettingsVar = isActiveActive + ? { + asn: clusterSettings.?asn ?? 65515 + bgpPeeringAddresses: [ + { + customBgpIpAddresses: clusterSettings.?customBgpIpAddresses + ipconfigurationId: '${az.resourceId('Microsoft.Network/virtualNetworkGateways', name)}/ipConfigurations/vNetGatewayConfig1' + } + { + customBgpIpAddresses: clusterSettings.?secondCustomBgpIpAddresses + ipconfigurationId: '${az.resourceId('Microsoft.Network/virtualNetworkGateways', name)}/ipConfigurations/vNetGatewayConfig2' + } + ] + } + : { + asn: clusterSettings.?asn ?? 65515 + bgpPeeringAddresses: [ + { + customBgpIpAddresses: clusterSettings.?customBgpIpAddresses + ipconfigurationId: '${az.resourceId('Microsoft.Network/virtualNetworkGateways', name)}/ipConfigurations/vNetGatewayConfig1' + } + ] + } -var isBgpValid = gatewayType != 'ExpressRoute' ? enableBgp : false -var bgpSettings = { - asn: asn -} -// Potential configurations (active-active vs active-passive) -var ipConfiguration = isActiveActiveValid +// Potential IP configurations (active-active vs active-passive) +var ipConfiguration = isActiveActive ? [ { properties: { @@ -179,8 +198,8 @@ var ipConfiguration = isActiveActiveValid id: '${vNetResourceId}/subnets/GatewaySubnet' } publicIPAddress: { - id: isActiveActiveValid - ? az.resourceId('Microsoft.Network/publicIPAddresses', activeGatewayPipName) + id: isActiveActive + ? az.resourceId('Microsoft.Network/publicIPAddresses', activeGatewayPipNameVar) : az.resourceId('Microsoft.Network/publicIPAddresses', gatewayPipName) } } @@ -331,13 +350,13 @@ resource virtualNetworkGateway 'Microsoft.Network/virtualNetworkGateways@2023-04 tags: tags properties: { ipConfigurations: ipConfiguration - activeActive: isActiveActiveValid + activeActive: isActiveActive allowRemoteVnetTraffic: allowRemoteVnetTraffic allowVirtualWanTraffic: allowVirtualWanTraffic - enableBgp: isBgpValid - bgpSettings: isBgpValid ? bgpSettings : null + enableBgp: isBgp + bgpSettings: isBgp ? bgpSettingsVar : null disableIPSecReplayProtection: disableIPSecReplayProtection - enableDnsForwarding: gatewayType == 'ExpressRoute' ? enableDnsForwarding : null + enableDnsForwarding: !isExpressRoute ? enableDnsForwarding : null enablePrivateIpAddress: enablePrivateIpAddress enableBgpRouteTranslationForNat: enableBgpRouteTranslationForNat gatewayType: gatewayType @@ -365,11 +384,11 @@ module virtualNetworkGateway_natRules 'nat-rule/main.bicep' = [ params: { name: natRule.name virtualNetworkGatewayName: virtualNetworkGateway.name - externalMappings: contains(natRule, 'externalMappings') ? natRule.externalMappings : [] - internalMappings: contains(natRule, 'internalMappings') ? natRule.internalMappings : [] - ipConfigurationId: contains(natRule, 'ipConfigurationId') ? natRule.ipConfigurationId : '' - mode: contains(natRule, 'mode') ? natRule.mode : '' - type: contains(natRule, 'type') ? natRule.type : '' + externalMappings: natRule.?externalMappings ?? [] + internalMappings: natRule.?internalMappings ?? [] + ipConfigurationId: natRule.?ipConfigurationId ?? '' + mode: natRule.?mode ?? '' + type: natRule.?type ?? '' } } ] @@ -533,3 +552,55 @@ type diagnosticSettingType = { @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') marketplacePartnerResourceId: string? }[]? + +type activePassiveNoBgpType = { + + clusterMode: 'activePassiveNoBgp' + +} + +type activeActiveNoBgpType = { + + clusterMode: 'activeActiveNoBgp' + + @description('Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it\'s not provided, a \'-pip2\' suffix will be appended to the gateway\'s name.') + activeGatewayPipName: string? + +} + +type activePassiveBgpType = { + + clusterMode: 'activePassiveBgp' + + @description('Optional. The Autonomous System Number value. If it\'s not provided, a default \'65515\' value will be assigned to the ASN.') + @minValue(0) + @maxValue(4294967295) + asn: int? + + @description('Optional. The list of custom BGP IP Address (APIPA) peering addresses which belong to IP configuration.') + customBgpIpAddresses: string[]? +} + +type activeActiveBgpType = { + + clusterMode: 'activeActiveBgp' + + @description('Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it\'s not provided, a \'-pip2\' suffix will be appended to the gateway\'s name.') + activeGatewayPipName: string? + + @description('Optional. The Autonomous System Number value. If it\'s not provided, a default \'65515\' value will be assigned to the ASN.') + @minValue(0) + @maxValue(4294967295) + asn: int? + + @description('Optional. The list of custom BGP IP Address (APIPA) peering addresses which belong to IP configuration.') + customBgpIpAddresses: string[]? + + @description('Optional. The list of the second custom BGP IP Address (APIPA) peering addresses which belong to IP configuration.') + secondCustomBgpIpAddresses: string[]? +} + +@discriminator('clusterMode') +type clusterSettingType = activeActiveNoBgpType | activeActiveBgpType | activePassiveBgpType | activePassiveNoBgpType + + diff --git a/avm/res/network/virtual-network-gateway/main.json b/avm/res/network/virtual-network-gateway/main.json index 19876a62108..5e1b4bdf4cb 100644 --- a/avm/res/network/virtual-network-gateway/main.json +++ b/avm/res/network/virtual-network-gateway/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15596993518588024968" + "templateHash": "973776533492793692" }, "name": "Virtual Network Gateways", "description": "This module deploys a Virtual Network Gateway.", @@ -230,6 +230,132 @@ } }, "nullable": true + }, + "activePassiveNoBgpType": { + "type": "object", + "properties": { + "clusterMode": { + "type": "string", + "allowedValues": [ + "activePassiveNoBgp" + ] + } + } + }, + "activeActiveNoBgpType": { + "type": "object", + "properties": { + "clusterMode": { + "type": "string", + "allowedValues": [ + "activeActiveNoBgp" + ] + }, + "activeGatewayPipName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it's not provided, a '-pip2' suffix will be appended to the gateway's name." + } + } + } + }, + "activePassiveBgpType": { + "type": "object", + "properties": { + "clusterMode": { + "type": "string", + "allowedValues": [ + "activePassiveBgp" + ] + }, + "asn": { + "type": "int", + "nullable": true, + "minValue": 0, + "maxValue": 4294967295, + "metadata": { + "description": "Optional. The Autonomous System Number value. If it's not provided, a default '65515' value will be assigned to the ASN." + } + }, + "customBgpIpAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of custom BGP IP Address (APIPA) peering addresses which belong to IP configuration." + } + } + } + }, + "activeActiveBgpType": { + "type": "object", + "properties": { + "clusterMode": { + "type": "string", + "allowedValues": [ + "activeActiveBgp" + ] + }, + "activeGatewayPipName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it's not provided, a '-pip2' suffix will be appended to the gateway's name." + } + }, + "asn": { + "type": "int", + "nullable": true, + "minValue": 0, + "maxValue": 4294967295, + "metadata": { + "description": "Optional. The Autonomous System Number value. If it's not provided, a default '65515' value will be assigned to the ASN." + } + }, + "customBgpIpAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of custom BGP IP Address (APIPA) peering addresses which belong to IP configuration." + } + }, + "secondCustomBgpIpAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of the second custom BGP IP Address (APIPA) peering addresses which belong to IP configuration." + } + } + } + }, + "clusterSettingType": { + "type": "object", + "discriminator": { + "propertyName": "clusterMode", + "mapping": { + "activeActiveNoBgp": { + "$ref": "#/definitions/activeActiveNoBgpType" + }, + "activeActiveBgp": { + "$ref": "#/definitions/activeActiveBgpType" + }, + "activePassiveBgp": { + "$ref": "#/definitions/activePassiveBgpType" + }, + "activePassiveNoBgp": { + "$ref": "#/definitions/activePassiveNoBgpType" + } + } + } } }, "parameters": { @@ -253,13 +379,6 @@ "description": "Optional. Specifies the name of the Public IP used by the Virtual Network Gateway. If it's not provided, a '-pip' suffix will be appended to the gateway's name." } }, - "activeGatewayPipName": { - "type": "string", - "defaultValue": "[format('{0}-pip2', parameters('name'))]", - "metadata": { - "description": "Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it's not provided, a '-pip' suffix will be appended to the gateway's name." - } - }, "publicIPPrefixResourceId": { "type": "string", "defaultValue": "", @@ -345,25 +464,10 @@ "description": "Required. Virtual Network resource ID." } }, - "activeActive": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Value to specify if the Gateway should be deployed in active-active or active-passive configuration." - } - }, - "enableBgp": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Value to specify if BGP is enabled or not." - } - }, - "asn": { - "type": "int", - "defaultValue": 65815, + "clusterSettings": { + "$ref": "#/definitions/clusterSettingType", "metadata": { - "description": "Optional. ASN value." + "description": "Required. Specifies one of the following four configurations: Active-Active with (clusterMode = activeActiveBgp) or without (clusterMode = activeActiveNoBgp) BGP, Active-Passive with (clusterMode = activePassiveBgp) or without (clusterMode = activePassiveNoBgp) BGP." } }, "vpnClientAddressPoolPrefix": { @@ -498,14 +602,14 @@ } ], "gatewayPipAllocationMethod": "[if(equals(parameters('skuName'), 'Basic'), 'Dynamic', 'Static')]", - "isActiveActiveValid": "[if(not(equals(parameters('gatewayType'), 'ExpressRoute')), parameters('activeActive'), false())]", - "virtualGatewayPipNameVar": "[if(variables('isActiveActiveValid'), createArray(parameters('gatewayPipName'), parameters('activeGatewayPipName')), createArray(parameters('gatewayPipName')))]", - "vpnTypeVar": "[if(not(equals(parameters('gatewayType'), 'ExpressRoute')), parameters('vpnType'), 'PolicyBased')]", - "isBgpValid": "[if(not(equals(parameters('gatewayType'), 'ExpressRoute')), parameters('enableBgp'), false())]", - "bgpSettings": { - "asn": "[parameters('asn')]" - }, - "ipConfiguration": "[if(variables('isActiveActiveValid'), createArray(createObject('properties', createObject('privateIPAllocationMethod', 'Dynamic', 'subnet', createObject('id', format('{0}/subnets/GatewaySubnet', parameters('vNetResourceId'))), 'publicIPAddress', createObject('id', resourceId('Microsoft.Network/publicIPAddresses', parameters('gatewayPipName')))), 'name', 'vNetGatewayConfig1'), createObject('properties', createObject('privateIPAllocationMethod', 'Dynamic', 'subnet', createObject('id', format('{0}/subnets/GatewaySubnet', parameters('vNetResourceId'))), 'publicIPAddress', createObject('id', if(variables('isActiveActiveValid'), resourceId('Microsoft.Network/publicIPAddresses', parameters('activeGatewayPipName')), resourceId('Microsoft.Network/publicIPAddresses', parameters('gatewayPipName'))))), 'name', 'vNetGatewayConfig2')), createArray(createObject('properties', createObject('privateIPAllocationMethod', 'Dynamic', 'subnet', createObject('id', format('{0}/subnets/GatewaySubnet', parameters('vNetResourceId'))), 'publicIPAddress', createObject('id', resourceId('Microsoft.Network/publicIPAddresses', parameters('gatewayPipName')))), 'name', 'vNetGatewayConfig1')))]", + "isExpressRoute": "[equals(parameters('gatewayType'), 'ExpressRoute')]", + "vpnTypeVar": "[if(not(variables('isExpressRoute')), parameters('vpnType'), 'PolicyBased')]", + "isBgp": "[and(or(equals(parameters('clusterSettings').clusterMode, 'activeActiveBgp'), equals(parameters('clusterSettings').clusterMode, 'activePassiveBgp')), not(variables('isExpressRoute')))]", + "isActiveActive": "[and(or(equals(parameters('clusterSettings').clusterMode, 'activeActiveNoBgp'), equals(parameters('clusterSettings').clusterMode, 'activeActiveBgp')), not(variables('isExpressRoute')))]", + "activeGatewayPipNameVar": "[if(variables('isActiveActive'), coalesce(tryGet(parameters('clusterSettings'), 'activeGatewayPipName'), format('{0}-pip2', parameters('name'))), null())]", + "virtualGatewayPipNameVar": "[if(variables('isActiveActive'), createArray(parameters('gatewayPipName'), variables('activeGatewayPipNameVar')), createArray(parameters('gatewayPipName')))]", + "bgpSettingsVar": "[if(variables('isActiveActive'), createObject('asn', coalesce(tryGet(parameters('clusterSettings'), 'asn'), 65515), 'bgpPeeringAddresses', createArray(createObject('customBgpIpAddresses', tryGet(parameters('clusterSettings'), 'customBgpIpAddresses'), 'ipconfigurationId', format('{0}/ipConfigurations/vNetGatewayConfig1', resourceId('Microsoft.Network/virtualNetworkGateways', parameters('name')))), createObject('customBgpIpAddresses', tryGet(parameters('clusterSettings'), 'secondCustomBgpIpAddresses'), 'ipconfigurationId', format('{0}/ipConfigurations/vNetGatewayConfig2', resourceId('Microsoft.Network/virtualNetworkGateways', parameters('name')))))), createObject('asn', coalesce(tryGet(parameters('clusterSettings'), 'asn'), 65515), 'bgpPeeringAddresses', createArray(createObject('customBgpIpAddresses', tryGet(parameters('clusterSettings'), 'customBgpIpAddresses'), 'ipconfigurationId', format('{0}/ipConfigurations/vNetGatewayConfig1', resourceId('Microsoft.Network/virtualNetworkGateways', parameters('name')))))))]", + "ipConfiguration": "[if(variables('isActiveActive'), createArray(createObject('properties', createObject('privateIPAllocationMethod', 'Dynamic', 'subnet', createObject('id', format('{0}/subnets/GatewaySubnet', parameters('vNetResourceId'))), 'publicIPAddress', createObject('id', resourceId('Microsoft.Network/publicIPAddresses', parameters('gatewayPipName')))), 'name', 'vNetGatewayConfig1'), createObject('properties', createObject('privateIPAllocationMethod', 'Dynamic', 'subnet', createObject('id', format('{0}/subnets/GatewaySubnet', parameters('vNetResourceId'))), 'publicIPAddress', createObject('id', if(variables('isActiveActive'), resourceId('Microsoft.Network/publicIPAddresses', variables('activeGatewayPipNameVar')), resourceId('Microsoft.Network/publicIPAddresses', parameters('gatewayPipName'))))), 'name', 'vNetGatewayConfig2')), createArray(createObject('properties', createObject('privateIPAllocationMethod', 'Dynamic', 'subnet', createObject('id', format('{0}/subnets/GatewaySubnet', parameters('vNetResourceId'))), 'publicIPAddress', createObject('id', resourceId('Microsoft.Network/publicIPAddresses', parameters('gatewayPipName')))), 'name', 'vNetGatewayConfig1')))]", "vpnClientConfiguration": "[if(not(empty(parameters('clientRootCertData'))), createObject('vpnClientAddressPool', createObject('addressPrefixes', createArray(parameters('vpnClientAddressPoolPrefix'))), 'vpnClientRootCertificates', createArray(createObject('name', 'RootCert1', 'properties', createObject('publicCertData', parameters('clientRootCertData')))), 'vpnClientRevokedCertificates', if(not(empty(parameters('clientRevokedCertThumbprint'))), createArray(createObject('name', 'RevokedCert1', 'properties', createObject('thumbprint', parameters('clientRevokedCertThumbprint')))), null())), if(not(empty(parameters('vpnClientAadConfiguration'))), createObject('vpnClientAddressPool', createObject('addressPrefixes', createArray(parameters('vpnClientAddressPoolPrefix'))), 'aadTenant', parameters('vpnClientAadConfiguration').aadTenant, 'aadAudience', parameters('vpnClientAadConfiguration').aadAudience, 'aadIssuer', parameters('vpnClientAadConfiguration').aadIssuer, 'vpnAuthenticationTypes', parameters('vpnClientAadConfiguration').vpnAuthenticationTypes, 'vpnClientProtocols', parameters('vpnClientAadConfiguration').vpnClientProtocols), null()))]", "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", @@ -545,13 +649,13 @@ "tags": "[parameters('tags')]", "properties": { "ipConfigurations": "[variables('ipConfiguration')]", - "activeActive": "[variables('isActiveActiveValid')]", + "activeActive": "[variables('isActiveActive')]", "allowRemoteVnetTraffic": "[parameters('allowRemoteVnetTraffic')]", "allowVirtualWanTraffic": "[parameters('allowVirtualWanTraffic')]", - "enableBgp": "[variables('isBgpValid')]", - "bgpSettings": "[if(variables('isBgpValid'), variables('bgpSettings'), null())]", + "enableBgp": "[variables('isBgp')]", + "bgpSettings": "[if(variables('isBgp'), variables('bgpSettingsVar'), null())]", "disableIPSecReplayProtection": "[parameters('disableIPSecReplayProtection')]", - "enableDnsForwarding": "[if(equals(parameters('gatewayType'), 'ExpressRoute'), parameters('enableDnsForwarding'), null())]", + "enableDnsForwarding": "[if(not(variables('isExpressRoute')), parameters('enableDnsForwarding'), null())]", "enablePrivateIpAddress": "[parameters('enablePrivateIpAddress')]", "enableBgpRouteTranslationForNat": "[parameters('enableBgpRouteTranslationForNat')]", "gatewayType": "[parameters('gatewayType')]", @@ -1329,11 +1433,21 @@ "virtualNetworkGatewayName": { "value": "[parameters('name')]" }, - "externalMappings": "[if(contains(parameters('natRules')[copyIndex()], 'externalMappings'), createObject('value', parameters('natRules')[copyIndex()].externalMappings), createObject('value', createArray()))]", - "internalMappings": "[if(contains(parameters('natRules')[copyIndex()], 'internalMappings'), createObject('value', parameters('natRules')[copyIndex()].internalMappings), createObject('value', createArray()))]", - "ipConfigurationId": "[if(contains(parameters('natRules')[copyIndex()], 'ipConfigurationId'), createObject('value', parameters('natRules')[copyIndex()].ipConfigurationId), createObject('value', ''))]", - "mode": "[if(contains(parameters('natRules')[copyIndex()], 'mode'), createObject('value', parameters('natRules')[copyIndex()].mode), createObject('value', ''))]", - "type": "[if(contains(parameters('natRules')[copyIndex()], 'type'), createObject('value', parameters('natRules')[copyIndex()].type), createObject('value', ''))]" + "externalMappings": { + "value": "[coalesce(tryGet(parameters('natRules')[copyIndex()], 'externalMappings'), createArray())]" + }, + "internalMappings": { + "value": "[coalesce(tryGet(parameters('natRules')[copyIndex()], 'internalMappings'), createArray())]" + }, + "ipConfigurationId": { + "value": "[coalesce(tryGet(parameters('natRules')[copyIndex()], 'ipConfigurationId'), '')]" + }, + "mode": { + "value": "[coalesce(tryGet(parameters('natRules')[copyIndex()], 'mode'), '')]" + }, + "type": { + "value": "[coalesce(tryGet(parameters('natRules')[copyIndex()], 'type'), '')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/aadvpn/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/aadvpn/main.test.bicep index 0b4425a9a05..09457de2bd4 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/aadvpn/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/aadvpn/main.test.bicep @@ -15,7 +15,7 @@ param resourceGroupName string = 'dep-${namePrefix}-network.virtualnetworkgatewa param resourceLocation string = deployment().location @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -param serviceShort string = 'nvngavpn' +param serviceShort string = 'nvgavpn' @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' @@ -55,7 +55,9 @@ module testDeployment '../../../main.bicep' = [ skuName: 'VpnGw2AZ' gatewayType: 'Vpn' vNetResourceId: nestedDependencies.outputs.vnetResourceId - activeActive: false + clusterSettings:{ + clusterMode: 'activePassiveNoBgp' + } domainNameLabel: [ '${namePrefix}-dm-${serviceShort}' ] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/dependencies.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/dependencies.bicep new file mode 100644 index 00000000000..c3aebf111c4 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/dependencies.bicep @@ -0,0 +1,49 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Local Network Gateway to create.') +param localNetworkGatewayName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'GatewaySubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-04-01' = { + name: localNetworkGatewayName + location: location + properties: { + gatewayIpAddress: '100.100.100.100' + localNetworkAddressSpace: { + addressPrefixes: [ + '192.168.0.0/24' + ] + } + } +} + +@description('The resource ID of the created Virtual Network.') +output vnetResourceId string = virtualNetwork.id + +@description('The resource ID of the created Local Network Gateway.') +output localNetworkGatewayResourceId string = localNetworkGateway.id diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/main.test.bicep new file mode 100644 index 00000000000..6270baf5985 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBGP/main.test.bicep @@ -0,0 +1,83 @@ +targetScope = 'subscription' + +metadata name = 'VPN Active Active with BGP settings' +metadata description = 'This instance deploys the module with the VPN Active Active with BGP settings.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.virtualnetworkgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nvgaab' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + localNetworkGatewayName: 'dep-${namePrefix}-lng-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + vpnGatewayGeneration: 'Generation2' + skuName: 'VpnGw2AZ' + gatewayType: 'Vpn' + vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings: { + clusterMode: 'activeActiveBgp' + } + + domainNameLabel: [ + '${namePrefix}-dm-${serviceShort}' + ] + publicIpZones: [ + 1 + 2 + 3 + ] + vpnType: 'RouteBased' + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: nestedDependencies.outputs.localNetworkGatewayResourceId + disableIPSecReplayProtection: true + allowRemoteVnetTraffic: true + enableBgpRouteTranslationForNat: true + } + dependsOn: [ + nestedDependencies + ] + } +] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/dependencies.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/dependencies.bicep new file mode 100644 index 00000000000..c3aebf111c4 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/dependencies.bicep @@ -0,0 +1,49 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Local Network Gateway to create.') +param localNetworkGatewayName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'GatewaySubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-04-01' = { + name: localNetworkGatewayName + location: location + properties: { + gatewayIpAddress: '100.100.100.100' + localNetworkAddressSpace: { + addressPrefixes: [ + '192.168.0.0/24' + ] + } + } +} + +@description('The resource ID of the created Virtual Network.') +output vnetResourceId string = virtualNetwork.id + +@description('The resource ID of the created Local Network Gateway.') +output localNetworkGatewayResourceId string = localNetworkGateway.id diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/main.test.bicep new file mode 100644 index 00000000000..16fe844184e --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveBgpAPIPA/main.test.bicep @@ -0,0 +1,85 @@ +targetScope = 'subscription' + +metadata name = 'VPN Active Active with BGP settings' +metadata description = 'This instance deploys the module with the VPN Active Active with APIPA BGP settings.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.virtualnetworkgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nvgaaa' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + localNetworkGatewayName: 'dep-${namePrefix}-lng-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + vpnGatewayGeneration: 'Generation2' + skuName: 'VpnGw2AZ' + gatewayType: 'Vpn' + vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings: { + clusterMode: 'activeActiveBgp' + customBgpIpAddresses: ['169.254.21.4','169.254.21.5'] + secondCustomBgpIpAddresses: ['169.254.22.4','169.254.22.5'] + } + + domainNameLabel: [ + '${namePrefix}-dm-${serviceShort}' + ] + publicIpZones: [ + 1 + 2 + 3 + ] + vpnType: 'RouteBased' + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: nestedDependencies.outputs.localNetworkGatewayResourceId + disableIPSecReplayProtection: true + allowRemoteVnetTraffic: true + enableBgpRouteTranslationForNat: true + } + dependsOn: [ + nestedDependencies + ] + } +] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/dependencies.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/dependencies.bicep new file mode 100644 index 00000000000..c3aebf111c4 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/dependencies.bicep @@ -0,0 +1,49 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Local Network Gateway to create.') +param localNetworkGatewayName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'GatewaySubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-04-01' = { + name: localNetworkGatewayName + location: location + properties: { + gatewayIpAddress: '100.100.100.100' + localNetworkAddressSpace: { + addressPrefixes: [ + '192.168.0.0/24' + ] + } + } +} + +@description('The resource ID of the created Virtual Network.') +output vnetResourceId string = virtualNetwork.id + +@description('The resource ID of the created Local Network Gateway.') +output localNetworkGatewayResourceId string = localNetworkGateway.id diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/main.test.bicep new file mode 100644 index 00000000000..7d799c5c2cb --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activeActiveNoBGP/main.test.bicep @@ -0,0 +1,83 @@ +targetScope = 'subscription' + +metadata name = 'VPN Active Active without BGP settings' +metadata description = 'This instance deploys the module with the VPN Active Active without BGP settings.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.virtualnetworkgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nvgaa' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + localNetworkGatewayName: 'dep-${namePrefix}-lng-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + vpnGatewayGeneration: 'Generation2' + skuName: 'VpnGw2AZ' + gatewayType: 'Vpn' + vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings: { + clusterMode: 'activeActiveNoBgp' + } + + domainNameLabel: [ + '${namePrefix}-dm-${serviceShort}' + ] + publicIpZones: [ + 1 + 2 + 3 + ] + vpnType: 'RouteBased' + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: nestedDependencies.outputs.localNetworkGatewayResourceId + disableIPSecReplayProtection: true + allowRemoteVnetTraffic: true + enableBgpRouteTranslationForNat: true + } + dependsOn: [ + nestedDependencies + ] + } +] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/dependencies.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/dependencies.bicep new file mode 100644 index 00000000000..c3aebf111c4 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/dependencies.bicep @@ -0,0 +1,49 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Local Network Gateway to create.') +param localNetworkGatewayName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'GatewaySubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-04-01' = { + name: localNetworkGatewayName + location: location + properties: { + gatewayIpAddress: '100.100.100.100' + localNetworkAddressSpace: { + addressPrefixes: [ + '192.168.0.0/24' + ] + } + } +} + +@description('The resource ID of the created Virtual Network.') +output vnetResourceId string = virtualNetwork.id + +@description('The resource ID of the created Local Network Gateway.') +output localNetworkGatewayResourceId string = localNetworkGateway.id diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/main.test.bicep new file mode 100644 index 00000000000..1aa9da7c341 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveBGP/main.test.bicep @@ -0,0 +1,85 @@ +targetScope = 'subscription' + +metadata name = 'VPN Active Passive with BGP settings' +metadata description = 'This instance deploys the module with the VPN Active Passive with APIPA BGP settings.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.virtualnetworkgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nvgapb' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + localNetworkGatewayName: 'dep-${namePrefix}-lng-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + vpnGatewayGeneration: 'Generation2' + skuName: 'VpnGw2AZ' + gatewayType: 'Vpn' + vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings: { + clusterMode:'activePassiveBgp' + customBgpIpAddresses: ['169.254.21.4','169.254.21.5'] + asn: 65815 + } + + domainNameLabel: [ + '${namePrefix}-dm-${serviceShort}' + ] + publicIpZones: [ + 1 + 2 + 3 + ] + vpnType: 'RouteBased' + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: nestedDependencies.outputs.localNetworkGatewayResourceId + disableIPSecReplayProtection: true + allowRemoteVnetTraffic: true + enableBgpRouteTranslationForNat: true + } + dependsOn: [ + nestedDependencies + ] + } +] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/dependencies.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/dependencies.bicep new file mode 100644 index 00000000000..c3aebf111c4 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/dependencies.bicep @@ -0,0 +1,49 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Virtual Network to create.') +param virtualNetworkName string + +@description('Required. The name of the Local Network Gateway to create.') +param localNetworkGatewayName string + +var addressPrefix = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-04-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addressPrefix + ] + } + subnets: [ + { + name: 'GatewaySubnet' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 16, 0) + } + } + ] + } +} + +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-04-01' = { + name: localNetworkGatewayName + location: location + properties: { + gatewayIpAddress: '100.100.100.100' + localNetworkAddressSpace: { + addressPrefixes: [ + '192.168.0.0/24' + ] + } + } +} + +@description('The resource ID of the created Virtual Network.') +output vnetResourceId string = virtualNetwork.id + +@description('The resource ID of the created Local Network Gateway.') +output localNetworkGatewayResourceId string = localNetworkGateway.id diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/main.test.bicep new file mode 100644 index 00000000000..ee3904f4b81 --- /dev/null +++ b/avm/res/network/virtual-network-gateway/tests/e2e/activePassiveNoBGP/main.test.bicep @@ -0,0 +1,83 @@ +targetScope = 'subscription' + +metadata name = 'VPN Active Passive without BGP settings' +metadata description = 'This instance deploys the module with the VPN Active Passive without BGP settings.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.virtualnetworkgateways-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nvgap' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + localNetworkGatewayName: 'dep-${namePrefix}-lng-${serviceShort}' + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + vpnGatewayGeneration: 'Generation2' + skuName: 'VpnGw2AZ' + gatewayType: 'Vpn' + vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings: { + clusterMode:'activePassiveNoBgp' + } + + domainNameLabel: [ + '${namePrefix}-dm-${serviceShort}' + ] + publicIpZones: [ + 1 + 2 + 3 + ] + vpnType: 'RouteBased' + enablePrivateIpAddress: true + gatewayDefaultSiteLocalNetworkGatewayId: nestedDependencies.outputs.localNetworkGatewayResourceId + disableIPSecReplayProtection: true + allowRemoteVnetTraffic: true + enableBgpRouteTranslationForNat: true + } + dependsOn: [ + nestedDependencies + ] + } +] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/defaults/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/defaults/main.test.bicep index 9e5f80b30ca..06f382ecb5d 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/defaults/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/defaults/main.test.bicep @@ -62,6 +62,9 @@ module testDeployment '../../../main.bicep' = [ 2 3 ] + clusterSettings: { + clusterMode:'activeActiveNoBgp' + } } dependsOn: [ nestedDependencies diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/expressRoute/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/expressRoute/main.test.bicep index 22b116891bb..600e3740258 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/expressRoute/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/expressRoute/main.test.bicep @@ -55,6 +55,9 @@ module testDeployment '../../../main.bicep' = [ skuName: 'ErGw1AZ' gatewayType: 'ExpressRoute' vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings:{ + clusterMode: 'activePassiveBgp' + } domainNameLabel: [ '${namePrefix}-dm-${serviceShort}' ] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/max/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/max/main.test.bicep index 346b3806105..b0314fa4dc5 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/max/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/max/main.test.bicep @@ -72,7 +72,12 @@ module testDeployment '../../../main.bicep' = [ skuName: 'VpnGw2AZ' gatewayType: 'Vpn' vNetResourceId: nestedDependencies.outputs.vnetResourceId - activeActive: true + clusterSettings:{ + clusterMode: 'activeActiveBgp' + activeGatewayPipName: '${namePrefix}${serviceShort}001-pip2' + customBgpIpAddresses: ['169.254.21.4','169.254.21.5'] + secondCustomBgpIpAddresses: ['169.254.22.4','169.254.22.5'] + } diagnosticSettings: [ { name: 'customSetting' diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep index 7966ff4a3d1..514b8429ab6 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep @@ -57,6 +57,9 @@ module testDeployment '../../../main.bicep' = [ skuName: 'VpnGw1' gatewayType: 'Vpn' vNetResourceId: nestedDependencies.outputs.vnetResourceId + clusterSettings:{ + clusterMode: 'activePassiveNoBgp' + } } dependsOn: [ nestedDependencies diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/vpn/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/vpn/main.test.bicep index 6ac66acfd18..4258c36c8a8 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/vpn/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/vpn/main.test.bicep @@ -57,7 +57,9 @@ module testDeployment '../../../main.bicep' = [ skuName: 'VpnGw2AZ' gatewayType: 'Vpn' vNetResourceId: nestedDependencies.outputs.vnetResourceId - activeActive: true + clusterSettings:{ + clusterMode: 'activeActiveNoBgp' + } domainNameLabel: [ '${namePrefix}-dm-${serviceShort}' ] diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/waf-aligned/main.test.bicep index 8a31ac21fce..e0da5f11c58 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/waf-aligned/main.test.bicep @@ -70,7 +70,12 @@ module testDeployment '../../../main.bicep' = [ skuName: 'VpnGw2AZ' gatewayType: 'Vpn' vNetResourceId: nestedDependencies.outputs.vnetResourceId - activeActive: true + clusterSettings: { + clusterMode:'activeActiveBgp' + customBgpIpAddresses: ['169.254.21.4','169.254.21.5'] + secondCustomBgpIpAddresses: ['169.254.22.4','169.254.22.5'] + asn: 65515 + } diagnosticSettings: [ { name: 'customSetting' diff --git a/avm/res/network/virtual-network-gateway/version.json b/avm/res/network/virtual-network-gateway/version.json index 76049e1c4ad..13669e66018 100644 --- a/avm/res/network/virtual-network-gateway/version.json +++ b/avm/res/network/virtual-network-gateway/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.3", + "version": "0.4", "pathFilters": [ "./main.json" ] From f1d14ed950ed805b89fc2aeed75779c133a3ae31 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 20 Sep 2024 01:26:11 +0200 Subject: [PATCH 25/46] fix: static validation error and BCP error in `avm.res.dev-ops-infrastructure.pool` (#3333) ## Description This pull request fixes the following: * The static validation is now passing, fixed by rerunning the Set-AVMModule command * BCP error regarding a property/parameter that cannot be null - fixed this by making it required Tagging @matebarabas @AlexanderSehr ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.dev-ops-infrastructure.pool](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.dev-ops-infrastructure.pool.yml/badge.svg?branch=johnlokerse%2Ffix-staticvalidation-mdp)](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.dev-ops-infrastructure.pool.yml) | ## Type of Change - [x] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- avm/res/dev-ops-infrastructure/pool/README.md | 41 +++++++++++-------- .../dev-ops-infrastructure/pool/main.bicep | 4 +- avm/res/dev-ops-infrastructure/pool/main.json | 7 ++-- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/avm/res/dev-ops-infrastructure/pool/README.md b/avm/res/dev-ops-infrastructure/pool/README.md index 4cd888f4b44..4ea945d1ab5 100644 --- a/avm/res/dev-ops-infrastructure/pool/README.md +++ b/avm/res/dev-ops-infrastructure/pool/README.md @@ -8,7 +8,7 @@ This module deploys the Managed DevOps Pool resource. - [Usage examples](#Usage-examples) - [Parameters](#Parameters) - [Outputs](#Outputs) -- [Cross-referenced modules](#Cross-referenced-modules) +- [Notes](#Notes) - [Data Collection](#Data-Collection) ## Resource Types @@ -20,10 +20,6 @@ This module deploys the Managed DevOps Pool resource. | `Microsoft.DevOpsInfrastructure/pools` | [2024-04-04-preview](https://learn.microsoft.com/en-us/azure/templates) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -## Notes - -The Managed DevOps Pool resource requires external permissions in Azure DevOps. Make sure that the deployment principal has permission in Azure DevOps: [Managed DevOps Pools - Verify Azure DevOps Permissions](https://learn.microsoft.com/en-us/azure/devops/managed-devops-pools/prerequisites?view=azure-devops&tabs=azure-portal#verify-azure-devops-permissions) - ## Usage examples The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. @@ -544,7 +540,6 @@ module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = {

- ## Parameters **Required parameters** @@ -739,26 +734,24 @@ The type of permission which determines which accounts are admins on the Azure D - Required: No - Type: object -**Optional parameters** +**Required parameters** | Parameter | Type | Description | | :-- | :-- | :-- | -| [`groups`](#parameter-organizationprofilepermissionprofilegroups) | array | Group email addresses. | | [`kind`](#parameter-organizationprofilepermissionprofilekind) | string | Determines who has admin permissions to the Azure DevOps pool. | -| [`users`](#parameter-organizationprofilepermissionprofileusers) | array | User email addresses. | - -### Parameter: `organizationProfile.permissionProfile.groups` -Group email addresses. +**Optional parameters** -- Required: No -- Type: array +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`groups`](#parameter-organizationprofilepermissionprofilegroups) | array | Group email addresses. | +| [`users`](#parameter-organizationprofilepermissionprofileusers) | array | User email addresses. | ### Parameter: `organizationProfile.permissionProfile.kind` Determines who has admin permissions to the Azure DevOps pool. -- Required: No +- Required: Yes - Type: string - Allowed: ```Bicep @@ -769,6 +762,13 @@ Determines who has admin permissions to the Azure DevOps pool. ] ``` +### Parameter: `organizationProfile.permissionProfile.groups` + +Group email addresses. + +- Required: No +- Type: array + ### Parameter: `organizationProfile.permissionProfile.users` User email addresses. @@ -1104,6 +1104,12 @@ Array of role assignments to create. - Required: No - Type: array +- Roles configurable by name: + - `'Contributor'` + - `'Owner'` + - `'Reader'` + - `'Role Based Access Control Administrator (Preview)'` + - `'User Access Administrator'` **Required parameters** @@ -1300,7 +1306,6 @@ Tags of the resource. - Required: No - Type: object - ## Outputs | Output | Type | Description | @@ -1311,9 +1316,9 @@ Tags of the resource. | `resourceId` | string | The resource ID of the Managed DevOps Pool. | | `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | -## Cross-referenced modules +## Notes -_None_ +The Managed DevOps Pool resource requires external permissions in Azure DevOps. Make sure that the deployment principal has permission in Azure DevOps: [Managed DevOps Pools - Verify Azure DevOps Permissions](https://learn.microsoft.com/en-us/azure/devops/managed-devops-pools/prerequisites?view=azure-devops&tabs=azure-portal#verify-azure-devops-permissions) ## Data Collection diff --git a/avm/res/dev-ops-infrastructure/pool/main.bicep b/avm/res/dev-ops-infrastructure/pool/main.bicep index c64d2bbce53..af61e650fe7 100644 --- a/avm/res/dev-ops-infrastructure/pool/main.bicep +++ b/avm/res/dev-ops-infrastructure/pool/main.bicep @@ -292,8 +292,8 @@ type organizationProfileType = { @description('Optional. The type of permission which determines which accounts are admins on the Azure DevOps pool.') permissionProfile: { - @description('Optional. Determines who has admin permissions to the Azure DevOps pool.') - kind: ('CreatorOnly' | 'Inherit' | 'SpecificAccounts')? + @description('Required. Determines who has admin permissions to the Azure DevOps pool.') + kind: 'CreatorOnly' | 'Inherit' | 'SpecificAccounts' @description('Optional. Group email addresses.') groups: string[]? diff --git a/avm/res/dev-ops-infrastructure/pool/main.json b/avm/res/dev-ops-infrastructure/pool/main.json index 38edaf771a1..ce80f4d180f 100644 --- a/avm/res/dev-ops-infrastructure/pool/main.json +++ b/avm/res/dev-ops-infrastructure/pool/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9138966756251042726" + "version": "0.30.3.12046", + "templateHash": "15191897376801297199" }, "name": "Managed DevOps Pool", "description": "This module deploys the Managed DevOps Pool resource.", @@ -189,9 +189,8 @@ "Inherit", "SpecificAccounts" ], - "nullable": true, "metadata": { - "description": "Optional. Determines who has admin permissions to the Azure DevOps pool." + "description": "Required. Determines who has admin permissions to the Azure DevOps pool." } }, "groups": { From 07a059307535995bdf22a385dfebf224d3ce34ea Mon Sep 17 00:00:00 2001 From: Peter Budai Date: Fri, 20 Sep 2024 09:35:15 +0200 Subject: [PATCH 26/46] feat: Retain existing settings during deployment - `avm/res/web/site` (#3311) ## Description This commit changes the app settings deployment in order to retain existing app settings that are not defined in the Bicep file. This change allows for updating **only** the app settings that are defined in the Bicep file, while leaving the rest unchanged. As this is a change in behavior, version number has been increased. Fixes #949 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.web.site](https://github.com/peterbud/bicep-registry-modules/actions/workflows/avm.res.web.site.yml/badge.svg)](https://github.com/peterbud/bicep-registry-modules/actions/workflows/avm.res.web.site.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [x] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/web/site/README.md | 108 +++++++++++++++--- .../web/site/config--appsettings/README.md | 11 +- .../web/site/config--appsettings/main.bicep | 16 ++- .../web/site/config--appsettings/main.json | 17 ++- avm/res/web/site/main.bicep | 3 +- avm/res/web/site/main.json | 44 ++++--- .../site/slot/config--appsettings/README.md | 9 ++ .../site/slot/config--appsettings/main.bicep | 10 +- .../site/slot/config--appsettings/main.json | 11 +- avm/res/web/site/slot/main.bicep | 1 + avm/res/web/site/slot/main.json | 16 ++- .../functionApp.settings/dependencies.bicep | 21 ++++ .../e2e/functionApp.settings/main.test.bicep | 67 +++++++++++ avm/res/web/site/version.json | 2 +- 14 files changed, 284 insertions(+), 52 deletions(-) create mode 100644 avm/res/web/site/tests/e2e/functionApp.settings/dependencies.bicep create mode 100644 avm/res/web/site/tests/e2e/functionApp.settings/main.test.bicep diff --git a/avm/res/web/site/README.md b/avm/res/web/site/README.md index bc9c390cb6a..12c8a2371f8 100644 --- a/avm/res/web/site/README.md +++ b/avm/res/web/site/README.md @@ -21,8 +21,9 @@ This module deploys a Web or Function App. | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | | `Microsoft.Network/privateEndpoints` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints/privateDnsZoneGroups) | -| `Microsoft.Web/sites` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-09-01/sites) | +| `Microsoft.Web/sites` | [2023-12-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | | `Microsoft.Web/sites/basicPublishingCredentialsPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | +| `Microsoft.Web/sites/config` | [2023-12-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | | `Microsoft.Web/sites/config` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | | `Microsoft.Web/sites/extensions` | [2023-12-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites/extensions) | | `Microsoft.Web/sites/hybridConnectionNamespaces/relays` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/2022-09-01/sites/hybridConnectionNamespaces/relays) | @@ -41,15 +42,16 @@ The following section provides usage examples for the module, which were used to - [Function App, using only defaults](#example-1-function-app-using-only-defaults) - [Function App, using large parameter set](#example-2-function-app-using-large-parameter-set) -- [Web App, using only defaults](#example-3-web-app-using-only-defaults) -- [Web App](#example-4-web-app) -- [WAF-aligned](#example-5-waf-aligned) -- [Web App, using only defaults](#example-6-web-app-using-only-defaults) -- [Web App, using large parameter set](#example-7-web-app-using-large-parameter-set) -- [Web App, using only defaults](#example-8-web-app-using-only-defaults) -- [Web App, using large parameter set](#example-9-web-app-using-large-parameter-set) -- [Web App](#example-10-web-app) -- [Windows Web App for Containers, using only defaults](#example-11-windows-web-app-for-containers-using-only-defaults) +- [Function App, using only defaults](#example-3-function-app-using-only-defaults) +- [Web App, using only defaults](#example-4-web-app-using-only-defaults) +- [Web App](#example-5-web-app) +- [WAF-aligned](#example-6-waf-aligned) +- [Web App, using only defaults](#example-7-web-app-using-only-defaults) +- [Web App, using large parameter set](#example-8-web-app-using-large-parameter-set) +- [Web App, using only defaults](#example-9-web-app-using-only-defaults) +- [Web App, using large parameter set](#example-10-web-app-using-large-parameter-set) +- [Web App](#example-11-web-app) +- [Windows Web App for Containers, using only defaults](#example-12-windows-web-app-for-containers-using-only-defaults) ### Example 1: _Function App, using only defaults_ @@ -515,7 +517,75 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 3: _Web App, using only defaults_ +### Example 3: _Function App, using only defaults_ + +This instance deploys the module as Function App with the minimum set of required parameters. + + +

+ +via Bicep module + +```bicep +module site 'br/public:avm/res/web/site:' = { + name: 'siteDeployment' + params: { + // Required parameters + kind: 'functionapp' + name: 'wsfaset001' + serverFarmResourceId: '' + // Non-required parameters + appSettingsKeyValuePairs: { + AzureFunctionsJobHost__logging__logLevel__default: 'Trace' + FUNCTIONS_EXTENSION_VERSION: '~4' + FUNCTIONS_WORKER_RUNTIME: 'dotnet' + } + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "kind": { + "value": "functionapp" + }, + "name": { + "value": "wsfaset001" + }, + "serverFarmResourceId": { + "value": "" + }, + // Non-required parameters + "appSettingsKeyValuePairs": { + "value": { + "AzureFunctionsJobHost__logging__logLevel__default": "Trace", + "FUNCTIONS_EXTENSION_VERSION": "~4", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + } + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 4: _Web App, using only defaults_ This instance deploys the module as a Linux Web App with the minimum set of required parameters. @@ -591,7 +661,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 4: _Web App_ +### Example 5: _Web App_ This instance deploys the module as Web App with the set of logs configuration. @@ -723,7 +793,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 5: _WAF-aligned_ +### Example 6: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. @@ -865,7 +935,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 6: _Web App, using only defaults_ +### Example 7: _Web App, using only defaults_ This instance deploys the module as Web App with the minimum set of required parameters. @@ -921,7 +991,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 7: _Web App, using large parameter set_ +### Example 8: _Web App, using large parameter set_ This instance deploys the module as Web App with most of its features enabled. @@ -1389,7 +1459,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 8: _Web App, using only defaults_ +### Example 9: _Web App, using only defaults_ This instance deploys the module as a Linux Web App with the minimum set of required parameters. @@ -1445,7 +1515,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 9: _Web App, using large parameter set_ +### Example 10: _Web App, using large parameter set_ This instance deploys the module asa Linux Web App with most of its features enabled. @@ -1907,7 +1977,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 10: _Web App_ +### Example 11: _Web App_ This instance deploys the module as Web App with the set of api management configuration. @@ -2003,7 +2073,7 @@ module site 'br/public:avm/res/web/site:' = {

-### Example 11: _Windows Web App for Containers, using only defaults_ +### Example 12: _Windows Web App for Containers, using only defaults_ This instance deploys the module as a Windows based Container Web App with the minimum set of required parameters. diff --git a/avm/res/web/site/config--appsettings/README.md b/avm/res/web/site/config--appsettings/README.md index 17ff4622369..db4a05b7d74 100644 --- a/avm/res/web/site/config--appsettings/README.md +++ b/avm/res/web/site/config--appsettings/README.md @@ -13,7 +13,7 @@ This module deploys a Site App Setting. | Resource Type | API Version | | :-- | :-- | -| `Microsoft.Web/sites/config` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | +| `Microsoft.Web/sites/config` | [2023-12-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Web/sites) | ## Parameters @@ -35,6 +35,7 @@ This module deploys a Site App Setting. | :-- | :-- | :-- | | [`appInsightResourceId`](#parameter-appinsightresourceid) | string | Resource ID of the app insight to leverage for this resource. | | [`appSettingsKeyValuePairs`](#parameter-appsettingskeyvaluepairs) | object | The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING. | +| [`currentAppSettings`](#parameter-currentappsettings) | object | The current app settings. | | [`storageAccountResourceId`](#parameter-storageaccountresourceid) | string | Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. | | [`storageAccountUseIdentityAuthentication`](#parameter-storageaccountuseidentityauthentication) | bool | If the provided storage account requires Identity based authentication ('allowSharedKeyAccess' is set to false). When set to true, the minimum role assignment required for the App Service Managed Identity to the storage account is 'Storage Blob Data Owner'. | @@ -83,6 +84,14 @@ The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDas - Required: No - Type: object +### Parameter: `currentAppSettings` + +The current app settings. + +- Required: No +- Type: object +- Default: `{}` + ### Parameter: `storageAccountResourceId` Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. diff --git a/avm/res/web/site/config--appsettings/main.bicep b/avm/res/web/site/config--appsettings/main.bicep index 2f770a77742..202a2ac2054 100644 --- a/avm/res/web/site/config--appsettings/main.bicep +++ b/avm/res/web/site/config--appsettings/main.bicep @@ -34,6 +34,9 @@ param appInsightResourceId string? @description('Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING.') param appSettingsKeyValuePairs object? +@description('Optional. The current app settings.') +param currentAppSettings object = {} + var azureWebJobsValues = !empty(storageAccountResourceId) && !(storageAccountUseIdentityAuthentication) ? { AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' @@ -51,9 +54,14 @@ var appInsightsValues = !empty(appInsightResourceId) } : {} -var expandedAppSettings = union(appSettingsKeyValuePairs ?? {}, azureWebJobsValues, appInsightsValues) +var expandedAppSettings = union( + currentAppSettings ?? {}, + appSettingsKeyValuePairs ?? {}, + azureWebJobsValues, + appInsightsValues +) -resource app 'Microsoft.Web/sites@2022-09-01' existing = { +resource app 'Microsoft.Web/sites@2023-12-01' existing = { name: appName } @@ -62,7 +70,7 @@ resource appInsight 'Microsoft.Insights/components@2020-02-02' existing = if (!e scope: resourceGroup(split(appInsightResourceId ?? '//', '/')[2], split(appInsightResourceId ?? '////', '/')[4]) } -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (!empty(storageAccountResourceId)) { +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = if (!empty(storageAccountResourceId)) { name: last(split(storageAccountResourceId ?? 'dummyName', '/')) scope: resourceGroup( split(storageAccountResourceId ?? '//', '/')[2], @@ -70,7 +78,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing ) } -resource appSettings 'Microsoft.Web/sites/config@2022-09-01' = { +resource appSettings 'Microsoft.Web/sites/config@2023-12-01' = { name: 'appsettings' kind: kind parent: app diff --git a/avm/res/web/site/config--appsettings/main.json b/avm/res/web/site/config--appsettings/main.json index c59a554e2ae..c50105f5013 100644 --- a/avm/res/web/site/config--appsettings/main.json +++ b/avm/res/web/site/config--appsettings/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "8777070640548664577" + "templateHash": "3998275265127709875" }, "name": "Site App Settings", "description": "This module deploys a Site App Setting.", @@ -66,13 +66,20 @@ "metadata": { "description": "Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING." } + }, + "currentAppSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The current app settings." + } } }, "resources": { "app": { "existing": true, "type": "Microsoft.Web/sites", - "apiVersion": "2022-09-01", + "apiVersion": "2023-12-01", "name": "[parameters('appName')]" }, "appInsight": { @@ -88,17 +95,17 @@ "condition": "[not(empty(parameters('storageAccountResourceId')))]", "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2023-01-01", + "apiVersion": "2023-05-01", "subscriptionId": "[split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2]]", "resourceGroup": "[split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]]", "name": "[last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))]" }, "appSettings": { "type": "Microsoft.Web/sites/config", - "apiVersion": "2022-09-01", + "apiVersion": "2023-12-01", "name": "[format('{0}/{1}', parameters('appName'), 'appsettings')]", "kind": "[parameters('kind')]", - "properties": "[union(coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", + "properties": "[union(coalesce(parameters('currentAppSettings'), createObject()), coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-05-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", "dependsOn": [ "app", "appInsight", diff --git a/avm/res/web/site/main.bicep b/avm/res/web/site/main.bicep index b7418015b6f..cbd5824ec5a 100644 --- a/avm/res/web/site/main.bicep +++ b/avm/res/web/site/main.bicep @@ -245,7 +245,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } } -resource app 'Microsoft.Web/sites@2022-09-01' = { +resource app 'Microsoft.Web/sites@2023-12-01' = { name: name location: location kind: kind @@ -294,6 +294,7 @@ module app_appsettings 'config--appsettings/main.bicep' = if (!empty(appSettings storageAccountUseIdentityAuthentication: storageAccountUseIdentityAuthentication appInsightResourceId: appInsightResourceId appSettingsKeyValuePairs: appSettingsKeyValuePairs + currentAppSettings: !empty(app.id) ? list('${app.id}/config/appsettings', '2023-12-01').properties : {} } } diff --git a/avm/res/web/site/main.json b/avm/res/web/site/main.json index e6eccb36882..d3577e551f6 100644 --- a/avm/res/web/site/main.json +++ b/avm/res/web/site/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7320044434284742277" + "templateHash": "4626438010490721609" }, "name": "Web/Function Apps", "description": "This module deploys a Web or Function App.", @@ -860,7 +860,7 @@ }, "app": { "type": "Microsoft.Web/sites", - "apiVersion": "2022-09-01", + "apiVersion": "2023-12-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "kind": "[parameters('kind')]", @@ -998,7 +998,8 @@ }, "appSettingsKeyValuePairs": { "value": "[parameters('appSettingsKeyValuePairs')]" - } + }, + "currentAppSettings": "[if(not(empty(resourceId('Microsoft.Web/sites', parameters('name')))), createObject('value', list(format('{0}/config/appsettings', resourceId('Microsoft.Web/sites', parameters('name'))), '2023-12-01').properties), createObject('value', createObject()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -1008,7 +1009,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "8777070640548664577" + "templateHash": "3998275265127709875" }, "name": "Site App Settings", "description": "This module deploys a Site App Setting.", @@ -1068,13 +1069,20 @@ "metadata": { "description": "Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING." } + }, + "currentAppSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The current app settings." + } } }, "resources": { "app": { "existing": true, "type": "Microsoft.Web/sites", - "apiVersion": "2022-09-01", + "apiVersion": "2023-12-01", "name": "[parameters('appName')]" }, "appInsight": { @@ -1090,17 +1098,17 @@ "condition": "[not(empty(parameters('storageAccountResourceId')))]", "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2023-01-01", + "apiVersion": "2023-05-01", "subscriptionId": "[split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2]]", "resourceGroup": "[split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]]", "name": "[last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))]" }, "appSettings": { "type": "Microsoft.Web/sites/config", - "apiVersion": "2022-09-01", + "apiVersion": "2023-12-01", "name": "[format('{0}/{1}', parameters('appName'), 'appsettings')]", "kind": "[parameters('kind')]", - "properties": "[union(coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", + "properties": "[union(coalesce(parameters('currentAppSettings'), createObject()), coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-05-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", "dependsOn": [ "app", "appInsight", @@ -1677,7 +1685,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15729572124587777376" + "templateHash": "13282951347078727812" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -2639,7 +2647,8 @@ }, "appSettingsKeyValuePairs": { "value": "[parameters('appSettingsKeyValuePairs')]" - } + }, + "currentAppSettings": "[if(not(empty(resourceId('Microsoft.Web/sites/slots', parameters('appName'), parameters('name')))), createObject('value', list(format('{0}/config/appsettings', resourceId('Microsoft.Web/sites/slots', parameters('appName'), parameters('name'))), '2023-12-01').properties), createObject('value', createObject()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2649,7 +2658,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7111332561212908044" + "templateHash": "9363357518124041583" }, "name": "Site Slot App Settings", "description": "This module deploys a Site Slot App Setting.", @@ -2715,6 +2724,13 @@ "metadata": { "description": "Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING." } + }, + "currentAppSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The current app settings." + } } }, "resources": { @@ -2756,7 +2772,7 @@ "apiVersion": "2022-09-01", "name": "[format('{0}/{1}/{2}', parameters('appName'), parameters('slotName'), 'appsettings')]", "kind": "[parameters('kind')]", - "properties": "[union(coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", + "properties": "[union(coalesce(parameters('currentAppSettings'), createObject()), coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", "dependsOn": [ "appInsight", "app::slot", @@ -5111,7 +5127,7 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[coalesce(tryGet(tryGet(reference('app', '2022-09-01', 'full'), 'identity'), 'principalId'), '')]" + "value": "[coalesce(tryGet(tryGet(reference('app', '2023-12-01', 'full'), 'identity'), 'principalId'), '')]" }, "slotSystemAssignedMIPrincipalIds": { "type": "array", @@ -5128,7 +5144,7 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('app', '2022-09-01', 'full').location]" + "value": "[reference('app', '2023-12-01', 'full').location]" }, "defaultHostname": { "type": "string", diff --git a/avm/res/web/site/slot/config--appsettings/README.md b/avm/res/web/site/slot/config--appsettings/README.md index 6be3108fe18..a4eaf7b5e3d 100644 --- a/avm/res/web/site/slot/config--appsettings/README.md +++ b/avm/res/web/site/slot/config--appsettings/README.md @@ -36,6 +36,7 @@ This module deploys a Site Slot App Setting. | :-- | :-- | :-- | | [`appInsightResourceId`](#parameter-appinsightresourceid) | string | Resource ID of the app insight to leverage for this resource. | | [`appSettingsKeyValuePairs`](#parameter-appsettingskeyvaluepairs) | object | The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING. | +| [`currentAppSettings`](#parameter-currentappsettings) | object | The current app settings. | | [`storageAccountResourceId`](#parameter-storageaccountresourceid) | string | Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. | | [`storageAccountUseIdentityAuthentication`](#parameter-storageaccountuseidentityauthentication) | bool | If the provided storage account requires Identity based authentication ('allowSharedKeyAccess' is set to false). When set to true, the minimum role assignment required for the App Service Managed Identity to the storage account is 'Storage Blob Data Owner'. | @@ -91,6 +92,14 @@ The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDas - Required: No - Type: object +### Parameter: `currentAppSettings` + +The current app settings. + +- Required: No +- Type: object +- Default: `{}` + ### Parameter: `storageAccountResourceId` Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions. diff --git a/avm/res/web/site/slot/config--appsettings/main.bicep b/avm/res/web/site/slot/config--appsettings/main.bicep index 74095161a8c..2fbe7b81d41 100644 --- a/avm/res/web/site/slot/config--appsettings/main.bicep +++ b/avm/res/web/site/slot/config--appsettings/main.bicep @@ -37,6 +37,9 @@ param appInsightResourceId string? @description('Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING.') param appSettingsKeyValuePairs object? +@description('Optional. The current app settings.') +param currentAppSettings object = {} + var azureWebJobsValues = !empty(storageAccountResourceId) && !(storageAccountUseIdentityAuthentication) ? { AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' @@ -54,7 +57,12 @@ var appInsightsValues = !empty(appInsightResourceId) } : {} -var expandedAppSettings = union(appSettingsKeyValuePairs ?? {}, azureWebJobsValues, appInsightsValues) +var expandedAppSettings = union( + currentAppSettings ?? {}, + appSettingsKeyValuePairs ?? {}, + azureWebJobsValues, + appInsightsValues +) resource app 'Microsoft.Web/sites@2022-09-01' existing = { name: appName diff --git a/avm/res/web/site/slot/config--appsettings/main.json b/avm/res/web/site/slot/config--appsettings/main.json index 48ed22304b8..af2de6024a7 100644 --- a/avm/res/web/site/slot/config--appsettings/main.json +++ b/avm/res/web/site/slot/config--appsettings/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7111332561212908044" + "templateHash": "9363357518124041583" }, "name": "Site Slot App Settings", "description": "This module deploys a Site Slot App Setting.", @@ -72,6 +72,13 @@ "metadata": { "description": "Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING." } + }, + "currentAppSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The current app settings." + } } }, "resources": { @@ -113,7 +120,7 @@ "apiVersion": "2022-09-01", "name": "[format('{0}/{1}/{2}', parameters('appName'), parameters('slotName'), 'appsettings')]", "kind": "[parameters('kind')]", - "properties": "[union(coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", + "properties": "[union(coalesce(parameters('currentAppSettings'), createObject()), coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", "dependsOn": [ "appInsight", "app::slot", diff --git a/avm/res/web/site/slot/main.bicep b/avm/res/web/site/slot/main.bicep index b871bf0960d..95b2e0eff71 100644 --- a/avm/res/web/site/slot/main.bicep +++ b/avm/res/web/site/slot/main.bicep @@ -264,6 +264,7 @@ module slot_appsettings 'config--appsettings/main.bicep' = if (!empty(appSetting storageAccountUseIdentityAuthentication: storageAccountUseIdentityAuthentication appInsightResourceId: appInsightResourceId appSettingsKeyValuePairs: appSettingsKeyValuePairs + currentAppSettings: !empty(slot.id) ? list('${slot.id}/config/appsettings', '2023-12-01').properties : {} } } diff --git a/avm/res/web/site/slot/main.json b/avm/res/web/site/slot/main.json index 8f8f81e34aa..3120546cad4 100644 --- a/avm/res/web/site/slot/main.json +++ b/avm/res/web/site/slot/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "15729572124587777376" + "templateHash": "13282951347078727812" }, "name": "Web/Function App Deployment Slots", "description": "This module deploys a Web or Function App Deployment Slot.", @@ -968,7 +968,8 @@ }, "appSettingsKeyValuePairs": { "value": "[parameters('appSettingsKeyValuePairs')]" - } + }, + "currentAppSettings": "[if(not(empty(resourceId('Microsoft.Web/sites/slots', parameters('appName'), parameters('name')))), createObject('value', list(format('{0}/config/appsettings', resourceId('Microsoft.Web/sites/slots', parameters('appName'), parameters('name'))), '2023-12-01').properties), createObject('value', createObject()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -978,7 +979,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7111332561212908044" + "templateHash": "9363357518124041583" }, "name": "Site Slot App Settings", "description": "This module deploys a Site Slot App Setting.", @@ -1044,6 +1045,13 @@ "metadata": { "description": "Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING." } + }, + "currentAppSettings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The current app settings." + } } }, "resources": { @@ -1085,7 +1093,7 @@ "apiVersion": "2022-09-01", "name": "[format('{0}/{1}/{2}', parameters('appName'), parameters('slotName'), 'appsettings')]", "kind": "[parameters('kind')]", - "properties": "[union(coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", + "properties": "[union(coalesce(parameters('currentAppSettings'), createObject()), coalesce(parameters('appSettingsKeyValuePairs'), createObject()), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(coalesce(parameters('storageAccountResourceId'), '//'), '/')[2], split(coalesce(parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), '2023-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), union(createObject('AzureWebJobsStorage__accountName', last(split(coalesce(parameters('storageAccountResourceId'), 'dummyName'), '/'))), createObject('AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob)), createObject())), if(not(empty(parameters('appInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('appInsight').ConnectionString), createObject()))]", "dependsOn": [ "appInsight", "app::slot", diff --git a/avm/res/web/site/tests/e2e/functionApp.settings/dependencies.bicep b/avm/res/web/site/tests/e2e/functionApp.settings/dependencies.bicep new file mode 100644 index 00000000000..dd34e10b1cc --- /dev/null +++ b/avm/res/web/site/tests/e2e/functionApp.settings/dependencies.bicep @@ -0,0 +1,21 @@ +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +@description('Required. The name of the Server Farm to create.') +param serverFarmName string + +resource serverFarm 'Microsoft.Web/serverfarms@2022-03-01' = { + name: serverFarmName + location: location + sku: { + name: 'S1' + tier: 'Standard' + size: 'S1' + family: 'S' + capacity: 1 + } + properties: {} +} + +@description('The resource ID of the created Server Farm.') +output serverFarmResourceId string = serverFarm.id diff --git a/avm/res/web/site/tests/e2e/functionApp.settings/main.test.bicep b/avm/res/web/site/tests/e2e/functionApp.settings/main.test.bicep new file mode 100644 index 00000000000..a62f7217a17 --- /dev/null +++ b/avm/res/web/site/tests/e2e/functionApp.settings/main.test.bicep @@ -0,0 +1,67 @@ +targetScope = 'subscription' + +metadata name = 'Function App, using only defaults' +metadata description = 'This instance deploys the module as Function App with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-web.sites-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'wsfaset' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + serverFarmName: 'dep-${namePrefix}-sf-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +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 + kind: 'functionapp' + serverFarmResourceId: nestedDependencies.outputs.serverFarmResourceId + appSettingsKeyValuePairs: { + AzureFunctionsJobHost__logging__logLevel__default: 'Trace' + FUNCTIONS_EXTENSION_VERSION: '~4' + FUNCTIONS_WORKER_RUNTIME: 'dotnet' + } + } + dependsOn: [ + nestedDependencies + ] + } +] diff --git a/avm/res/web/site/version.json b/avm/res/web/site/version.json index 0f81d22abc4..b8b30a0125a 100644 --- a/avm/res/web/site/version.json +++ b/avm/res/web/site/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.8", + "version": "0.9", "pathFilters": [ "./main.json" ] From 73fc84f9a61c1427b1f4947805d7d41cdaaacb74 Mon Sep 17 00:00:00 2001 From: Fabio Masciotra Date: Mon, 23 Sep 2024 13:35:56 +0200 Subject: [PATCH 27/46] fix: module `avm/res/network/nat-gateway` (#3341) ## Description added a test case about the use of an existing Public IP address Closes #2378 ## Pipeline Reference [![avm.res.network.nat-gateway](https://github.com/fabmas/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml/badge.svg?branch=natgw)](https://github.com/fabmas/bicep-registry-modules/actions/workflows/avm.res.network.nat-gateway.yml) | Pipeline | | -------- | | | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/network/nat-gateway/README.md | 69 +++++++++++++++++-- avm/res/network/nat-gateway/main.bicep | 4 +- avm/res/network/nat-gateway/main.json | 20 +++--- .../tests/e2e/existingPip/dependencies.bicep | 25 +++++++ .../tests/e2e/existingPip/main.test.bicep | 61 ++++++++++++++++ 5 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 avm/res/network/nat-gateway/tests/e2e/existingPip/dependencies.bicep create mode 100644 avm/res/network/nat-gateway/tests/e2e/existingPip/main.test.bicep diff --git a/avm/res/network/nat-gateway/README.md b/avm/res/network/nat-gateway/README.md index 8c6358ff0c0..d12c7b26f78 100644 --- a/avm/res/network/nat-gateway/README.md +++ b/avm/res/network/nat-gateway/README.md @@ -31,9 +31,10 @@ The following section provides usage examples for the module, which were used to >**Note**: To reference the module, please use the following syntax `br/public:avm/res/network/nat-gateway:`. - [Using only defaults](#example-1-using-only-defaults) -- [Using large parameter set](#example-2-using-large-parameter-set) -- [Combine a generated and provided Public IP Prefix](#example-3-combine-a-generated-and-provided-public-ip-prefix) -- [WAF-aligned](#example-4-waf-aligned) +- [Using an existing Public IP](#example-2-using-an-existing-public-ip) +- [Using large parameter set](#example-3-using-large-parameter-set) +- [Combine a generated and provided Public IP Prefix](#example-4-combine-a-generated-and-provided-public-ip-prefix) +- [WAF-aligned](#example-5-waf-aligned) ### Example 1: _Using only defaults_ @@ -87,7 +88,63 @@ module natGateway 'br/public:avm/res/network/nat-gateway:' = {

-### Example 2: _Using large parameter set_ +### Example 2: _Using an existing Public IP_ + +This instance deploys the module using an existing Public IP address. + + +

+ +via Bicep module + +```bicep +module natGateway 'br/public:avm/res/network/nat-gateway:' = { + name: 'natGatewayDeployment' + params: { + // Required parameters + name: 'nngepip001' + zone: 1 + // Non-required parameters + location: '' + publicIpResourceIds: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "nngepip001" + }, + "zone": { + "value": 1 + }, + // Non-required parameters + "location": { + "value": "" + }, + "publicIpResourceIds": { + "value": "" + } + } +} +``` + +
+

+ +### Example 3: _Using large parameter set_ This instance deploys the module with most of its features enabled. @@ -287,7 +344,7 @@ module natGateway 'br/public:avm/res/network/nat-gateway:' = {

-### Example 3: _Combine a generated and provided Public IP Prefix_ +### Example 4: _Combine a generated and provided Public IP Prefix_ This example shows how you can provide a Public IP Prefix to the module, while also generating one in the module. @@ -359,7 +416,7 @@ module natGateway 'br/public:avm/res/network/nat-gateway:' = {

-### Example 4: _WAF-aligned_ +### Example 5: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. diff --git a/avm/res/network/nat-gateway/main.bicep b/avm/res/network/nat-gateway/main.bicep index 8836c815fe8..f576caedd56 100644 --- a/avm/res/network/nat-gateway/main.bicep +++ b/avm/res/network/nat-gateway/main.bicep @@ -99,7 +99,7 @@ module publicIPAddresses 'br/public:avm/res/network/public-ip-address:0.5.1' = [ for (publicIPAddressObject, index) in (publicIPAddressObjects ?? []): { name: '${uniqueString(deployment().name, location)}-NatGw-PIP-${index}' params: { - name: contains(publicIPAddressObject, 'name') ? publicIPAddressObject.name : '${name}-pip' + name: publicIPAddressObject.?name ?? '${name}-pip' location: location lock: publicIPAddressObject.?lock ?? lock diagnosticSettings: publicIPAddressObject.?diagnosticSettings @@ -133,7 +133,7 @@ module publicIPPrefixes 'br/public:avm/res/network/public-ip-prefix:0.4.1' = [ for (publicIPPrefixObject, index) in (publicIPPrefixObjects ?? []): { name: '${uniqueString(deployment().name, location)}-NatGw-Prefix-PIP-${index}' params: { - name: contains(publicIPPrefixObject, 'name') ? publicIPPrefixObject.name : '${name}-pip' + name: publicIPPrefixObject.?name ?? '${name}-pip' location: location lock: publicIPPrefixObject.?lock ?? lock prefixLength: publicIPPrefixObject.prefixLength diff --git a/avm/res/network/nat-gateway/main.json b/avm/res/network/nat-gateway/main.json index 414a6e01297..588a626b305 100644 --- a/avm/res/network/nat-gateway/main.json +++ b/avm/res/network/nat-gateway/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3430947452943320440" + "version": "0.30.3.12046", + "templateHash": "16462612640291787003" }, "name": "NAT Gateways", "description": "This module deploys a NAT Gateway.", @@ -308,7 +308,9 @@ }, "mode": "Incremental", "parameters": { - "name": "[if(contains(coalesce(parameters('publicIPAddressObjects'), createArray())[copyIndex()], 'name'), createObject('value', coalesce(parameters('publicIPAddressObjects'), createArray())[copyIndex()].name), createObject('value', format('{0}-pip', parameters('name'))))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('publicIPAddressObjects'), createArray())[copyIndex()], 'name'), format('{0}-pip', parameters('name')))]" + }, "location": { "value": "[parameters('location')]" }, @@ -1004,8 +1006,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15040145942768763519" + "version": "0.30.3.12046", + "templateHash": "9121047532434826411" } }, "parameters": { @@ -1050,7 +1052,9 @@ }, "mode": "Incremental", "parameters": { - "name": "[if(contains(coalesce(parameters('publicIPPrefixObjects'), createArray())[copyIndex()], 'name'), createObject('value', coalesce(parameters('publicIPPrefixObjects'), createArray())[copyIndex()].name), createObject('value', format('{0}-pip', parameters('name'))))]", + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('publicIPPrefixObjects'), createArray())[copyIndex()], 'name'), format('{0}-pip', parameters('name')))]" + }, "location": { "value": "[parameters('location')]" }, @@ -1416,8 +1420,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15040145942768763519" + "version": "0.30.3.12046", + "templateHash": "9121047532434826411" } }, "parameters": { diff --git a/avm/res/network/nat-gateway/tests/e2e/existingPip/dependencies.bicep b/avm/res/network/nat-gateway/tests/e2e/existingPip/dependencies.bicep new file mode 100644 index 00000000000..d12b008b0c1 --- /dev/null +++ b/avm/res/network/nat-gateway/tests/e2e/existingPip/dependencies.bicep @@ -0,0 +1,25 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Public IP to create.') +param existingPipName string + +resource existingPip 'Microsoft.Network/publicIPAddresses@2023-04-01' = { + name: existingPipName + location: location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + publicIPAllocationMethod: 'Static' + } + zones: [ + '1' + '2' + '3' + ] +} + +@description('The resource ID of the existing Public IP.') +output existingPipResourceId string = existingPip.id diff --git a/avm/res/network/nat-gateway/tests/e2e/existingPip/main.test.bicep b/avm/res/network/nat-gateway/tests/e2e/existingPip/main.test.bicep new file mode 100644 index 00000000000..1b138482081 --- /dev/null +++ b/avm/res/network/nat-gateway/tests/e2e/existingPip/main.test.bicep @@ -0,0 +1,61 @@ +targetScope = 'subscription' + +metadata name = 'Using an existing Public IP' +metadata description = 'This instance deploys the module using an existing Public IP address.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.natgateway-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nngepip' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + location: resourceLocation + existingPipName: '${namePrefix}${serviceShort}001-existingpip1' + + } +} + + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + location: resourceLocation + name: '${namePrefix}${serviceShort}001' + zone: 1 + publicIpResourceIds: [nestedDependencies.outputs.existingPipResourceId] + } + } +] From 201c378a69ca7cf115b71a900091e708ba00c909 Mon Sep 17 00:00:00 2001 From: Chinedum Echeta <60179183+cecheta@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:34:43 -0700 Subject: [PATCH 28/46] feat: `avm/ptn/ai-platform/baseline` Change identities of AI workspaces (#3345) ## Description This PR changes the managed identities of both the workspace project and hub * The workspace hub no longer creates a user assigned identity, but instead expects the name of an already existing UAI to be passed in. This is because there is now validation when creating a workspace that it should have permissions to approve private endpoint connections * The workspace project now only uses a system assigned identity, as it is not possible to authenticate with a private container registry using a user assigned identity ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.ai-platform.baseline](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml/badge.svg?branch=identity)](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [x] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/ai-platform/baseline/README.md | 56 +--- avm/ptn/ai-platform/baseline/main.bicep | 286 ++++++---------- avm/ptn/ai-platform/baseline/main.json | 309 +++--------------- .../baseline/tests/e2e/max/dependencies.bicep | 11 + .../baseline/tests/e2e/max/main.test.bicep | 6 +- .../tests/e2e/waf-aligned/dependencies.bicep | 23 ++ .../tests/e2e/waf-aligned/main.test.bicep | 2 + avm/ptn/ai-platform/baseline/version.json | 2 +- 8 files changed, 200 insertions(+), 495 deletions(-) diff --git a/avm/ptn/ai-platform/baseline/README.md b/avm/ptn/ai-platform/baseline/README.md index 45718c8a77c..dfaf48f4a58 100644 --- a/avm/ptn/ai-platform/baseline/README.md +++ b/avm/ptn/ai-platform/baseline/README.md @@ -38,7 +38,6 @@ By integrating with Microsoft Entra ID for secure identity management and utiliz | `Microsoft.MachineLearningServices/workspaces` | [2024-04-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.MachineLearningServices/2024-04-01-preview/workspaces) | | `Microsoft.MachineLearningServices/workspaces/computes` | [2022-10-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.MachineLearningServices/2022-10-01/workspaces/computes) | | `Microsoft.Maintenance/configurationAssignments` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Maintenance/2023-04-01/configurationAssignments) | -| `Microsoft.ManagedIdentity/userAssignedIdentities` | [2023-01-31](https://learn.microsoft.com/en-us/azure/templates/Microsoft.ManagedIdentity/2023-01-31/userAssignedIdentities) | | `Microsoft.Network/bastionHosts` | [2022-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2022-11-01/bastionHosts) | | `Microsoft.Network/networkInterfaces` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/networkInterfaces) | | `Microsoft.Network/networkSecurityGroups` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/networkSecurityGroups) | @@ -54,8 +53,8 @@ By integrating with Microsoft Entra ID for secure identity management and utiliz | `Microsoft.Network/privateDnsZones/virtualNetworkLinks` | [2020-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2020-06-01/privateDnsZones/virtualNetworkLinks) | | `Microsoft.Network/privateEndpoints` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | -| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | | `Microsoft.Network/publicIPAddresses` | [2023-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-09-01/publicIPAddresses) | | `Microsoft.Network/virtualNetworks` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks) | | `Microsoft.Network/virtualNetworks/subnets` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/subnets) | @@ -185,10 +184,7 @@ module baseline 'br/public:avm/ptn/ai-platform/baseline:' = { logAnalyticsConfiguration: { name: 'log-aipbmax' } - managedIdentityConfiguration: { - hubName: 'id-hub-aipbmax' - projectName: 'id-project-aipbmax' - } + managedIdentityName: '' storageAccountConfiguration: { allowSharedKeyAccess: true name: 'staipbmax' @@ -321,11 +317,8 @@ module baseline 'br/public:avm/ptn/ai-platform/baseline:' = { "name": "log-aipbmax" } }, - "managedIdentityConfiguration": { - "value": { - "hubName": "id-hub-aipbmax", - "projectName": "id-project-aipbmax" - } + "managedIdentityName": { + "value": "" }, "storageAccountConfiguration": { "value": { @@ -544,6 +537,7 @@ module baseline 'br/public:avm/ptn/ai-platform/baseline:' = { // Required parameters name: '' // Non-required parameters + managedIdentityName: '' tags: { Env: 'test' 'hidden-title': 'This is visible in the resource name' @@ -591,6 +585,9 @@ module baseline 'br/public:avm/ptn/ai-platform/baseline:' = { "value": "" }, // Non-required parameters + "managedIdentityName": { + "value": "" + }, "tags": { "value": { "Env": "test", @@ -649,7 +646,7 @@ module baseline 'br/public:avm/ptn/ai-platform/baseline:' = { | [`keyVaultConfiguration`](#parameter-keyvaultconfiguration) | object | Configuration for the key vault. | | [`location`](#parameter-location) | string | Location for all Resources. | | [`logAnalyticsConfiguration`](#parameter-loganalyticsconfiguration) | object | Configuration for the Log Analytics workspace. | -| [`managedIdentityConfiguration`](#parameter-managedidentityconfiguration) | object | Configuration for the user-assigned managed identities. | +| [`managedIdentityName`](#parameter-managedidentityname) | string | The name of the user assigned identity for the AI Studio hub. If not provided, the hub will use a system assigned identity. | | [`storageAccountConfiguration`](#parameter-storageaccountconfiguration) | object | Configuration for the storage account. | | [`tags`](#parameter-tags) | object | Resource tags. | | [`virtualMachineConfiguration`](#parameter-virtualmachineconfiguration) | secureObject | Configuration for the virtual machine. | @@ -889,30 +886,9 @@ The name of the Log Analytics workspace. - Required: No - Type: string -### Parameter: `managedIdentityConfiguration` - -Configuration for the user-assigned managed identities. - -- Required: No -- Type: object - -**Optional parameters** - -| Parameter | Type | Description | -| :-- | :-- | :-- | -| [`hubName`](#parameter-managedidentityconfigurationhubname) | string | The name of the workspace hub user-assigned managed identity. | -| [`projectName`](#parameter-managedidentityconfigurationprojectname) | string | The name of the workspace project user-assigned managed identity. | - -### Parameter: `managedIdentityConfiguration.hubName` - -The name of the workspace hub user-assigned managed identity. - -- Required: No -- Type: string - -### Parameter: `managedIdentityConfiguration.projectName` +### Parameter: `managedIdentityName` -The name of the workspace project user-assigned managed identity. +The name of the user assigned identity for the AI Studio hub. If not provided, the hub will use a system assigned identity. - Required: No - Type: string @@ -1144,14 +1120,6 @@ The name of the AI Studio workspace project. | `location` | string | The location the module was deployed to. | | `logAnalyticsWorkspaceName` | string | The name of the log analytics workspace. | | `logAnalyticsWorkspaceResourceId` | string | The resource ID of the log analytics workspace. | -| `managedIdentityHubClientId` | string | The client ID of the workspace hub user assigned managed identity. | -| `managedIdentityHubName` | string | The name of the workspace hub user assigned managed identity. | -| `managedIdentityHubPrincipalId` | string | The principal ID of the workspace hub user assigned managed identity. | -| `managedIdentityHubResourceId` | string | The resource ID of the workspace hub user assigned managed identity. | -| `managedIdentityProjectClientId` | string | The client ID of the workspace project user assigned managed identity. | -| `managedIdentityProjectName` | string | The name of the workspace project user assigned managed identity. | -| `managedIdentityProjectPrincipalId` | string | The principal ID of the workspace project user assigned managed identity. | -| `managedIdentityProjectResourceId` | string | The resource ID of the workspace project user assigned managed identity. | | `resourceGroupName` | string | The name of the resource group the module was deployed to. | | `storageAccountName` | string | The name of the storage account. | | `storageAccountResourceId` | string | The resource ID of the storage account. | @@ -1161,8 +1129,10 @@ The name of the AI Studio workspace project. | `virtualNetworkResourceId` | string | The resource ID of the virtual network. | | `virtualNetworkSubnetName` | string | The name of the subnet in the virtual network. | | `virtualNetworkSubnetResourceId` | string | The resource ID of the subnet in the virtual network. | +| `workspaceHubManagedIdentityPrincipalId` | string | The principal ID of the workspace hub system assigned identity, if applicable. | | `workspaceHubName` | string | The name of the workspace hub. | | `workspaceHubResourceId` | string | The resource ID of the workspace hub. | +| `workspaceProjectManagedIdentityPrincipalId` | string | The principal ID of the workspace project system assigned identity. | | `workspaceProjectName` | string | The name of the workspace project. | | `workspaceProjectResourceId` | string | The resource ID of the workspace project. | diff --git a/avm/ptn/ai-platform/baseline/main.bicep b/avm/ptn/ai-platform/baseline/main.bicep index d3095c0f797..29b505f083b 100644 --- a/avm/ptn/ai-platform/baseline/main.bicep +++ b/avm/ptn/ai-platform/baseline/main.bicep @@ -17,8 +17,8 @@ param tags object? @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true -@description('Optional. Configuration for the user-assigned managed identities.') -param managedIdentityConfiguration managedIdentityConfigurationType +@description('Optional. The name of the user assigned identity for the AI Studio hub. If not provided, the hub will use a system assigned identity.') +param managedIdentityName string? @description('Optional. Configuration for the Log Analytics workspace.') param logAnalyticsConfiguration logAnalyticsConfigurationType @@ -120,13 +120,15 @@ module workspaceHub_privateDnsZones 'br/public:avm/res/network/private-dns-zone: virtualNetworkResourceId: virtualNetwork.id } ] - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + ] + : null } } ] @@ -278,16 +280,8 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:0.5.3' = if (cr } } -resource managedIdentityHub 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: managedIdentityConfiguration.?hubName ?? 'id-hub-${name}' - location: location - tags: tags -} - -resource managedIdentityProject 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: managedIdentityConfiguration.?projectName ?? 'id-project-${name}' - location: location - tags: tags +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = if (managedIdentityName != null) { + name: managedIdentityName ?? 'null' } resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { @@ -296,14 +290,14 @@ resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09 tags: tags } -resource resourceGroup_roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { +resource resourceGroup_roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (managedIdentityName != null) { name: guid(resourceGroup().id, name) properties: { roleDefinitionId: subscriptionResourceId( 'Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7' // Reader ) - principalId: managedIdentityHub.properties.principalId + principalId: userAssignedIdentity.properties.principalId principalType: 'ServicePrincipal' } } @@ -324,28 +318,20 @@ module keyVault 'br/public:avm/res/key-vault/vault:0.6.2' = { } publicNetworkAccess: 'Disabled' enablePurgeProtection: keyVaultConfiguration.?enablePurgeProtection ?? true - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Key Vault Administrator' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Key Vault Administrator' - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Key Vault Administrator' + principalType: 'ServicePrincipal' + } + ] + : null diagnosticSettings: [ { workspaceResourceId: logAnalyticsWorkspace.id @@ -385,48 +371,25 @@ module storageAccount 'br/public:avm/res/storage/storage-account:0.11.0' = { privateDnsZoneResourceIds: [resourceId('Microsoft.Network/privateDnsZones', zone.key)] }) : null - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Reader' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Storage Account Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Storage Table Data Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Storage Blob Data Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Storage Blob Data Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Privileged Contributor - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Privileged Contributor - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalType: 'ServicePrincipal' + } + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Privileged Contributor + principalType: 'ServicePrincipal' + } + ] + : null tags: tags } @@ -444,28 +407,20 @@ module containerRegistry 'br/public:avm/res/container-registry/registry:0.3.1' = networkRuleBypassOptions: 'AzureServices' zoneRedundancy: 'Enabled' trustPolicyStatus: containerRegistryConfiguration.?trustPolicyStatus ?? 'enabled' - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'AcrPull' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'AcrPull' - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'AcrPull' + principalType: 'ServicePrincipal' + } + ] + : null tags: tags } } @@ -478,18 +433,15 @@ module applicationInsights 'br/public:avm/res/insights/component:0.3.1' = { kind: 'web' enableTelemetry: enableTelemetry workspaceResourceId: logAnalyticsWorkspace.id - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + ] + : null tags: tags } } @@ -509,12 +461,14 @@ module workspaceHub 'br/public:avm/res/machine-learning-services/workspace:0.5.0 workspaceHubConfig: { defaultWorkspaceResourceGroup: resourceGroup().id } - managedIdentities: { - userAssignedResourceIds: [ - managedIdentityHub.id - ] - } - primaryUserAssignedIdentity: managedIdentityHub.id + managedIdentities: managedIdentityName != null + ? { + userAssignedResourceIds: [ + userAssignedIdentity.id + ] + } + : null + primaryUserAssignedIdentity: managedIdentityName != null ? userAssignedIdentity.id : null computes: workspaceConfiguration.?computes managedNetworkSettings: { isolationMode: workspaceConfiguration.?networkIsolationMode ?? 'AllowInternetOutbound' @@ -537,19 +491,22 @@ module workspaceHub 'br/public:avm/res/machine-learning-services/workspace:0.5.0 ] : null systemDatastoresAuthMode: 'identity' - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + ] + : null tags: tags } dependsOn: workspaceHub_privateDnsZones } +// The workspace project uses a system assigned managed identity, so it can authenticate with the container registry module workspaceProject 'br/public:avm/res/machine-learning-services/workspace:0.5.0' = { name: '${uniqueString(deployment().name, location)}-project' params: { @@ -559,24 +516,15 @@ module workspaceProject 'br/public:avm/res/machine-learning-services/workspace:0 enableTelemetry: enableTelemetry kind: 'Project' hubResourceId: workspaceHub.outputs.resourceId - managedIdentities: { - userAssignedResourceIds: [ - managedIdentityProject.id - ] - } - primaryUserAssignedIdentity: managedIdentityProject.id - roleAssignments: [ - { - principalId: managedIdentityHub.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - { - principalId: managedIdentityProject.properties.principalId - roleDefinitionIdOrName: 'Contributor' - principalType: 'ServicePrincipal' - } - ] + roleAssignments: managedIdentityName != null + ? [ + { + principalId: userAssignedIdentity.properties.principalId + roleDefinitionIdOrName: 'Contributor' + principalType: 'ServicePrincipal' + } + ] + : null tags: tags } } @@ -612,30 +560,6 @@ output logAnalyticsWorkspaceResourceId string = logAnalyticsWorkspace.id @description('The name of the log analytics workspace.') output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name -@description('The resource ID of the workspace hub user assigned managed identity.') -output managedIdentityHubResourceId string = managedIdentityHub.id - -@description('The name of the workspace hub user assigned managed identity.') -output managedIdentityHubName string = managedIdentityHub.name - -@description('The principal ID of the workspace hub user assigned managed identity.') -output managedIdentityHubPrincipalId string = managedIdentityHub.properties.principalId - -@description('The client ID of the workspace hub user assigned managed identity.') -output managedIdentityHubClientId string = managedIdentityHub.properties.clientId - -@description('The resource ID of the workspace project user assigned managed identity.') -output managedIdentityProjectResourceId string = managedIdentityProject.id - -@description('The name of the workspace project user assigned managed identity.') -output managedIdentityProjectName string = managedIdentityProject.name - -@description('The principal ID of the workspace project user assigned managed identity.') -output managedIdentityProjectPrincipalId string = managedIdentityProject.properties.principalId - -@description('The client ID of the workspace project user assigned managed identity.') -output managedIdentityProjectClientId string = managedIdentityProject.properties.clientId - @description('The resource ID of the key vault.') output keyVaultResourceId string = keyVault.outputs.resourceId @@ -663,6 +587,12 @@ output workspaceHubResourceId string = workspaceHub.outputs.resourceId @description('The name of the workspace hub.') output workspaceHubName string = workspaceHub.outputs.name +@description('The principal ID of the workspace hub system assigned identity, if applicable.') +output workspaceHubManagedIdentityPrincipalId string = workspaceHub.outputs.systemAssignedMIPrincipalId + +@description('The principal ID of the workspace project system assigned identity.') +output workspaceProjectManagedIdentityPrincipalId string = workspaceProject.outputs.systemAssignedMIPrincipalId + @description('The resource ID of the workspace project.') output workspaceProjectResourceId string = workspaceProject.outputs.resourceId @@ -697,14 +627,6 @@ output virtualMachineName string = createVirtualMachine ? virtualMachine.outputs // Definitions // // ================ // -type managedIdentityConfigurationType = { - @description('Optional. The name of the workspace hub user-assigned managed identity.') - hubName: string? - - @description('Optional. The name of the workspace project user-assigned managed identity.') - projectName: string? -}? - type logAnalyticsConfigurationType = { @description('Optional. The name of the Log Analytics workspace.') name: string? diff --git a/avm/ptn/ai-platform/baseline/main.json b/avm/ptn/ai-platform/baseline/main.json index 4fe65a2d838..a6eafbcbbd2 100644 --- a/avm/ptn/ai-platform/baseline/main.json +++ b/avm/ptn/ai-platform/baseline/main.json @@ -5,34 +5,14 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2263138345896714301" + "version": "0.30.3.12046", + "templateHash": "12442663604053803236" }, "name": "AI Platform Baseline", "description": "This module provides a secure and scalable environment for deploying AI applications on Azure.\nThe module encompasses all essential components required for building, managing, and observing AI solutions, including a machine learning workspace, observability tools, and necessary data management services.\nBy integrating with Microsoft Entra ID for secure identity management and utilizing private endpoints for services like Key Vault and Blob Storage, the module ensures secure communication and data access.", "owner": "Azure/module-maintainers" }, "definitions": { - "managedIdentityConfigurationType": { - "type": "object", - "properties": { - "hubName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the workspace hub user-assigned managed identity." - } - }, - "projectName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the workspace project user-assigned managed identity." - } - } - }, - "nullable": true - }, "logAnalyticsConfigurationType": { "type": "object", "properties": { @@ -776,10 +756,11 @@ "description": "Optional. Enable/Disable usage telemetry for module." } }, - "managedIdentityConfiguration": { - "$ref": "#/definitions/managedIdentityConfigurationType", + "managedIdentityName": { + "type": "string", + "nullable": true, "metadata": { - "description": "Optional. Configuration for the user-assigned managed identities." + "description": "Optional. The name of the user assigned identity for the AI Studio hub. If not provided, the hub will use a system assigned identity." } }, "logAnalyticsConfiguration": { @@ -919,19 +900,12 @@ } } }, - "managedIdentityHub": { + "userAssignedIdentity": { + "condition": "[not(equals(parameters('managedIdentityName'), null()))]", + "existing": true, "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", - "name": "[coalesce(tryGet(parameters('managedIdentityConfiguration'), 'hubName'), format('id-hub-{0}', parameters('name')))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]" - }, - "managedIdentityProject": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[coalesce(tryGet(parameters('managedIdentityConfiguration'), 'projectName'), format('id-project-{0}', parameters('name')))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]" + "name": "[coalesce(parameters('managedIdentityName'), 'null')]" }, "logAnalyticsWorkspace": { "type": "Microsoft.OperationalInsights/workspaces", @@ -941,16 +915,17 @@ "tags": "[parameters('tags')]" }, "resourceGroup_roleAssignment": { + "condition": "[not(equals(parameters('managedIdentityName'), null()))]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "name": "[guid(resourceGroup().id, parameters('name'))]", "properties": { "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", - "principalId": "[reference('managedIdentityHub').principalId]", + "principalId": "[reference('userAssignedIdentity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ - "managedIdentityHub" + "userAssignedIdentity" ] }, "storageAccount_privateDnsZones": { @@ -3860,15 +3835,7 @@ } ] }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - } - ] - } + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -6723,7 +6690,7 @@ } }, "dependsOn": [ - "managedIdentityHub", + "userAssignedIdentity", "virtualNetwork" ] }, @@ -14047,30 +14014,7 @@ "enablePurgeProtection": { "value": "[coalesce(tryGet(parameters('keyVaultConfiguration'), 'enablePurgeProtection'), true())]" }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Key Vault Administrator", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Key Vault Administrator", - "principalType": "ServicePrincipal" - } - ] - }, + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'), createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Key Vault Administrator', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "diagnosticSettings": { "value": [ { @@ -16818,8 +16762,7 @@ }, "dependsOn": [ "logAnalyticsWorkspace", - "managedIdentityHub", - "managedIdentityProject" + "userAssignedIdentity" ] }, "storageAccount": { @@ -16863,50 +16806,7 @@ } }, "privateEndpoints": "[if(not(equals(variables('subnetResourceId'), null())), createObject('value', map(items(variables('storagePrivateDnsZones')), lambda('zone', createObject('name', format('pep-{0}-{1}', lambdaVariables('zone').value, parameters('name')), 'customNetworkInterfaceName', format('nic-{0}-{1}', lambdaVariables('zone').value, parameters('name')), 'service', lambdaVariables('zone').value, 'subnetResourceId', coalesce(variables('subnetResourceId'), ''), 'privateDnsZoneResourceIds', createArray(resourceId('Microsoft.Network/privateDnsZones', lambdaVariables('zone').key)))))), createObject('value', null()))]", - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Reader", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Storage Account Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Storage Table Data Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Storage Blob Data Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Storage Blob Data Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "69566ab7-960f-475b-8e7c-b3118f30c6bd", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "69566ab7-960f-475b-8e7c-b3118f30c6bd", - "principalType": "ServicePrincipal" - } - ] - }, + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'), createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Storage Blob Data Contributor', 'principalType', 'ServicePrincipal'), createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', '69566ab7-960f-475b-8e7c-b3118f30c6bd', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "tags": { "value": "[parameters('tags')]" } @@ -21570,9 +21470,8 @@ }, "dependsOn": [ "virtualNetwork::defaultSubnet", - "managedIdentityHub", - "managedIdentityProject", - "storageAccount_privateDnsZones" + "storageAccount_privateDnsZones", + "userAssignedIdentity" ] }, "containerRegistry": { @@ -21609,30 +21508,7 @@ "trustPolicyStatus": { "value": "[coalesce(tryGet(parameters('containerRegistryConfiguration'), 'trustPolicyStatus'), 'enabled')]" }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "AcrPull", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "AcrPull", - "principalType": "ServicePrincipal" - } - ] - }, + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'), createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'AcrPull', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "tags": { "value": "[parameters('tags')]" } @@ -23754,8 +23630,7 @@ } }, "dependsOn": [ - "managedIdentityHub", - "managedIdentityProject" + "userAssignedIdentity" ] }, "applicationInsights": { @@ -23783,20 +23658,7 @@ "workspaceResourceId": { "value": "[resourceId('Microsoft.OperationalInsights/workspaces', coalesce(tryGet(parameters('logAnalyticsConfiguration'), 'name'), format('log-{0}', parameters('name'))))]" }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - } - ] - }, + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "tags": { "value": "[parameters('tags')]" } @@ -24397,8 +24259,7 @@ }, "dependsOn": [ "logAnalyticsWorkspace", - "managedIdentityHub", - "managedIdentityProject" + "userAssignedIdentity" ] }, "workspaceHub": { @@ -24443,16 +24304,8 @@ "defaultWorkspaceResourceGroup": "[resourceGroup().id]" } }, - "managedIdentities": { - "value": { - "userAssignedResourceIds": [ - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(tryGet(parameters('managedIdentityConfiguration'), 'hubName'), format('id-hub-{0}', parameters('name'))))]" - ] - } - }, - "primaryUserAssignedIdentity": { - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(tryGet(parameters('managedIdentityConfiguration'), 'hubName'), format('id-hub-{0}', parameters('name'))))]" - }, + "managedIdentities": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createObject('userAssignedResourceIds', createArray(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(parameters('managedIdentityName'), 'null'))))), createObject('value', null()))]", + "primaryUserAssignedIdentity": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(parameters('managedIdentityName'), 'null'))), createObject('value', null()))]", "computes": { "value": "[tryGet(parameters('workspaceConfiguration'), 'computes')]" }, @@ -24466,15 +24319,7 @@ "systemDatastoresAuthMode": { "value": "identity" }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - } - ] - }, + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "tags": { "value": "[parameters('tags')]" } @@ -26720,8 +26565,8 @@ "containerRegistry", "virtualNetwork::defaultSubnet", "keyVault", - "managedIdentityHub", "storageAccount", + "userAssignedIdentity", "workspaceHub_privateDnsZones" ] }, @@ -26753,30 +26598,7 @@ "hubResourceId": { "value": "[reference('workspaceHub').outputs.resourceId.value]" }, - "managedIdentities": { - "value": { - "userAssignedResourceIds": [ - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(tryGet(parameters('managedIdentityConfiguration'), 'projectName'), format('id-project-{0}', parameters('name'))))]" - ] - } - }, - "primaryUserAssignedIdentity": { - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(tryGet(parameters('managedIdentityConfiguration'), 'projectName'), format('id-project-{0}', parameters('name'))))]" - }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('managedIdentityHub').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('managedIdentityProject').principalId]", - "roleDefinitionIdOrName": "Contributor", - "principalType": "ServicePrincipal" - } - ] - }, + "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "tags": { "value": "[parameters('tags')]" } @@ -29018,8 +28840,7 @@ } }, "dependsOn": [ - "managedIdentityHub", - "managedIdentityProject", + "userAssignedIdentity", "workspaceHub" ] } @@ -29088,62 +28909,6 @@ }, "value": "[coalesce(tryGet(parameters('logAnalyticsConfiguration'), 'name'), format('log-{0}', parameters('name')))]" }, - "managedIdentityHubResourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the workspace hub user assigned managed identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(tryGet(parameters('managedIdentityConfiguration'), 'hubName'), format('id-hub-{0}', parameters('name'))))]" - }, - "managedIdentityHubName": { - "type": "string", - "metadata": { - "description": "The name of the workspace hub user assigned managed identity." - }, - "value": "[coalesce(tryGet(parameters('managedIdentityConfiguration'), 'hubName'), format('id-hub-{0}', parameters('name')))]" - }, - "managedIdentityHubPrincipalId": { - "type": "string", - "metadata": { - "description": "The principal ID of the workspace hub user assigned managed identity." - }, - "value": "[reference('managedIdentityHub').principalId]" - }, - "managedIdentityHubClientId": { - "type": "string", - "metadata": { - "description": "The client ID of the workspace hub user assigned managed identity." - }, - "value": "[reference('managedIdentityHub').clientId]" - }, - "managedIdentityProjectResourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the workspace project user assigned managed identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', coalesce(tryGet(parameters('managedIdentityConfiguration'), 'projectName'), format('id-project-{0}', parameters('name'))))]" - }, - "managedIdentityProjectName": { - "type": "string", - "metadata": { - "description": "The name of the workspace project user assigned managed identity." - }, - "value": "[coalesce(tryGet(parameters('managedIdentityConfiguration'), 'projectName'), format('id-project-{0}', parameters('name')))]" - }, - "managedIdentityProjectPrincipalId": { - "type": "string", - "metadata": { - "description": "The principal ID of the workspace project user assigned managed identity." - }, - "value": "[reference('managedIdentityProject').principalId]" - }, - "managedIdentityProjectClientId": { - "type": "string", - "metadata": { - "description": "The client ID of the workspace project user assigned managed identity." - }, - "value": "[reference('managedIdentityProject').clientId]" - }, "keyVaultResourceId": { "type": "string", "metadata": { @@ -29207,6 +28972,20 @@ }, "value": "[reference('workspaceHub').outputs.name.value]" }, + "workspaceHubManagedIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the workspace hub system assigned identity, if applicable." + }, + "value": "[reference('workspaceHub').outputs.systemAssignedMIPrincipalId.value]" + }, + "workspaceProjectManagedIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the workspace project system assigned identity." + }, + "value": "[reference('workspaceProject').outputs.systemAssignedMIPrincipalId.value]" + }, "workspaceProjectResourceId": { "type": "string", "metadata": { diff --git a/avm/ptn/ai-platform/baseline/tests/e2e/max/dependencies.bicep b/avm/ptn/ai-platform/baseline/tests/e2e/max/dependencies.bicep index d2ea57bb6a7..1ce964acc0e 100644 --- a/avm/ptn/ai-platform/baseline/tests/e2e/max/dependencies.bicep +++ b/avm/ptn/ai-platform/baseline/tests/e2e/max/dependencies.bicep @@ -4,6 +4,9 @@ param location string = resourceGroup().location @description('Required. The name of the Storage Account to create.') param storageAccountName string +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + @description('Required. The name of the Maintenance Configuration to create.') param maintenanceConfigurationName string @@ -13,6 +16,11 @@ param networkSecurityGroupName string @description('Required. The name of the Bastion Network Security Group to create.') param networkSecurityGroupBastionName string +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { name: storageAccountName location: location @@ -196,6 +204,9 @@ resource networkSecurityGroupBastion 'Microsoft.Network/networkSecurityGroups@20 @description('The resource ID of the created Storage Account.') output storageAccountResourceId string = storageAccount.id +@description('The name of the created Managed Identity.') +output managedIdentityName string = managedIdentity.name + @description('The resource ID of the created Network Security Group.') output networkSecurityGroupResourceId string = networkSecurityGroup.id diff --git a/avm/ptn/ai-platform/baseline/tests/e2e/max/main.test.bicep b/avm/ptn/ai-platform/baseline/tests/e2e/max/main.test.bicep index 240496050a0..a8beac49bf4 100644 --- a/avm/ptn/ai-platform/baseline/tests/e2e/max/main.test.bicep +++ b/avm/ptn/ai-platform/baseline/tests/e2e/max/main.test.bicep @@ -44,6 +44,7 @@ module nestedDependencies 'dependencies.bicep' = { params: { location: resourceLocation storageAccountName: 'dep${namePrefix}st${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-mi-${serviceShort}' maintenanceConfigurationName: 'dep-${namePrefix}-mc-${serviceShort}' networkSecurityGroupName: 'dep${namePrefix}nsg${serviceShort}' networkSecurityGroupBastionName: 'dep-${namePrefix}-nsg-bastion-${serviceShort}' @@ -61,10 +62,7 @@ module testDeployment '../../../main.bicep' = [ name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}' - managedIdentityConfiguration: { - hubName: '${namePrefix}-id-hub-${serviceShort}' - projectName: '${namePrefix}-id-project-${serviceShort}' - } + managedIdentityName: nestedDependencies.outputs.managedIdentityName logAnalyticsConfiguration: { name: '${namePrefix}-log-${serviceShort}' } diff --git a/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/dependencies.bicep b/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/dependencies.bicep index 299d71ad379..fbb0fe92d41 100644 --- a/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/dependencies.bicep +++ b/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/dependencies.bicep @@ -1,12 +1,20 @@ @description('Optional. The location to deploy to.') param location string = resourceGroup().location +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + @description('Required. The name of the Maintenance Configuration to create.') param maintenanceConfigurationName string @description('Required. The name of the Storage Account to create.') param storageAccountName string +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { name: storageAccountName location: location @@ -16,6 +24,18 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = { kind: 'StorageV2' } +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, storageAccount.id, managedIdentity.id) + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'b556d68e-0be0-4f35-a333-ad7ee1ce17ea' // Azure AI Enterprise Network Connection Approver + ) + principalId: managedIdentity.properties.principalId + principalType: 'ServicePrincipal' + } +} + resource maintenanceConfiguration 'Microsoft.Maintenance/maintenanceConfigurations@2023-10-01-preview' = { name: maintenanceConfigurationName location: location @@ -46,5 +66,8 @@ resource maintenanceConfiguration 'Microsoft.Maintenance/maintenanceConfiguratio @description('The resource ID of the created Storage Account.') output storageAccountResourceId string = storageAccount.id +@description('The name of the created Managed Identity.') +output managedIdentityName string = managedIdentity.name + @description('The resource ID of the maintenance configuration.') output maintenanceConfigurationResourceId string = maintenanceConfiguration.id diff --git a/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/main.test.bicep b/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/main.test.bicep index 21d1ba89347..ec39ff7d58c 100644 --- a/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/ptn/ai-platform/baseline/tests/e2e/waf-aligned/main.test.bicep @@ -44,6 +44,7 @@ module nestedDependencies 'dependencies.bicep' = { name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { storageAccountName: 'dep${namePrefix}st${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-mi-${serviceShort}' maintenanceConfigurationName: 'dep-${namePrefix}-mc-${serviceShort}' location: enforcedLocation } @@ -60,6 +61,7 @@ module testDeployment '../../../main.bicep' = [ name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}${substring(uniqueString(baseTime), 0, 3)}' + managedIdentityName: nestedDependencies.outputs.managedIdentityName virtualMachineConfiguration: { adminUsername: 'localAdminUser' adminPassword: password diff --git a/avm/ptn/ai-platform/baseline/version.json b/avm/ptn/ai-platform/baseline/version.json index aa34c4d0f56..41fc8c654f4 100644 --- a/avm/ptn/ai-platform/baseline/version.json +++ b/avm/ptn/ai-platform/baseline/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.4", + "version": "0.5", "pathFilters": [ "./main.json" ] From 432219fbad171fbb51886495b260820412ad0664 Mon Sep 17 00:00:00 2001 From: Ahmad Abdalla <28486158+ahmadabdalla@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:47:42 +1000 Subject: [PATCH 29/46] fix: Add explicit dependsOn in the privateLinkScope_privateEndpoints module for `avm/res/insights/private-link-scope` (#3348) ## Description Fixes #3208 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.insights.private-link-scope](https://github.com/ahmadabdalla/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml/badge.svg?branch=users%2Fahmad%2F3208_pls)](https://github.com/ahmadabdalla/bicep-registry-modules/actions/workflows/avm.res.insights.private-link-scope.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/insights/private-link-scope/README.md | 4 ++-- avm/res/insights/private-link-scope/main.bicep | 3 +++ avm/res/insights/private-link-scope/main.json | 13 +++++++------ .../private-link-scope/scoped-resource/main.json | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/avm/res/insights/private-link-scope/README.md b/avm/res/insights/private-link-scope/README.md index 4dda77b2098..e8ef974f3cd 100644 --- a/avm/res/insights/private-link-scope/README.md +++ b/avm/res/insights/private-link-scope/README.md @@ -770,7 +770,7 @@ module privateLinkScope 'br/public:avm/res/insights/private-link-scope: | Parameter | Type | Description | | :-- | :-- | :-- | -| [`accessModeSettings`](#parameter-accessmodesettings) | object | Specifies the access mode of ingestion or queries through associated private endpoints in scope. For security reasons, it is recommended to use PrivateOnly whenever possible to avoid data exfiltration.

* Private Only - This mode allows the connected virtual network to reach only Private Link resources. It is the most secure mode and is set as the default when the `privateEndpoints` parameter is configured.

* Open - Allows the connected virtual network to reach both Private Link resources and the resources not in the AMPLS resource. Data exfiltration cannot be prevented in this mode. | +| [`accessModeSettings`](#parameter-accessmodesettings) | object | Specifies the access mode of ingestion or queries through associated private endpoints in scope. For security reasons, it is recommended to use PrivateOnly whenever possible to avoid data exfiltration.

* Private Only - This mode allows the connected virtual network to reach only Private Link resources. It is the most secure mode and is set as the default when the `privateEndpoints` parameter is configured.

* Open - Allows the connected virtual network to reach both Private Link resources and the resources not in the AMPLS resource. Data exfiltration cannot be prevented in this mode. | | [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | | [`location`](#parameter-location) | string | The location of the private link scope. Should be global. | | [`lock`](#parameter-lock) | object | The lock settings of the service. | @@ -788,7 +788,7 @@ Name of the private link scope. ### Parameter: `accessModeSettings` -Specifies the access mode of ingestion or queries through associated private endpoints in scope. For security reasons, it is recommended to use PrivateOnly whenever possible to avoid data exfiltration.

* Private Only - This mode allows the connected virtual network to reach only Private Link resources. It is the most secure mode and is set as the default when the `privateEndpoints` parameter is configured.

* Open - Allows the connected virtual network to reach both Private Link resources and the resources not in the AMPLS resource. Data exfiltration cannot be prevented in this mode. +Specifies the access mode of ingestion or queries through associated private endpoints in scope. For security reasons, it is recommended to use PrivateOnly whenever possible to avoid data exfiltration.

* Private Only - This mode allows the connected virtual network to reach only Private Link resources. It is the most secure mode and is set as the default when the `privateEndpoints` parameter is configured.

* Open - Allows the connected virtual network to reach both Private Link resources and the resources not in the AMPLS resource. Data exfiltration cannot be prevented in this mode. - Required: No - Type: object diff --git a/avm/res/insights/private-link-scope/main.bicep b/avm/res/insights/private-link-scope/main.bicep index 50534b03833..a6666eec33d 100644 --- a/avm/res/insights/private-link-scope/main.bicep +++ b/avm/res/insights/private-link-scope/main.bicep @@ -203,6 +203,9 @@ module privateLinkScope_privateEndpoints 'br/public:avm/res/network/private-endp applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName } + dependsOn: [ + privateLinkScope_scopedResource + ] } ] diff --git a/avm/res/insights/private-link-scope/main.json b/avm/res/insights/private-link-scope/main.json index c1efbd235dc..ecb92303a1e 100644 --- a/avm/res/insights/private-link-scope/main.json +++ b/avm/res/insights/private-link-scope/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9210191494689145931" + "version": "0.30.3.12046", + "templateHash": "5947451992668490696" }, "name": "Azure Monitor Private Link Scopes", "description": "This module deploys an Azure Monitor Private Link Scope.", @@ -430,7 +430,7 @@ "accessModeSettings": { "$ref": "#/definitions/accessModeType", "metadata": { - "description": "Optional. Specifies the access mode of ingestion or queries through associated private endpoints in scope. For security reasons, it is recommended to use PrivateOnly whenever possible to avoid data exfiltration.\n\n * Private Only - This mode allows the connected virtual network to reach only Private Link resources. It is the most secure mode and is set as the default when the `privateEndpoints` parameter is configured.\n * Open - Allows the connected virtual network to reach both Private Link resources and the resources not in the AMPLS resource. Data exfiltration cannot be prevented in this mode." + "description": "Optional. Specifies the access mode of ingestion or queries through associated private endpoints in scope. For security reasons, it is recommended to use PrivateOnly whenever possible to avoid data exfiltration.\n\n* Private Only - This mode allows the connected virtual network to reach only Private Link resources. It is the most secure mode and is set as the default when the `privateEndpoints` parameter is configured.\n* Open - Allows the connected virtual network to reach both Private Link resources and the resources not in the AMPLS resource. Data exfiltration cannot be prevented in this mode." } }, "location": { @@ -601,8 +601,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15704480901641345186" + "version": "0.30.3.12046", + "templateHash": "16277811786602972091" }, "name": "Private Link Scope Scoped Resources", "description": "This module deploys a Private Link Scope Scoped Resource.", @@ -1432,7 +1432,8 @@ } }, "dependsOn": [ - "privateLinkScope" + "privateLinkScope", + "privateLinkScope_scopedResource" ] } }, diff --git a/avm/res/insights/private-link-scope/scoped-resource/main.json b/avm/res/insights/private-link-scope/scoped-resource/main.json index 57e0b76f73b..430361afb5d 100644 --- a/avm/res/insights/private-link-scope/scoped-resource/main.json +++ b/avm/res/insights/private-link-scope/scoped-resource/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15704480901641345186" + "version": "0.30.3.12046", + "templateHash": "16277811786602972091" }, "name": "Private Link Scope Scoped Resources", "description": "This module deploys a Private Link Scope Scoped Resource.", From 33030a858a3cdbc466a1f4b4309b3725ea9743a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20H=C3=A9zser?= Date: Tue, 24 Sep 2024 10:49:54 +0200 Subject: [PATCH 30/46] fix: `ptn/deployment-script/import-image-to-acr` overwriteExistingImage (#3350) ## Description Setting overwriteExistingImage to false, breaks the module as it will end with an error. Fixes #3349 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.deployment-script.import-image-to-acr](https://github.com/ReneHezser/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml/badge.svg?branch=import-image-to-acr-fix)](https://github.com/ReneHezser/bicep-registry-modules/actions/workflows/avm.ptn.deployment-script.import-image-to-acr.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [x] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/deployment-script/import-image-to-acr/main.bicep | 2 -- avm/ptn/deployment-script/import-image-to-acr/main.json | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/avm/ptn/deployment-script/import-image-to-acr/main.bicep b/avm/ptn/deployment-script/import-image-to-acr/main.bicep index 61534011ce2..e6c532e5671 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/main.bicep +++ b/avm/ptn/deployment-script/import-image-to-acr/main.bicep @@ -189,8 +189,6 @@ module imageImport 'br/public:avm/res/resources/deployment-script:0.4.0' = { containerGroupName: '${resourceGroup().name}-infrastructure' subnetResourceIds: subnetResourceIds scriptContent: '''#!/bin/bash - set -e - echo "Waiting on RBAC replication ($initialDelay)\n" sleep $initialDelay diff --git a/avm/ptn/deployment-script/import-image-to-acr/main.json b/avm/ptn/deployment-script/import-image-to-acr/main.json index 4f8a4504ae2..6bfe24d5cda 100644 --- a/avm/ptn/deployment-script/import-image-to-acr/main.json +++ b/avm/ptn/deployment-script/import-image-to-acr/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "13166673091432959100" + "version": "0.30.3.12046", + "templateHash": "17375159703541878382" }, "name": "import-image-to-acr", "description": "This modules deployes an image to an Azure Container Registry.", @@ -368,7 +368,7 @@ "value": "[parameters('subnetResourceIds')]" }, "scriptContent": { - "value": "#!/bin/bash\n set -e\n\n echo \"Waiting on RBAC replication ($initialDelay)\\n\"\n sleep $initialDelay\n\n # retry loop to catch errors (usually RBAC delays, but 'Error copying blobs' is also not unheard of)\n retryLoopCount=0\n until [ $retryLoopCount -ge $retryMax ]\n do\n echo \"Importing Image ($retryLoopCount): $imageName into ACR: $acrName\\n\"\n if [ $overwriteExistingImage = 'true' ]; then\n if [ -n \"$sourceRegistryUsername\" ] && [ -n \"$sourceRegistryPassword\" ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --force --username $sourceRegistryUsername --password $sourceRegistryPassword\n else\n az acr import -n $acrName --source $imageName --image $newImageName --force\n fi\n else\n if [ -n \"$sourceRegistryUsername\" ] && [ -n \"$sourceRegistryPassword\" ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --username $sourceRegistryUsername --password $sourceRegistryPassword\n else\n az acr import -n $acrName --source $imageName --image $newImageName\n fi\n fi\n\n sleep $retrySleep\n retryLoopCount=$((retryLoopCount+1))\n done\n\n echo \"done\\n\"" + "value": "#!/bin/bash\n echo \"Waiting on RBAC replication ($initialDelay)\\n\"\n sleep $initialDelay\n\n # retry loop to catch errors (usually RBAC delays, but 'Error copying blobs' is also not unheard of)\n retryLoopCount=0\n until [ $retryLoopCount -ge $retryMax ]\n do\n echo \"Importing Image ($retryLoopCount): $imageName into ACR: $acrName\\n\"\n if [ $overwriteExistingImage = 'true' ]; then\n if [ -n \"$sourceRegistryUsername\" ] && [ -n \"$sourceRegistryPassword\" ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --force --username $sourceRegistryUsername --password $sourceRegistryPassword\n else\n az acr import -n $acrName --source $imageName --image $newImageName --force\n fi\n else\n if [ -n \"$sourceRegistryUsername\" ] && [ -n \"$sourceRegistryPassword\" ]; then\n az acr import -n $acrName --source $imageName --image $newImageName --username $sourceRegistryUsername --password $sourceRegistryPassword\n else\n az acr import -n $acrName --source $imageName --image $newImageName\n fi\n fi\n\n sleep $retrySleep\n retryLoopCount=$((retryLoopCount+1))\n done\n\n echo \"done\\n\"" } }, "template": { From f08da65f2c6d3e53e8339b74bb33bfe8e20c361a Mon Sep 17 00:00:00 2001 From: Chinedum Echeta <60179183+cecheta@users.noreply.github.com> Date: Wed, 25 Sep 2024 01:56:19 -0700 Subject: [PATCH 31/46] feat: `avm/ptn/ai-platform/baseline` Use VNet AVM (#3353) ## Description This PR is essentially a revert of https://github.com/Azure/bicep-registry-modules/pull/3054, plus updating the version of the VNet module to the latest version. The VNet module now creates subnets as child modules, therefore we can use it instead of creating the resources ourselves. ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.ai-platform.baseline](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml/badge.svg?branch=vnet-module)](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [x] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/ai-platform/baseline/README.md | 6 +- avm/ptn/ai-platform/baseline/main.bicep | 88 +- avm/ptn/ai-platform/baseline/main.json | 1706 +++++++++++++++++++-- avm/ptn/ai-platform/baseline/version.json | 2 +- 4 files changed, 1644 insertions(+), 158 deletions(-) diff --git a/avm/ptn/ai-platform/baseline/README.md b/avm/ptn/ai-platform/baseline/README.md index dfaf48f4a58..39a480afb9e 100644 --- a/avm/ptn/ai-platform/baseline/README.md +++ b/avm/ptn/ai-platform/baseline/README.md @@ -51,13 +51,14 @@ By integrating with Microsoft Entra ID for secure identity management and utiliz | `Microsoft.Network/privateDnsZones/SRV` | [2020-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2020-06-01/privateDnsZones/SRV) | | `Microsoft.Network/privateDnsZones/TXT` | [2020-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2020-06-01/privateDnsZones/TXT) | | `Microsoft.Network/privateDnsZones/virtualNetworkLinks` | [2020-06-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2020-06-01/privateDnsZones/virtualNetworkLinks) | -| `Microsoft.Network/privateEndpoints` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | -| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Network/privateEndpoints` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | +| `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/privateEndpoints/privateDnsZoneGroups) | | `Microsoft.Network/publicIPAddresses` | [2023-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-09-01/publicIPAddresses) | | `Microsoft.Network/virtualNetworks` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks) | | `Microsoft.Network/virtualNetworks/subnets` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/subnets) | +| `Microsoft.Network/virtualNetworks/virtualNetworkPeerings` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/virtualNetworkPeerings) | | `Microsoft.OperationalInsights/workspaces` | [2023-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.OperationalInsights/2023-09-01/workspaces) | | `Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.RecoveryServices/2023-01-01/vaults/backupFabrics/protectionContainers/protectedItems) | | `Microsoft.Storage/storageAccounts` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts) | @@ -1150,6 +1151,7 @@ This section gives you an overview of all local-referenced module files (i.e., o | `br/public:avm/res/network/bastion-host:0.2.2` | Remote reference | | `br/public:avm/res/network/network-security-group:0.3.1` | Remote reference | | `br/public:avm/res/network/private-dns-zone:0.3.1` | Remote reference | +| `br/public:avm/res/network/virtual-network:0.4.0` | Remote reference | | `br/public:avm/res/storage/storage-account:0.11.0` | Remote reference | ## Data Collection diff --git a/avm/ptn/ai-platform/baseline/main.bicep b/avm/ptn/ai-platform/baseline/main.bicep index 29b505f083b..cfa8b0f7d78 100644 --- a/avm/ptn/ai-platform/baseline/main.bicep +++ b/avm/ptn/ai-platform/baseline/main.bicep @@ -59,7 +59,7 @@ var createVirtualMachine = createVirtualNetwork && virtualMachineConfiguration.? var createDefaultNsg = virtualNetworkConfiguration.?subnet.networkSecurityGroupResourceId == null -var subnetResourceId = createVirtualNetwork ? virtualNetwork::defaultSubnet.id : null +var subnetResourceId = createVirtualNetwork ? virtualNetwork.outputs.subnetResourceIds[0] : null var mlTargetSubResource = 'amlworkspace' @@ -103,7 +103,7 @@ module storageAccount_privateDnsZones 'br/public:avm/res/network/private-dns-zon name: zone virtualNetworkLinks: [ { - virtualNetworkResourceId: virtualNetwork.id + virtualNetworkResourceId: virtualNetwork.outputs.resourceId } ] } @@ -117,7 +117,7 @@ module workspaceHub_privateDnsZones 'br/public:avm/res/network/private-dns-zone: name: zone virtualNetworkLinks: [ { - virtualNetworkResourceId: virtualNetwork.id + virtualNetworkResourceId: virtualNetwork.outputs.resourceId } ] roleAssignments: managedIdentityName != null @@ -160,47 +160,37 @@ module defaultNetworkSecurityGroup 'br/public:avm/res/network/network-security-g } } -// Not using the br/public:avm/res/network/virtual-network module here to -// allow consumers of the module to add subnets from outside of the module -// https://github.com/Azure/bicep-registry-modules/issues/2689 -resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-01-01' = if (createVirtualNetwork) { - name: virtualNetworkConfiguration.?name ?? 'vnet-${name}' - location: location - tags: tags - properties: { - addressSpace: { - addressPrefixes: [ - virtualNetworkConfiguration.?addressPrefix ?? '10.0.0.0/16' - ] - } - } - - resource defaultSubnet 'subnets@2024-01-01' = { - name: virtualNetworkConfiguration.?subnet.name ?? 'default' - properties: { - addressPrefix: virtualNetworkConfiguration.?subnet.addressPrefix ?? '10.0.0.0/24' - networkSecurityGroup: { - id: createDefaultNsg - ? defaultNetworkSecurityGroup.outputs.resourceId - : virtualNetworkConfiguration.?subnet.networkSecurityGroupResourceId - } - } - } - - resource bastionSubnet 'subnets@2024-01-01' = if (createBastion) { - name: 'AzureBastionSubnet' - properties: { - addressPrefix: bastionConfiguration.?subnetAddressPrefix ?? '10.0.1.0/26' - networkSecurityGroup: bastionConfiguration.?networkSecurityGroupResourceId != null - ? { - id: bastionConfiguration.?networkSecurityGroupResourceId - } - : null - } - - dependsOn: [ - defaultSubnet +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.4.0' = if (createVirtualNetwork) { + name: '${uniqueString(deployment().name, location)}-virtual-network' + params: { + name: virtualNetworkConfiguration.?name ?? 'vnet-${name}' + location: location + enableTelemetry: enableTelemetry + addressPrefixes: [ + virtualNetworkConfiguration.?addressPrefix ?? '10.0.0.0/16' ] + subnets: union( + // The default subnet **must** be the first in the subnets array + [ + { + addressPrefix: virtualNetworkConfiguration.?subnet.addressPrefix ?? '10.0.0.0/24' + name: virtualNetworkConfiguration.?subnet.name ?? 'default' + networkSecurityGroupResourceId: createDefaultNsg + ? defaultNetworkSecurityGroup.outputs.resourceId + : virtualNetworkConfiguration.?subnet.networkSecurityGroupResourceId + } + ], + createBastion + ? [ + { + addressPrefix: bastionConfiguration.?subnetAddressPrefix ?? '10.0.1.0/26' + name: 'AzureBastionSubnet' + networkSecurityGroupResourceId: bastionConfiguration.?networkSecurityGroupResourceId + } + ] + : [] + ) + tags: tags } } @@ -211,7 +201,7 @@ module bastion 'br/public:avm/res/network/bastion-host:0.2.2' = if (createBastio location: location skuName: bastionConfiguration.?sku ?? 'Standard' enableTelemetry: enableTelemetry - virtualNetworkResourceId: virtualNetwork.id + virtualNetworkResourceId: virtualNetwork.outputs.resourceId disableCopyPaste: bastionConfiguration.?disableCopyPaste enableFileCopy: bastionConfiguration.?enableFileCopy enableIpConnect: bastionConfiguration.?enableIpConnect @@ -240,7 +230,7 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:0.5.3' = if (cr { name: virtualMachineConfiguration.?nicConfigurationConfiguration.ipConfigName ?? 'nic-vm-${name}-ipconfig' privateIPAllocationMethod: virtualMachineConfiguration.?nicConfigurationConfiguration.privateIPAllocationMethod ?? 'Dynamic' - subnetResourceId: virtualNetwork::defaultSubnet.id + subnetResourceId: virtualNetwork.outputs.subnetResourceIds[0] } ] } @@ -600,16 +590,16 @@ output workspaceProjectResourceId string = workspaceProject.outputs.resourceId output workspaceProjectName string = workspaceProject.outputs.name @description('The resource ID of the virtual network.') -output virtualNetworkResourceId string = createVirtualNetwork ? virtualNetwork.id : '' +output virtualNetworkResourceId string = createVirtualNetwork ? virtualNetwork.outputs.resourceId : '' @description('The name of the virtual network.') -output virtualNetworkName string = createVirtualNetwork ? virtualNetwork.name : '' +output virtualNetworkName string = createVirtualNetwork ? virtualNetwork.outputs.name : '' @description('The resource ID of the subnet in the virtual network.') -output virtualNetworkSubnetResourceId string = createVirtualNetwork ? virtualNetwork::defaultSubnet.id : '' +output virtualNetworkSubnetResourceId string = createVirtualNetwork ? virtualNetwork.outputs.subnetResourceIds[0] : '' @description('The name of the subnet in the virtual network.') -output virtualNetworkSubnetName string = createVirtualNetwork ? virtualNetwork::defaultSubnet.name : '' +output virtualNetworkSubnetName string = createVirtualNetwork ? virtualNetwork.outputs.subnetNames[0] : '' @description('The resource ID of the Azure Bastion host.') output bastionResourceId string = createBastion ? bastion.outputs.resourceId : '' diff --git a/avm/ptn/ai-platform/baseline/main.json b/avm/ptn/ai-platform/baseline/main.json index a6eafbcbbd2..9da789cd5fc 100644 --- a/avm/ptn/ai-platform/baseline/main.json +++ b/avm/ptn/ai-platform/baseline/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.30.3.12046", - "templateHash": "12442663604053803236" + "templateHash": "14736544493210620894" }, "name": "AI Platform Baseline", "description": "This module provides a secure and scalable environment for deploying AI applications on Azure.\nThe module encompasses all essential components required for building, managing, and observing AI solutions, including a machine learning workspace, observability tools, and necessary data management services.\nBy integrating with Microsoft Entra ID for secure identity management and utilizing private endpoints for services like Key Vault and Blob Storage, the module ensures secure communication and data access.", @@ -823,7 +823,6 @@ "createBastion": "[and(variables('createVirtualNetwork'), not(equals(tryGet(parameters('bastionConfiguration'), 'enabled'), false())))]", "createVirtualMachine": "[and(variables('createVirtualNetwork'), not(equals(tryGet(parameters('virtualMachineConfiguration'), 'enabled'), false())))]", "createDefaultNsg": "[equals(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'networkSecurityGroupResourceId'), null())]", - "subnetResourceId": "[if(variables('createVirtualNetwork'), resourceId('Microsoft.Network/virtualNetworks/subnets', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))), coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'name'), 'default')), null())]", "mlTargetSubResource": "amlworkspace", "mlPrivateDnsZones": { "privatelink.api.azureml.ms": "[variables('mlTargetSubResource')]", @@ -835,36 +834,6 @@ } }, "resources": { - "virtualNetwork::defaultSubnet": { - "condition": "[variables('createVirtualNetwork')]", - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-01-01", - "name": "[format('{0}/{1}', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))), coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'name'), 'default'))]", - "properties": { - "addressPrefix": "[coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'addressPrefix'), '10.0.0.0/24')]", - "networkSecurityGroup": { - "id": "[if(variables('createDefaultNsg'), reference('defaultNetworkSecurityGroup').outputs.resourceId.value, tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'networkSecurityGroupResourceId'))]" - } - }, - "dependsOn": [ - "defaultNetworkSecurityGroup", - "virtualNetwork" - ] - }, - "virtualNetwork::bastionSubnet": { - "condition": "[and(variables('createVirtualNetwork'), variables('createBastion'))]", - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-01-01", - "name": "[format('{0}/{1}', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))), 'AzureBastionSubnet')]", - "properties": { - "addressPrefix": "[coalesce(tryGet(parameters('bastionConfiguration'), 'subnetAddressPrefix'), '10.0.1.0/26')]", - "networkSecurityGroup": "[if(not(equals(tryGet(parameters('bastionConfiguration'), 'networkSecurityGroupResourceId'), null())), createObject('id', tryGet(parameters('bastionConfiguration'), 'networkSecurityGroupResourceId')), null())]" - }, - "dependsOn": [ - "virtualNetwork::defaultSubnet", - "virtualNetwork" - ] - }, "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", @@ -885,21 +854,6 @@ } } }, - "virtualNetwork": { - "condition": "[variables('createVirtualNetwork')]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-01-01", - "name": "[coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name')))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'addressPrefix'), '10.0.0.0/16')]" - ] - } - } - }, "userAssignedIdentity": { "condition": "[not(equals(parameters('managedIdentityName'), null()))]", "existing": true, @@ -949,7 +903,7 @@ "virtualNetworkLinks": { "value": [ { - "virtualNetworkResourceId": "[resourceId('Microsoft.Network/virtualNetworks', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))))]" + "virtualNetworkResourceId": "[reference('virtualNetwork').outputs.resourceId.value]" } ] } @@ -3831,7 +3785,7 @@ "virtualNetworkLinks": { "value": [ { - "virtualNetworkResourceId": "[resourceId('Microsoft.Network/virtualNetworks', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))))]" + "virtualNetworkResourceId": "[reference('virtualNetwork').outputs.resourceId.value]" } ] }, @@ -7235,61 +7189,1578 @@ "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" }, "dependsOn": [ - "networkSecurityGroup" + "networkSecurityGroup" + ] + }, + "networkSecurityGroup_diagnosticSettings": { + "copy": { + "name": "networkSecurityGroup_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "networkSecurityGroup" + ] + }, + "networkSecurityGroup_roleAssignments": { + "copy": { + "name": "networkSecurityGroup_roleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[guid(resourceId('Microsoft.Network/networkSecurityGroups', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "properties": { + "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "networkSecurityGroup" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the network security group was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the network security group." + }, + "value": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the network security group." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('networkSecurityGroup', '2023-11-01', 'full').location]" + } + } + } + } + }, + "virtualNetwork": { + "condition": "[variables('createVirtualNetwork')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtual-network', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name')))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "addressPrefixes": { + "value": [ + "[coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'addressPrefix'), '10.0.0.0/16')]" + ] + }, + "subnets": { + "value": "[union(createArray(createObject('addressPrefix', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'addressPrefix'), '10.0.0.0/24'), 'name', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'name'), 'default'), 'networkSecurityGroupResourceId', if(variables('createDefaultNsg'), reference('defaultNetworkSecurityGroup').outputs.resourceId.value, tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'networkSecurityGroupResourceId')))), if(variables('createBastion'), createArray(createObject('addressPrefix', coalesce(tryGet(parameters('bastionConfiguration'), 'subnetAddressPrefix'), '10.0.1.0/26'), 'name', 'AzureBastionSubnet', 'networkSecurityGroupResourceId', tryGet(parameters('bastionConfiguration'), 'networkSecurityGroupResourceId'))), createArray()))]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "15949466154563447171" + }, + "name": "Virtual Networks", + "description": "This module deploys a Virtual Network (vNet).", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "peeringType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be peer-localVnetName-remoteVnetName." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + }, + "remotePeeringEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Deploy the outbound and the inbound peering." + } + }, + "remotePeeringName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the VNET Peering resource in the remove Virtual Network. If not provided, default value will be peer-remoteVnetName-localVnetName." + } + }, + "remotePeeringAllowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "remotePeeringAllowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "remotePeeringAllowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "remotePeeringDoNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "remotePeeringUseRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private link service in the subnet." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "routeTableResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "serviceEndpoints": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Virtual Network (vNet)." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network." + } + }, + "virtualNetworkBgpCommunity": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The BGP community associated with the virtual network." + } + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An Array of subnets to deploy to the Virtual Network." + } + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. DNS Servers associated to the Virtual Network." + } + }, + "ddosProtectionPlanResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the DDoS protection plan to assign the VNET to. If it's left blank, DDoS protection will not be configured. If it's provided, the VNET created by this template will be attached to the referenced DDoS protection plan. The DDoS protection plan can exist in the same or in a different subscription." + } + }, + "peerings": { + "type": "array", + "items": { + "$ref": "#/definitions/peeringType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Virtual Network Peering configurations." + } + }, + "vnetEncryption": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates if encryption is enabled on virtual network and if VM without encryption is allowed in encrypted VNet. Requires the EnableVNetEncryption feature to be registered for the subscription and a supported region to use this property." + } + }, + "vnetEncryptionEnforcement": { + "type": "string", + "defaultValue": "AllowUnencrypted", + "allowedValues": [ + "AllowUnencrypted", + "DropUnencrypted" + ], + "metadata": { + "description": "Optional. If the encrypted VNet allows VM that does not support encryption. Can only be used when vnetEncryption is enabled." + } + }, + "flowTimeoutInMinutes": { + "type": "int", + "defaultValue": 0, + "maxValue": 30, + "metadata": { + "description": "Optional. The flow timeout in minutes for the Virtual Network, which is used to enable connection tracking for intra-VM flows. Possible values are between 4 and 30 minutes. Default value 0 will set the property to null." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "enableVmProtection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates if VM protection is enabled for all the subnets in the virtual network." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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" + } + } + } + } + }, + "virtualNetwork": { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "addressSpace": { + "addressPrefixes": "[parameters('addressPrefixes')]" + }, + "bgpCommunities": "[if(not(empty(parameters('virtualNetworkBgpCommunity'))), createObject('virtualNetworkCommunity', parameters('virtualNetworkBgpCommunity')), null())]", + "ddosProtectionPlan": "[if(not(empty(parameters('ddosProtectionPlanResourceId'))), createObject('id', parameters('ddosProtectionPlanResourceId')), null())]", + "dhcpOptions": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', array(parameters('dnsServers'))), null())]", + "enableDdosProtection": "[not(empty(parameters('ddosProtectionPlanResourceId')))]", + "encryption": "[if(equals(parameters('vnetEncryption'), true()), createObject('enabled', parameters('vnetEncryption'), 'enforcement', parameters('vnetEncryptionEnforcement')), null())]", + "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]", + "enableVmProtection": "[parameters('enableVmProtection')]" + } + }, + "virtualNetwork_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_diagnosticSettings": { + "copy": { + "name": "virtualNetwork_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_roleAssignments": { + "copy": { + "name": "virtualNetwork_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "virtualNetwork_subnets": { + "copy": { + "name": "virtualNetwork_subnets", + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-subnet-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('subnets'), createArray())[copyIndex()].name]" + }, + "addressPrefix": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefix')]" + }, + "addressPrefixes": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefixes')]" + }, + "applicationGatewayIPConfigurations": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'applicationGatewayIPConfigurations')]" + }, + "delegation": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'delegation')]" + }, + "natGatewayResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'natGatewayResourceId')]" + }, + "networkSecurityGroupResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'networkSecurityGroupResourceId')]" + }, + "privateEndpointNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateEndpointNetworkPolicies')]" + }, + "privateLinkServiceNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateLinkServiceNetworkPolicies')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "routeTableResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'routeTableResourceId')]" + }, + "serviceEndpointPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpointPolicies')]" + }, + "serviceEndpoints": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpoints')]" + }, + "defaultOutboundAccess": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'defaultOutboundAccess')]" + }, + "sharingScope": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'sharingScope')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5699372618313647761" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Requird. The Name of the subnet resource." + } + }, + "virtualNetworkName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual network. Required if the template is used in a standalone deployment." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "routeTableResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpoints": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "Disabled", + "Enabled", + "" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private link service in the subnet." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." + } + }, + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "virtualNetwork": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "serviceEndpoints", + "count": "[length(parameters('serviceEndpoints'))]", + "input": { + "service": "[parameters('serviceEndpoints')[copyIndex('serviceEndpoints')]]" + } + } + ], + "addressPrefix": "[parameters('addressPrefix')]", + "addressPrefixes": "[parameters('addressPrefixes')]", + "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]", + "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]", + "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]", + "delegations": "[if(not(empty(parameters('delegation'))), createArray(createObject('name', parameters('delegation'), 'properties', createObject('serviceName', parameters('delegation')))), createArray())]", + "privateEndpointNetworkPolicies": "[if(not(empty(parameters('privateEndpointNetworkPolicies'))), parameters('privateEndpointNetworkPolicies'), null())]", + "privateLinkServiceNetworkPolicies": "[if(not(empty(parameters('privateLinkServiceNetworkPolicies'))), parameters('privateLinkServiceNetworkPolicies'), null())]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "subnet_roleAssignments": { + "copy": { + "name": "subnet_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "subnet" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]" + }, + "addressPrefix": { + "type": "string", + "metadata": { + "description": "The address prefix for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefix'), '')]" + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "List of address prefixes for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefixes'), createArray())]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" ] }, - "networkSecurityGroup_diagnosticSettings": { + "virtualNetwork_peering_local": { "copy": { - "name": "networkSecurityGroup_diagnosticSettings", - "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + "name": "virtualNetwork_peering_local", + "count": "[length(coalesce(parameters('peerings'), createArray()))]" }, - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetworkPeering-local-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { - "copy": [ - { - "name": "logs", - "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", - "input": { - "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", - "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", - "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "localVnetName": { + "value": "[parameters('name')]" + }, + "remoteVirtualNetworkResourceId": { + "value": "[coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'name')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'doNotVerifyRemoteGateways')]" + }, + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'useRemoteGateways')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + } + }, + "localVnetName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", + "properties": { + "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", + "allowGatewayTransit": "[parameters('allowGatewayTransit')]", + "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]", + "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", + "useRemoteGateways": "[parameters('useRemoteGateways')]", + "remoteVirtualNetwork": { + "id": "[parameters('remoteVirtualNetworkResourceId')]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]" } } - ], - "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", - "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", - "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", - "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", - "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", - "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + } }, "dependsOn": [ - "networkSecurityGroup" + "virtualNetwork" ] }, - "networkSecurityGroup_roleAssignments": { + "virtualNetwork_peering_remote": { "copy": { - "name": "networkSecurityGroup_roleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + "name": "virtualNetwork_peering_remote", + "count": "[length(coalesce(parameters('peerings'), createArray()))]" }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", - "name": "[guid(resourceId('Microsoft.Network/networkSecurityGroups', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "condition": "[coalesce(tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringEnabled'), false())]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetworkPeering-remote-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[2]]", + "resourceGroup": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[4]]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", - "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "localVnetName": { + "value": "[last(split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/'))]" + }, + "remoteVirtualNetworkResourceId": { + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringName')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways')]" + }, + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringUseRemoteGateways')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + } + }, + "localVnetName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If we need to verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", + "properties": { + "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", + "allowGatewayTransit": "[parameters('allowGatewayTransit')]", + "allowVirtualNetworkAccess": "[parameters('allowVirtualNetworkAccess')]", + "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", + "useRemoteGateways": "[parameters('useRemoteGateways')]", + "remoteVirtualNetwork": { + "id": "[parameters('remoteVirtualNetworkResourceId')]" + } + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the virtual network peering was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the virtual network peering." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the virtual network peering." + }, + "value": "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings', parameters('localVnetName'), parameters('name'))]" + } + } + } }, "dependsOn": [ - "networkSecurityGroup" + "virtualNetwork" ] } }, @@ -7297,34 +8768,57 @@ "resourceGroupName": { "type": "string", "metadata": { - "description": "The resource group the network security group was deployed into." + "description": "The resource group the virtual network was deployed into." }, "value": "[resourceGroup().name]" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the network security group." + "description": "The resource ID of the virtual network." }, - "value": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('name'))]" + "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" }, "name": { "type": "string", "metadata": { - "description": "The name of the network security group." + "description": "The name of the virtual network." }, "value": "[parameters('name')]" }, + "subnetNames": { + "type": "array", + "metadata": { + "description": "The names of the deployed subnets." + }, + "copy": { + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.name.value]" + } + }, + "subnetResourceIds": { + "type": "array", + "metadata": { + "description": "The resource IDs of the deployed subnets." + }, + "copy": { + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.resourceId.value]" + } + }, "location": { "type": "string", "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('networkSecurityGroup', '2023-11-01', 'full').location]" + "value": "[reference('virtualNetwork', '2024-01-01', 'full').location]" } } } - } + }, + "dependsOn": [ + "defaultNetworkSecurityGroup" + ] }, "bastion": { "condition": "[variables('createBastion')]", @@ -7350,7 +8844,7 @@ "value": "[parameters('enableTelemetry')]" }, "virtualNetworkResourceId": { - "value": "[resourceId('Microsoft.Network/virtualNetworks', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))))]" + "value": "[reference('virtualNetwork').outputs.resourceId.value]" }, "disableCopyPaste": { "value": "[tryGet(parameters('bastionConfiguration'), 'disableCopyPaste')]" @@ -8549,7 +10043,7 @@ { "name": "[coalesce(tryGet(parameters('virtualMachineConfiguration'), 'nicConfigurationConfiguration', 'ipConfigName'), format('nic-vm-{0}-ipconfig', parameters('name')))]", "privateIPAllocationMethod": "[coalesce(tryGet(parameters('virtualMachineConfiguration'), 'nicConfigurationConfiguration', 'privateIPAllocationMethod'), 'Dynamic')]", - "subnetResourceId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))), coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'name'), 'default'))]" + "subnetResourceId": "[reference('virtualNetwork').outputs.subnetResourceIds.value[0]]" } ] } @@ -13968,7 +15462,7 @@ } }, "dependsOn": [ - "virtualNetwork::defaultSubnet" + "virtualNetwork" ] }, "keyVault": { @@ -16805,7 +18299,7 @@ "bypass": "AzureServices" } }, - "privateEndpoints": "[if(not(equals(variables('subnetResourceId'), null())), createObject('value', map(items(variables('storagePrivateDnsZones')), lambda('zone', createObject('name', format('pep-{0}-{1}', lambdaVariables('zone').value, parameters('name')), 'customNetworkInterfaceName', format('nic-{0}-{1}', lambdaVariables('zone').value, parameters('name')), 'service', lambdaVariables('zone').value, 'subnetResourceId', coalesce(variables('subnetResourceId'), ''), 'privateDnsZoneResourceIds', createArray(resourceId('Microsoft.Network/privateDnsZones', lambdaVariables('zone').key)))))), createObject('value', null()))]", + "privateEndpoints": "[if(not(equals(if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.subnetResourceIds.value[0], null()), null())), createObject('value', map(items(variables('storagePrivateDnsZones')), lambda('zone', createObject('name', format('pep-{0}-{1}', lambdaVariables('zone').value, parameters('name')), 'customNetworkInterfaceName', format('nic-{0}-{1}', lambdaVariables('zone').value, parameters('name')), 'service', lambdaVariables('zone').value, 'subnetResourceId', coalesce(if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.subnetResourceIds.value[0], null()), ''), 'privateDnsZoneResourceIds', createArray(resourceId('Microsoft.Network/privateDnsZones', lambdaVariables('zone').key)))))), createObject('value', null()))]", "roleAssignments": "[if(not(equals(parameters('managedIdentityName'), null())), createObject('value', createArray(createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Contributor', 'principalType', 'ServicePrincipal'), createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', 'Storage Blob Data Contributor', 'principalType', 'ServicePrincipal'), createObject('principalId', reference('userAssignedIdentity').principalId, 'roleDefinitionIdOrName', '69566ab7-960f-475b-8e7c-b3118f30c6bd', 'principalType', 'ServicePrincipal'))), createObject('value', null()))]", "tags": { "value": "[parameters('tags')]" @@ -21469,9 +22963,9 @@ } }, "dependsOn": [ - "virtualNetwork::defaultSubnet", "storageAccount_privateDnsZones", - "userAssignedIdentity" + "userAssignedIdentity", + "virtualNetwork" ] }, "containerRegistry": { @@ -24315,7 +25809,7 @@ "outboundRules": "[tryGet(parameters('workspaceConfiguration'), 'networkOutboundRules')]" } }, - "privateEndpoints": "[if(not(equals(variables('subnetResourceId'), null())), createObject('value', createArray(createObject('name', format('pep-{0}-{1}', variables('mlTargetSubResource'), parameters('name')), 'customNetworkInterfaceName', format('nic-{0}-{1}', variables('mlTargetSubResource'), parameters('name')), 'service', variables('mlTargetSubResource'), 'subnetResourceId', coalesce(variables('subnetResourceId'), ''), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', map(objectKeys(variables('mlPrivateDnsZones')), lambda('zone', createObject('name', replace(lambdaVariables('zone'), '.', '-'), 'privateDnsZoneResourceId', resourceId('Microsoft.Network/privateDnsZones', lambdaVariables('zone'))))))))), createObject('value', null()))]", + "privateEndpoints": "[if(not(equals(if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.subnetResourceIds.value[0], null()), null())), createObject('value', createArray(createObject('name', format('pep-{0}-{1}', variables('mlTargetSubResource'), parameters('name')), 'customNetworkInterfaceName', format('nic-{0}-{1}', variables('mlTargetSubResource'), parameters('name')), 'service', variables('mlTargetSubResource'), 'subnetResourceId', coalesce(if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.subnetResourceIds.value[0], null()), ''), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', map(objectKeys(variables('mlPrivateDnsZones')), lambda('zone', createObject('name', replace(lambdaVariables('zone'), '.', '-'), 'privateDnsZoneResourceId', resourceId('Microsoft.Network/privateDnsZones', lambdaVariables('zone'))))))))), createObject('value', null()))]", "systemDatastoresAuthMode": { "value": "identity" }, @@ -26563,10 +28057,10 @@ "dependsOn": [ "applicationInsights", "containerRegistry", - "virtualNetwork::defaultSubnet", "keyVault", "storageAccount", "userAssignedIdentity", + "virtualNetwork", "workspaceHub_privateDnsZones" ] }, @@ -29005,28 +30499,28 @@ "metadata": { "description": "The resource ID of the virtual network." }, - "value": "[if(variables('createVirtualNetwork'), resourceId('Microsoft.Network/virtualNetworks', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name')))), '')]" + "value": "[if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.resourceId.value, '')]" }, "virtualNetworkName": { "type": "string", "metadata": { "description": "The name of the virtual network." }, - "value": "[if(variables('createVirtualNetwork'), coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))), '')]" + "value": "[if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.name.value, '')]" }, "virtualNetworkSubnetResourceId": { "type": "string", "metadata": { "description": "The resource ID of the subnet in the virtual network." }, - "value": "[if(variables('createVirtualNetwork'), resourceId('Microsoft.Network/virtualNetworks/subnets', coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'name'), format('vnet-{0}', parameters('name'))), coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'name'), 'default')), '')]" + "value": "[if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.subnetResourceIds.value[0], '')]" }, "virtualNetworkSubnetName": { "type": "string", "metadata": { "description": "The name of the subnet in the virtual network." }, - "value": "[if(variables('createVirtualNetwork'), coalesce(tryGet(parameters('virtualNetworkConfiguration'), 'subnet', 'name'), 'default'), '')]" + "value": "[if(variables('createVirtualNetwork'), reference('virtualNetwork').outputs.subnetNames.value[0], '')]" }, "bastionResourceId": { "type": "string", diff --git a/avm/ptn/ai-platform/baseline/version.json b/avm/ptn/ai-platform/baseline/version.json index 41fc8c654f4..c332ff1f3ab 100644 --- a/avm/ptn/ai-platform/baseline/version.json +++ b/avm/ptn/ai-platform/baseline/version.json @@ -1,6 +1,6 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.5", + "version": "0.6", "pathFilters": [ "./main.json" ] From 51596f2d87a24e51f0ef8bf37e7b8f2af5d23d79 Mon Sep 17 00:00:00 2001 From: Chinedum Echeta <60179183+cecheta@users.noreply.github.com> Date: Wed, 25 Sep 2024 02:40:38 -0700 Subject: [PATCH 32/46] docs: `avm/ptn/ai-platform/baseline` Update README (#3358) ## Description Updating the README, which changed since the last PR was raised ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.ai-platform.baseline](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml/badge.svg?branch=readme)](https://github.com/cecheta/bicep-registry-modules/actions/workflows/avm.ptn.ai-platform.baseline.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/ai-platform/baseline/README.md | 12 ++++++------ avm/ptn/ai-platform/baseline/main.json | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/avm/ptn/ai-platform/baseline/README.md b/avm/ptn/ai-platform/baseline/README.md index 39a480afb9e..709ebd90fed 100644 --- a/avm/ptn/ai-platform/baseline/README.md +++ b/avm/ptn/ai-platform/baseline/README.md @@ -65,14 +65,14 @@ By integrating with Microsoft Entra ID for secure identity management and utiliz | `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | | `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | | `Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers/immutabilityPolicies) | -| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/fileServices) | | `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | -| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/localUsers) | | `Microsoft.Storage/storageAccounts/managementPolicies` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/managementPolicies) | -| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | -| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices/tables) | ## Usage examples diff --git a/avm/ptn/ai-platform/baseline/main.json b/avm/ptn/ai-platform/baseline/main.json index 9da789cd5fc..cbb20a89d40 100644 --- a/avm/ptn/ai-platform/baseline/main.json +++ b/avm/ptn/ai-platform/baseline/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.3.12046", - "templateHash": "14736544493210620894" + "version": "0.30.23.60470", + "templateHash": "14190804454906731332" }, "name": "AI Platform Baseline", "description": "This module provides a secure and scalable environment for deploying AI applications on Azure.\nThe module encompasses all essential components required for building, managing, and observing AI solutions, including a machine learning workspace, observability tools, and necessary data management services.\nBy integrating with Microsoft Entra ID for secure identity management and utilizing private endpoints for services like Key Vault and Blob Storage, the module ensures secure communication and data access.", From d213e8a8a0149f9ad72cfb037b0917af552d6af1 Mon Sep 17 00:00:00 2001 From: Nate Arnold Date: Wed, 25 Sep 2024 05:57:42 -0600 Subject: [PATCH 33/46] fix: Added depends_on to flexibleserver_administrators (#3355) ## Description Fixes #3257 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.db-for-postgre-sql.flexible-server](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml/badge.svg?branch=avm%2Fres%2Fdb-for-postgre-sql%2Fflexible-server2)](https://github.com/arnoldna/bicep-registry-modules/actions/workflows/avm.res.db-for-postgre-sql.flexible-server.yml) | ## Type of Change - [X] Update to CI Environment or utilities (Non-module affecting changes) - [X] Azure Verified Module updates: - [X] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [X] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/db-for-postgre-sql/flexible-server/main.bicep | 3 +++ avm/res/db-for-postgre-sql/flexible-server/main.json | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/avm/res/db-for-postgre-sql/flexible-server/main.bicep b/avm/res/db-for-postgre-sql/flexible-server/main.bicep index 19c07e2e2fc..7b24eb2bc7f 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/main.bicep +++ b/avm/res/db-for-postgre-sql/flexible-server/main.bicep @@ -377,6 +377,9 @@ module flexibleServer_administrators 'administrator/main.bicep' = [ principalType: administrator.principalType tenantId: administrator.?tenantId ?? tenant().tenantId } + dependsOn: [ + flexibleServer_configurations + ] } ] diff --git a/avm/res/db-for-postgre-sql/flexible-server/main.json b/avm/res/db-for-postgre-sql/flexible-server/main.json index 9472f5d8f90..3e71af1f04f 100644 --- a/avm/res/db-for-postgre-sql/flexible-server/main.json +++ b/avm/res/db-for-postgre-sql/flexible-server/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "745168326315156090" + "templateHash": "16193893481581771669" }, "name": "DBforPostgreSQL Flexible Servers", "description": "This module deploys a DBforPostgreSQL Flexible Server.", @@ -1193,7 +1193,8 @@ } }, "dependsOn": [ - "flexibleServer" + "flexibleServer", + "flexibleServer_configurations" ] } }, From 5fc18d3c5929f8cb031b371bf6974d55b1e803c0 Mon Sep 17 00:00:00 2001 From: hundredacres Date: Wed, 25 Sep 2024 09:06:49 -0700 Subject: [PATCH 34/46] fix: Resolve issue when bastion/firewall is not deployed (#3356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Resolves issue when bastion and firewall are not deployed. Added test to confirm. Fixes #3343 Closes #3343 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.network.hub-networking](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.ptn.network.hub-networking.yml/badge.svg?branch=fix%2Fissues%2F3343)](https://github.com/hundredacres/bicep-registry-modules/actions/workflows/avm.ptn.network.hub-networking.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [X] Azure Verified Module updates: - [X] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [X] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Máté Barabás Co-authored-by: Rainer Halanek <61878316+rahalan@users.noreply.github.com> Co-authored-by: JFolberth --- avm/ptn/network/hub-networking/README.md | 173 +++++++++++++++++- avm/ptn/network/hub-networking/main.bicep | 28 +-- avm/ptn/network/hub-networking/main.json | 30 +-- .../tests/e2e/no-addons/main.test.bicep | 124 +++++++++++++ 4 files changed, 321 insertions(+), 34 deletions(-) create mode 100644 avm/ptn/network/hub-networking/tests/e2e/no-addons/main.test.bicep diff --git a/avm/ptn/network/hub-networking/README.md b/avm/ptn/network/hub-networking/README.md index 9dd17dbb8d6..e033907b826 100644 --- a/avm/ptn/network/hub-networking/README.md +++ b/avm/ptn/network/hub-networking/README.md @@ -38,7 +38,8 @@ The following section provides usage examples for the module, which were used to - [Using only defaults](#example-1-using-only-defaults) - [Using large parameter set](#example-2-using-large-parameter-set) -- [WAF-aligned](#example-3-waf-aligned) +- [No Addons](#example-3-no-addons) +- [WAF-aligned](#example-4-waf-aligned) ### Example 1: _Using only defaults_ @@ -448,7 +449,175 @@ module hubNetworking 'br/public:avm/ptn/network/hub-networking:' = {

-### Example 3: _WAF-aligned_ +### Example 3: _No Addons_ + +This instance deploys the module with no add-ons (Firewall / Bastion) enabled. + + +

+ +via Bicep module + +```bicep +module hubNetworking 'br/public:avm/ptn/network/hub-networking:' = { + name: 'hubNetworkingDeployment' + params: { + hubVirtualNetworks: { + hub1: { + addressPrefixes: '' + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: '' + eventHubName: '' + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: '' + workspaceResourceId: '' + } + ] + dnsServers: [ + '10.0.1.6' + '10.0.1.7' + ] + enableAzureFirewall: false + enableBastion: false + enablePeering: false + enableTelemetry: true + flowTimeoutInMinutes: 30 + location: '' + lock: { + kind: 'CanNotDelete' + name: 'hub1Lock' + } + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + addressPrefix: '' + name: 'GatewaySubnet' + } + { + addressPrefix: '' + name: 'AzureFirewallSubnet' + } + { + addressPrefix: '' + name: 'AzureBastionSubnet' + } + ] + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + } + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "hubVirtualNetworks": { + "value": { + "hub1": { + "addressPrefixes": "", + "diagnosticSettings": [ + { + "eventHubAuthorizationRuleResourceId": "", + "eventHubName": "", + "metricCategories": [ + { + "category": "AllMetrics" + } + ], + "name": "customSetting", + "storageAccountResourceId": "", + "workspaceResourceId": "" + } + ], + "dnsServers": [ + "10.0.1.6", + "10.0.1.7" + ], + "enableAzureFirewall": false, + "enableBastion": false, + "enablePeering": false, + "enableTelemetry": true, + "flowTimeoutInMinutes": 30, + "location": "", + "lock": { + "kind": "CanNotDelete", + "name": "hub1Lock" + }, + "routes": [ + { + "name": "defaultRoute", + "properties": { + "addressPrefix": "0.0.0.0/0", + "nextHopType": "Internet" + } + } + ], + "subnets": [ + { + "addressPrefix": "", + "name": "GatewaySubnet" + }, + { + "addressPrefix": "", + "name": "AzureFirewallSubnet" + }, + { + "addressPrefix": "", + "name": "AzureBastionSubnet" + } + ], + "tags": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + }, + "vnetEncryption": false, + "vnetEncryptionEnforcement": "AllowUnencrypted" + } + } + }, + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 4: _WAF-aligned_ This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. diff --git a/avm/ptn/network/hub-networking/main.bicep b/avm/ptn/network/hub-networking/main.bicep index 981cdd4effd..2f222c6bc0c 100644 --- a/avm/ptn/network/hub-networking/main.bicep +++ b/avm/ptn/network/hub-networking/main.bicep @@ -245,22 +245,26 @@ output hubVirtualNetworks object[] = [ @description('Array of hub bastion resources.') output hubBastions object[] = [ - for (hub, index) in items(hubVirtualNetworks ?? {}): { - resourceGroupName: hubBastion[index].outputs.resourceGroupName - location: hubBastion[index].outputs.location - name: hubBastion[index].outputs.name - resourceId: hubBastion[index].outputs.resourceId - } + for (hub, index) in items(hubVirtualNetworks ?? {}): (hub.value.enableBastion) + ? { + resourceGroupName: hubBastion[index].outputs.resourceGroupName + location: hubBastion[index].outputs.location + name: hubBastion[index].outputs.name + resourceId: hubBastion[index].outputs.resourceId + } + : {} ] @description('Array of hub Azure Firewall resources.') output hubAzureFirewalls object[] = [ - for (hub, index) in items(hubVirtualNetworks ?? {}): { - resourceGroupName: hubAzureFirewall[index].outputs.resourceGroupName - location: hubAzureFirewall[index].outputs.location - name: hubAzureFirewall[index].outputs.name - resourceId: hubAzureFirewall[index].outputs.resourceId - } + for (hub, index) in items(hubVirtualNetworks ?? {}): (hub.value.enableAzureFirewall) + ? { + resourceGroupName: hubAzureFirewall[index].outputs.resourceGroupName + location: hubAzureFirewall[index].outputs.location + name: hubAzureFirewall[index].outputs.name + resourceId: hubAzureFirewall[index].outputs.resourceId + } + : {} ] @description('The subnets of the hub virtual network.') diff --git a/avm/ptn/network/hub-networking/main.json b/avm/ptn/network/hub-networking/main.json index 8fb5cb887bc..6cac9667579 100644 --- a/avm/ptn/network/hub-networking/main.json +++ b/avm/ptn/network/hub-networking/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9188161100861636713" + "version": "0.30.3.12046", + "templateHash": "8969613921663763778" }, "name": "Hub Networking", "description": "This module is designed to simplify the creation of multi-region hub networks in Azure. It will create a number of virtual networks and subnets, and optionally peer them together in a mesh topology with routing.", @@ -2315,8 +2315,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15958982442955537466" + "version": "0.30.3.12046", + "templateHash": "5568850224456572684" }, "name": "Virtual Networks", "description": "This module deploys a Virtual Network.", @@ -6436,8 +6436,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "13190798974838698070" + "version": "0.30.3.12046", + "templateHash": "16563975082451649304" }, "name": "Existing Virtual Network Subnets", "description": "This module retrieves an existing Virtual Network Subnet.", @@ -6519,8 +6519,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "11735652948112662202" + "version": "0.30.3.12046", + "templateHash": "10294962787410461549" }, "name": "Virtual Network Subnets", "description": "This module deploys a Virtual Network Subnet.", @@ -6859,12 +6859,7 @@ }, "copy": { "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", - "input": { - "resourceGroupName": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.resourceGroupName.value]", - "location": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.location.value]", - "name": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.name.value]", - "resourceId": "[reference(format('hubBastion[{0}]', copyIndex())).outputs.resourceId.value]" - } + "input": "[if(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enableBastion, createObject('resourceGroupName', reference(format('hubBastion[{0}]', copyIndex())).outputs.resourceGroupName.value, 'location', reference(format('hubBastion[{0}]', copyIndex())).outputs.location.value, 'name', reference(format('hubBastion[{0}]', copyIndex())).outputs.name.value, 'resourceId', reference(format('hubBastion[{0}]', copyIndex())).outputs.resourceId.value), createObject())]" } }, "hubAzureFirewalls": { @@ -6877,12 +6872,7 @@ }, "copy": { "count": "[length(items(coalesce(parameters('hubVirtualNetworks'), createObject())))]", - "input": { - "resourceGroupName": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.resourceGroupName.value]", - "location": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.location.value]", - "name": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.name.value]", - "resourceId": "[reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.resourceId.value]" - } + "input": "[if(items(coalesce(parameters('hubVirtualNetworks'), createObject()))[copyIndex()].value.enableAzureFirewall, createObject('resourceGroupName', reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.resourceGroupName.value, 'location', reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.location.value, 'name', reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.name.value, 'resourceId', reference(format('hubAzureFirewall[{0}]', copyIndex())).outputs.resourceId.value), createObject())]" } }, "hubVirtualNetworkSubnets": { diff --git a/avm/ptn/network/hub-networking/tests/e2e/no-addons/main.test.bicep b/avm/ptn/network/hub-networking/tests/e2e/no-addons/main.test.bicep new file mode 100644 index 00000000000..12d7a9613a0 --- /dev/null +++ b/avm/ptn/network/hub-networking/tests/e2e/no-addons/main.test.bicep @@ -0,0 +1,124 @@ +targetScope = 'subscription' + +metadata name = 'No Addons' +metadata description = 'This instance deploys the module with no add-ons (Firewall / Bastion) enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.hub-networking-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nhnnadd' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: resourceLocation +} + +// Diagnostics +// =========== +module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + params: { + storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' + eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // + +var addressPrefix = '10.0.0.0/16' + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + // You parameters go here + location: resourceLocation + hubVirtualNetworks: { + hub1: { + addressPrefixes: array(addressPrefix) + enableAzureFirewall: false + enableBastion: false + enablePeering: false + enableTelemetry: true + flowTimeoutInMinutes: 30 + dnsServers: ['10.0.1.6', '10.0.1.7'] + diagnosticSettings: [ + { + eventHubAuthorizationRuleResourceId: diagnosticDependencies.outputs.eventHubAuthorizationRuleId + eventHubName: diagnosticDependencies.outputs.eventHubNamespaceEventHubName + metricCategories: [ + { + category: 'AllMetrics' + } + ] + name: 'customSetting' + storageAccountResourceId: diagnosticDependencies.outputs.storageAccountResourceId + workspaceResourceId: diagnosticDependencies.outputs.logAnalyticsWorkspaceResourceId + } + ] + location: resourceLocation + lock: { + kind: 'CanNotDelete' + name: 'hub1Lock' + } + routes: [ + { + name: 'defaultRoute' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'Internet' + } + } + ] + subnets: [ + { + name: 'GatewaySubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 0) + } + { + name: 'AzureFirewallSubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 1) + } + { + name: 'AzureBastionSubnet' + addressPrefix: cidrSubnet(addressPrefix, 26, 2) + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + vnetEncryption: false + vnetEncryptionEnforcement: 'AllowUnencrypted' + } + } + } + } +] From 57769c252159f9c25c12e67bc33938f6790236c8 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Thu, 26 Sep 2024 03:08:22 +0200 Subject: [PATCH 35/46] fix: StorageAccount - Added implicit dependency to blobServices from container (#3254) ## Description - Added implicit dependency to blobServices from container Closes #3210 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.storage.storage-account](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Falsehr%2FsaDep&event=workflow_dispatch)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation --- .../blob-service/container/README.md | 9 +++++++ .../blob-service/container/main.bicep | 5 +++- .../blob-service/container/main.json | 19 ++++++++----- .../storage-account/blob-service/main.bicep | 1 + .../storage-account/blob-service/main.json | 25 ++++++++++++----- avm/res/storage/storage-account/main.json | 27 +++++++++++++------ 6 files changed, 64 insertions(+), 22 deletions(-) diff --git a/avm/res/storage/storage-account/blob-service/container/README.md b/avm/res/storage/storage-account/blob-service/container/README.md index c66f943899b..34d861e3b56 100644 --- a/avm/res/storage/storage-account/blob-service/container/README.md +++ b/avm/res/storage/storage-account/blob-service/container/README.md @@ -34,6 +34,7 @@ This module deploys a Storage Account Blob Container. | Parameter | Type | Description | | :-- | :-- | :-- | +| [`blobServiceName`](#parameter-blobservicename) | string | The name of the parent Blob Service. Required if the template is used in a standalone deployment. | | [`defaultEncryptionScope`](#parameter-defaultencryptionscope) | string | Default the container to use specified encryption scope for all writes. | | [`denyEncryptionScopeOverride`](#parameter-denyencryptionscopeoverride) | bool | Block override of encryption scope from the container default. | | [`enableNfsV3AllSquash`](#parameter-enablenfsv3allsquash) | bool | Enable NFSv3 all squash on blob container. | @@ -59,6 +60,14 @@ The name of the parent Storage Account. Required if the template is used in a st - Required: Yes - Type: string +### Parameter: `blobServiceName` + +The name of the parent Blob Service. Required if the template is used in a standalone deployment. + +- Required: No +- Type: string +- Default: `'default'` + ### Parameter: `defaultEncryptionScope` Default the container to use specified encryption scope for all writes. diff --git a/avm/res/storage/storage-account/blob-service/container/main.bicep b/avm/res/storage/storage-account/blob-service/container/main.bicep index fa0193da72f..9a19a6096f5 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.bicep +++ b/avm/res/storage/storage-account/blob-service/container/main.bicep @@ -6,6 +6,9 @@ metadata owner = 'Azure/module-maintainers' @description('Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment.') param storageAccountName string +@description('Optional. The name of the parent Blob Service. Required if the template is used in a standalone deployment.') +param blobServiceName string = 'default' + @description('Required. The name of the storage container to deploy.') param name string @@ -105,7 +108,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing name: storageAccountName resource blobServices 'blobServices@2022-09-01' existing = { - name: 'default' + name: blobServiceName } } diff --git a/avm/res/storage/storage-account/blob-service/container/main.json b/avm/res/storage/storage-account/blob-service/container/main.json index 98d00e679fb..92a5cbbbd0f 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.json +++ b/avm/res/storage/storage-account/blob-service/container/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1020003258393866601" + "templateHash": "3558916747425087131" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -95,6 +95,13 @@ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." } }, + "blobServiceName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the parent Blob Service. Required if the template is used in a standalone deployment." + } + }, "name": { "type": "string", "metadata": { @@ -205,7 +212,7 @@ "existing": true, "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('blobServiceName'))]", "dependsOn": [ "storageAccount" ] @@ -219,7 +226,7 @@ "container": { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", "properties": { "defaultEncryptionScope": "[if(not(empty(parameters('defaultEncryptionScope'))), parameters('defaultEncryptionScope'), null())]", "denyEncryptionScopeOverride": "[if(equals(parameters('denyEncryptionScopeOverride'), true()), parameters('denyEncryptionScopeOverride'), null())]", @@ -240,8 +247,8 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", @@ -387,7 +394,7 @@ "metadata": { "description": "The resource ID of the deployed container." }, - "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name'))]" + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", diff --git a/avm/res/storage/storage-account/blob-service/main.bicep b/avm/res/storage/storage-account/blob-service/main.bicep index c02c02f6faa..bb43c4d6102 100644 --- a/avm/res/storage/storage-account/blob-service/main.bicep +++ b/avm/res/storage/storage-account/blob-service/main.bicep @@ -149,6 +149,7 @@ module blobServices_container 'container/main.bicep' = [ name: '${deployment().name}-Container-${index}' params: { storageAccountName: storageAccount.name + blobServiceName: blobServices.name name: container.name defaultEncryptionScope: container.?defaultEncryptionScope denyEncryptionScopeOverride: container.?denyEncryptionScopeOverride diff --git a/avm/res/storage/storage-account/blob-service/main.json b/avm/res/storage/storage-account/blob-service/main.json index 75312674681..944846b041a 100644 --- a/avm/res/storage/storage-account/blob-service/main.json +++ b/avm/res/storage/storage-account/blob-service/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17077763197163073998" + "templateHash": "16657059190807174649" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -365,6 +365,9 @@ "storageAccountName": { "value": "[parameters('storageAccountName')]" }, + "blobServiceName": { + "value": "[variables('name')]" + }, "name": { "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" }, @@ -404,7 +407,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1020003258393866601" + "templateHash": "3558916747425087131" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -493,6 +496,13 @@ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." } }, + "blobServiceName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the parent Blob Service. Required if the template is used in a standalone deployment." + } + }, "name": { "type": "string", "metadata": { @@ -603,7 +613,7 @@ "existing": true, "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('blobServiceName'))]", "dependsOn": [ "storageAccount" ] @@ -617,7 +627,7 @@ "container": { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", "properties": { "defaultEncryptionScope": "[if(not(empty(parameters('defaultEncryptionScope'))), parameters('defaultEncryptionScope'), null())]", "denyEncryptionScopeOverride": "[if(equals(parameters('denyEncryptionScopeOverride'), true()), parameters('denyEncryptionScopeOverride'), null())]", @@ -638,8 +648,8 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", @@ -785,7 +795,7 @@ "metadata": { "description": "The resource ID of the deployed container." }, - "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name'))]" + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", @@ -798,6 +808,7 @@ } }, "dependsOn": [ + "blobServices", "storageAccount" ] } diff --git a/avm/res/storage/storage-account/main.json b/avm/res/storage/storage-account/main.json index 38e3f3d998d..8120b7936ee 100644 --- a/avm/res/storage/storage-account/main.json +++ b/avm/res/storage/storage-account/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "6735651687082765200" + "templateHash": "8986504733456130232" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account.", @@ -2266,7 +2266,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17077763197163073998" + "templateHash": "16657059190807174649" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -2625,6 +2625,9 @@ "storageAccountName": { "value": "[parameters('storageAccountName')]" }, + "blobServiceName": { + "value": "[variables('name')]" + }, "name": { "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" }, @@ -2664,7 +2667,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "1020003258393866601" + "templateHash": "3558916747425087131" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -2753,6 +2756,13 @@ "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." } }, + "blobServiceName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the parent Blob Service. Required if the template is used in a standalone deployment." + } + }, "name": { "type": "string", "metadata": { @@ -2863,7 +2873,7 @@ "existing": true, "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('blobServiceName'))]", "dependsOn": [ "storageAccount" ] @@ -2877,7 +2887,7 @@ "container": { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", "properties": { "defaultEncryptionScope": "[if(not(empty(parameters('defaultEncryptionScope'))), parameters('defaultEncryptionScope'), null())]", "denyEncryptionScopeOverride": "[if(equals(parameters('denyEncryptionScopeOverride'), true()), parameters('denyEncryptionScopeOverride'), null())]", @@ -2898,8 +2908,8 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", @@ -3045,7 +3055,7 @@ "metadata": { "description": "The resource ID of the deployed container." }, - "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), 'default', parameters('name'))]" + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", @@ -3058,6 +3068,7 @@ } }, "dependsOn": [ + "blobServices", "storageAccount" ] } From 717e7fd82c698a28e2d152c2b1c39ddee6fe48ac Mon Sep 17 00:00:00 2001 From: Buddy <38195643+tsc-buddy@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:00:53 +1200 Subject: [PATCH 36/46] fix: test updates for az defaults (#3344) ## Description Addressing the AZ defaults now that the PSRule test bug has been addressed [here](https://github.com/microsoft/PSRule/issues/1900#issuecomment-2356575578). ## Pipeline Reference | Pipeline | | -------- | |[![avm.res.web.serverfarm](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml/badge.svg?branch=fix%2Fasp-zr-default)](https://github.com/tsc-buddy/bicep-registry-modules/actions/workflows/avm.res.web.serverfarm.yml)| ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [x] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- avm/res/web/serverfarm/README.md | 96 +++++++++---------- avm/res/web/serverfarm/main.bicep | 19 ++-- avm/res/web/serverfarm/main.json | 12 ++- .../tests/e2e/defaults/main.test.bicep | 4 +- .../serverfarm/tests/e2e/max/main.test.bicep | 8 +- .../tests/e2e/waf-aligned/main.test.bicep | 6 +- 6 files changed, 70 insertions(+), 75 deletions(-) diff --git a/avm/res/web/serverfarm/README.md b/avm/res/web/serverfarm/README.md index 0df60a5510e..152351ffe24 100644 --- a/avm/res/web/serverfarm/README.md +++ b/avm/res/web/serverfarm/README.md @@ -46,8 +46,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { params: { // Required parameters name: 'wsfmin001' - skuCapacity: 2 - skuName: 'S1' // Non-required parameters location: '' } @@ -70,12 +68,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": { "value": "wsfmin001" }, - "skuCapacity": { - "value": 2 - }, - "skuName": { - "value": "S1" - }, // Non-required parameters "location": { "value": "" @@ -102,8 +94,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { params: { // Required parameters name: 'wsfmax001' - skuCapacity: 1 - skuName: 'S1' // Non-required parameters diagnosticSettings: [ { @@ -143,12 +133,14 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { roleDefinitionIdOrName: '' } ] + skuCapacity: 3 + skuName: 'P1v3' tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' Role: 'DeploymentValidation' } - zoneRedundant: false + zoneRedundant: true } } ``` @@ -169,12 +161,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": { "value": "wsfmax001" }, - "skuCapacity": { - "value": 1 - }, - "skuName": { - "value": "S1" - }, // Non-required parameters "diagnosticSettings": { "value": [ @@ -226,6 +212,12 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { } ] }, + "skuCapacity": { + "value": 3 + }, + "skuName": { + "value": "P1v3" + }, "tags": { "value": { "Environment": "Non-Prod", @@ -234,7 +226,7 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { } }, "zoneRedundant": { - "value": false + "value": true } } } @@ -258,8 +250,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { params: { // Required parameters name: 'wsfwaf001' - skuCapacity: 2 - skuName: 'P1v3' // Non-required parameters diagnosticSettings: [ { @@ -281,12 +271,14 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { kind: 'CanNotDelete' name: 'lock' } + skuCapacity: 3 + skuName: 'P1v3' tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' Role: 'DeploymentValidation' } - zoneRedundant: false + zoneRedundant: true } } ``` @@ -307,12 +299,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": { "value": "wsfwaf001" }, - "skuCapacity": { - "value": 2 - }, - "skuName": { - "value": "P1v3" - }, // Non-required parameters "diagnosticSettings": { "value": [ @@ -342,6 +328,12 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { "name": "lock" } }, + "skuCapacity": { + "value": 3 + }, + "skuName": { + "value": "P1v3" + }, "tags": { "value": { "Environment": "Non-Prod", @@ -350,7 +342,7 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { } }, "zoneRedundant": { - "value": false + "value": true } } } @@ -366,8 +358,6 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { | Parameter | Type | Description | | :-- | :-- | :-- | | [`name`](#parameter-name) | string | Name of the app service plan. | -| [`skuCapacity`](#parameter-skucapacity) | int | Number of workers associated with the App Service Plan. | -| [`skuName`](#parameter-skuname) | string | The name of the SKU will Determine the tier, size, family of the App Service Plan. | **Conditional parameters** @@ -389,6 +379,8 @@ module serverfarm 'br/public:avm/res/web/serverfarm:' = { | [`maximumElasticWorkerCount`](#parameter-maximumelasticworkercount) | int | Maximum number of total workers allowed for this ElasticScaleEnabled App Service Plan. | | [`perSiteScaling`](#parameter-persitescaling) | bool | If true, apps assigned to this App Service plan can be scaled independently. If false, apps assigned to this App Service plan will scale to all instances of the plan. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`skuCapacity`](#parameter-skucapacity) | int | Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones. | +| [`skuName`](#parameter-skuname) | string | The name of the SKU will Determine the tier, size, family of the App Service Plan. This defaults to P1v3 to leverage availability zones. | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`targetWorkerCount`](#parameter-targetworkercount) | int | Scaling worker count. | | [`targetWorkerSize`](#parameter-targetworkersize) | int | The instance size of the hosting plan (small, medium, or large). | @@ -402,27 +394,6 @@ Name of the app service plan. - Required: Yes - Type: string -### Parameter: `skuCapacity` - -Number of workers associated with the App Service Plan. - -- Required: Yes -- Type: int - -### Parameter: `skuName` - -The name of the SKU will Determine the tier, size, family of the App Service Plan. - -- Required: Yes -- Type: string -- Example: - ```Bicep - 'F1' - 'B1' - 'P1v3' - 'I1v2' - ``` - ### Parameter: `reserved` Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true. @@ -739,6 +710,29 @@ The principal type of the assigned principal ID. ] ``` +### Parameter: `skuCapacity` + +Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones. + +- Required: No +- Type: int +- Default: `3` + +### Parameter: `skuName` + +The name of the SKU will Determine the tier, size, family of the App Service Plan. This defaults to P1v3 to leverage availability zones. + +- Required: No +- Type: string +- Default: `'P1v3'` +- Example: + ```Bicep + 'F1' + 'B1' + 'P1v3' + 'I1v2' + ``` + ### Parameter: `tags` Tags of the resource. diff --git a/avm/res/web/serverfarm/main.bicep b/avm/res/web/serverfarm/main.bicep index cd823f28cee..a9dc0155380 100644 --- a/avm/res/web/serverfarm/main.bicep +++ b/avm/res/web/serverfarm/main.bicep @@ -7,7 +7,7 @@ metadata owner = 'Azure/module-maintainers' @maxLength(60) param name string -@description('Required. The name of the SKU will Determine the tier, size, family of the App Service Plan.') +@description('Optional. The name of the SKU will Determine the tier, size, family of the App Service Plan. This defaults to P1v3 to leverage availability zones.') @metadata({ example: ''' 'F1' @@ -16,10 +16,10 @@ param name string 'I1v2' ''' }) -param skuName string +param skuName string = 'P1v3' -@description('Required. Number of workers associated with the App Service Plan.') -param skuCapacity int +@description('Optional. Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones.') +param skuCapacity int = 3 @description('Optional. Location for all resources.') param location string = resourceGroup().location @@ -185,11 +185,12 @@ resource appServicePlan_roleAssignments 'Microsoft.Authorization/roleAssignments for (roleAssignment, index) in (roleAssignments ?? []): { name: guid(appServicePlan.id, roleAssignment.principalId, roleAssignment.roleDefinitionIdOrName) properties: { - roleDefinitionId: contains(builtInRoleNames, roleAssignment.roleDefinitionIdOrName) - ? builtInRoleNames[roleAssignment.roleDefinitionIdOrName] - : contains(roleAssignment.roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/') - ? roleAssignment.roleDefinitionIdOrName - : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName) + roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains( + roleAssignment.roleDefinitionIdOrName, + '/providers/Microsoft.Authorization/roleDefinitions/' + ) + ? roleAssignment.roleDefinitionIdOrName + : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName)) principalId: roleAssignment.principalId description: roleAssignment.?description principalType: roleAssignment.?principalType diff --git a/avm/res/web/serverfarm/main.json b/avm/res/web/serverfarm/main.json index 68729b80337..ba4108ee0b0 100644 --- a/avm/res/web/serverfarm/main.json +++ b/avm/res/web/serverfarm/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "16609348340052214807" + "version": "0.30.3.12046", + "templateHash": "12599229174633311842" }, "name": "App Service Plan", "description": "This module deploys an App Service Plan.", @@ -203,15 +203,17 @@ }, "skuName": { "type": "string", + "defaultValue": "P1v3", "metadata": { "example": " 'F1'\n 'B1'\n 'P1v3'\n 'I1v2'\n ", - "description": "Required. The name of the SKU will Determine the tier, size, family of the App Service Plan." + "description": "Optional. The name of the SKU will Determine the tier, size, family of the App Service Plan. This defaults to P1v3 to leverage availability zones." } }, "skuCapacity": { "type": "int", + "defaultValue": 3, "metadata": { - "description": "Required. Number of workers associated with the App Service Plan." + "description": "Optional. Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones." } }, "location": { @@ -447,7 +449,7 @@ "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]", "name": "[guid(resourceId('Microsoft.Web/serverfarms', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", + "roleDefinitionId": "[coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)))]", "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", diff --git a/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep index 1de03468efe..afb9a3ac269 100644 --- a/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/defaults/main.test.bicep @@ -18,7 +18,7 @@ param serviceShort string = 'wsfmin' param namePrefix string = '#_namePrefix_#' #disable-next-line no-hardcoded-location // Just a value to avoid ongoing capacity challenges -var enforcedLocation = 'eastus' +var enforcedLocation = 'australiaeast' // ============ // // Dependencies // @@ -43,8 +43,6 @@ module testDeployment '../../../main.bicep' = [ params: { name: '${namePrefix}${serviceShort}001' location: enforcedLocation - skuName: 'S1' - skuCapacity: 2 } } ] diff --git a/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep index b5d2046b5f9..7b0bd49b7cc 100644 --- a/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/max/main.test.bicep @@ -18,7 +18,7 @@ param serviceShort string = 'wsfmax' param namePrefix string = '#_namePrefix_#' #disable-next-line no-hardcoded-location // Just a value to avoid ongoing capacity challenges -var enforcedLocation = 'eastus' +var enforcedLocation = 'australiaeast' // ============ // // Dependencies // @@ -64,10 +64,10 @@ module testDeployment '../../../main.bicep' = [ params: { name: '${namePrefix}${serviceShort}001' location: enforcedLocation - skuName: 'S1' - skuCapacity: 1 + skuName: 'P1v3' + skuCapacity: 3 perSiteScaling: true - zoneRedundant: false + zoneRedundant: true kind: 'App' lock: { name: 'lock' diff --git a/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep b/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep index 15eb7e25be6..3e0e62dd536 100644 --- a/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/web/serverfarm/tests/e2e/waf-aligned/main.test.bicep @@ -18,7 +18,7 @@ param serviceShort string = 'wsfwaf' param namePrefix string = '#_namePrefix_#' #disable-next-line no-hardcoded-location // Just a value to avoid ongoing capacity challenges -var enforcedLocation = 'eastus' +var enforcedLocation = 'australiaeast' // ============ // // Dependencies // @@ -56,8 +56,8 @@ module testDeployment '../../../main.bicep' = [ name: '${namePrefix}${serviceShort}001' location: enforcedLocation skuName: 'P1v3' - skuCapacity: 2 - zoneRedundant: false + skuCapacity: 3 + zoneRedundant: true kind: 'App' lock: { name: 'lock' From 032457048e32f131aa9578da896bc0b801ecd0e5 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 26 Sep 2024 11:37:22 +0200 Subject: [PATCH 37/46] fix: Added a fixed uksouth location for `avm.res.dev-ops-infrastructure.pool` (#3351) ## Description This pull request introduces a fixed location (uksouth) used to run the validation deployments. ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.dev-ops-infrastructure.pool](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.dev-ops-infrastructure.pool.yml/badge.svg?branch=johnlokerse%2Fenforce-location-mdp)](https://github.com/johnlokerse/bicep-registry-modules/actions/workflows/avm.res.dev-ops-infrastructure.pool.yml) | ## Type of Change - [x] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/dev-ops-infrastructure/pool/main.json | 4 ++-- .../pool/tests/e2e/defaults/main.test.bicep | 17 +++++++++-------- .../pool/tests/e2e/max/main.test.bicep | 17 +++++++++-------- .../pool/tests/e2e/waf-aligned/main.test.bicep | 17 +++++++++-------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/avm/res/dev-ops-infrastructure/pool/main.json b/avm/res/dev-ops-infrastructure/pool/main.json index ce80f4d180f..689ef6eb8c6 100644 --- a/avm/res/dev-ops-infrastructure/pool/main.json +++ b/avm/res/dev-ops-infrastructure/pool/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.3.12046", - "templateHash": "15191897376801297199" + "version": "0.30.23.60470", + "templateHash": "3502193398932835678" }, "name": "Managed DevOps Pool", "description": "This module deploys the Managed DevOps Pool resource.", diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep index c21a95f5054..45332f3a952 100644 --- a/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep @@ -10,25 +10,26 @@ metadata description = 'This instance deploys the module with the minimum set of @maxLength(90) param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location - @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') param serviceShort string = 'mdpmin' @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' -@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@description('Required. Name of the Azure DevOps organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') @secure() param azureDevOpsOrganizationName string = '' +// The Managed DevOps Pools resource is not available in all regions +#disable-next-line no-hardcoded-location +var enforcedLocation = 'uksouth' + // ============ // // Dependencies // // ============ // module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' @@ -40,7 +41,7 @@ module nestedDependencies 'dependencies.bicep' = { // ================= // resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: resourceGroupName - location: resourceLocation + location: enforcedLocation } // ============== // @@ -50,10 +51,10 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}001' - location: resourceLocation + location: enforcedLocation agentProfile: { kind: 'Stateless' } diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep index 7416f2934cf..09b40200e5c 100644 --- a/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep @@ -11,16 +11,13 @@ metadata description = 'This instance deploys the module with most of its featur @maxLength(90) param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location - @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') param serviceShort string = 'mdpmax' @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' -@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@description('Required. Name of the Azure DevOps organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') @secure() param azureDevOpsOrganizationName string = '' @@ -32,12 +29,16 @@ param azureDevOpsProjectName string = '' @secure() param devOpsInfrastructureObjectID string = '' +// The Managed DevOps Pools resource is not available in all regions +#disable-next-line no-hardcoded-location +var enforcedLocation = 'uksouth' + // ============ // // Dependencies // // ============ // module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' @@ -51,7 +52,7 @@ module nestedDependencies 'dependencies.bicep' = { // ================= resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: resourceGroupName - location: resourceLocation + location: enforcedLocation } // ============== // @@ -62,10 +63,10 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}001' - location: resourceLocation + location: enforcedLocation agentProfile: { kind: 'Stateless' resourcePredictions: { diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep index fdf015444fc..20a04a39007 100644 --- a/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep @@ -11,16 +11,13 @@ metadata description = 'This instance deploys the module in alignment with the b @maxLength(90) param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location - @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') param serviceShort string = 'mdpwaf' @description('Optional. A token to inject into the name of each resource.') param namePrefix string = '#_namePrefix_#' -@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@description('Required. Name of the Azure DevOps organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') @secure() param azureDevOpsOrganizationName string = '' @@ -32,12 +29,16 @@ param azureDevOpsProjectName string = '' @secure() param devOpsInfrastructureObjectID string = '' +// The Managed DevOps Pools resource is not available in all regions +#disable-next-line no-hardcoded-location +var enforcedLocation = 'uksouth' + // ============ // // Dependencies // // ============ // module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation)}-nestedDependencies' params: { devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' @@ -51,7 +52,7 @@ module nestedDependencies 'dependencies.bicep' = { // ================= resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: resourceGroupName - location: resourceLocation + location: enforcedLocation } // ============== // @@ -62,10 +63,10 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}001' - location: resourceLocation + location: enforcedLocation agentProfile: { kind: 'Stateless' resourcePredictions: { From 0c002037f359c4e40ecf878f946ccf7ccd4916c9 Mon Sep 17 00:00:00 2001 From: Peter Budai Date: Fri, 27 Sep 2024 03:09:13 +0200 Subject: [PATCH 38/46] fix: Rename 'capacity' property to skuCapacity in the tests - `avm/res/sql/server` (#3318) Fixes #1324 ## Description ## Pipeline Reference | Pipeline | | -------- | | | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- avm/res/sql/server/README.md | 8 ++++---- avm/res/sql/server/tests/e2e/max/main.test.bicep | 2 +- avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/avm/res/sql/server/README.md b/avm/res/sql/server/README.md index 4f73df2fa8e..dcc3f4e02ff 100644 --- a/avm/res/sql/server/README.md +++ b/avm/res/sql/server/README.md @@ -270,7 +270,6 @@ module server 'br/public:avm/res/sql/server:' = { backupShortTermRetentionPolicy: { retentionDays: 14 } - capacity: 0 collation: 'SQL_Latin1_General_CP1_CI_AS' diagnosticSettings: [ { @@ -289,6 +288,7 @@ module server 'br/public:avm/res/sql/server:' = { licenseType: 'LicenseIncluded' maxSizeBytes: 34359738368 name: 'sqlsmaxdb-001' + skuCapacity: 0 skuName: 'ElasticPool' skuTier: 'GeneralPurpose' } @@ -438,7 +438,6 @@ module server 'br/public:avm/res/sql/server:' = { "backupShortTermRetentionPolicy": { "retentionDays": 14 }, - "capacity": 0, "collation": "SQL_Latin1_General_CP1_CI_AS", "diagnosticSettings": [ { @@ -457,6 +456,7 @@ module server 'br/public:avm/res/sql/server:' = { "licenseType": "LicenseIncluded", "maxSizeBytes": 34359738368, "name": "sqlsmaxdb-001", + "skuCapacity": 0, "skuName": "ElasticPool", "skuTier": "GeneralPurpose" } @@ -857,7 +857,6 @@ module server 'br/public:avm/res/sql/server:' = { backupShortTermRetentionPolicy: { retentionDays: 14 } - capacity: 0 collation: 'SQL_Latin1_General_CP1_CI_AS' diagnosticSettings: [ { @@ -876,6 +875,7 @@ module server 'br/public:avm/res/sql/server:' = { licenseType: 'LicenseIncluded' maxSizeBytes: 34359738368 name: 'sqlswafdb-001' + skuCapacity: 0 skuName: 'ElasticPool' skuTier: 'GeneralPurpose' } @@ -988,7 +988,6 @@ module server 'br/public:avm/res/sql/server:' = { "backupShortTermRetentionPolicy": { "retentionDays": 14 }, - "capacity": 0, "collation": "SQL_Latin1_General_CP1_CI_AS", "diagnosticSettings": [ { @@ -1007,6 +1006,7 @@ module server 'br/public:avm/res/sql/server:' = { "licenseType": "LicenseIncluded", "maxSizeBytes": 34359738368, "name": "sqlswafdb-001", + "skuCapacity": 0, "skuName": "ElasticPool", "skuTier": "GeneralPurpose" } diff --git a/avm/res/sql/server/tests/e2e/max/main.test.bicep b/avm/res/sql/server/tests/e2e/max/main.test.bicep index 662e8900fd3..ec70abad1ae 100644 --- a/avm/res/sql/server/tests/e2e/max/main.test.bicep +++ b/avm/res/sql/server/tests/e2e/max/main.test.bicep @@ -125,7 +125,7 @@ module testDeployment '../../../main.bicep' = { collation: 'SQL_Latin1_General_CP1_CI_AS' skuTier: 'GeneralPurpose' skuName: 'ElasticPool' - capacity: 0 + skuCapacity: 0 maxSizeBytes: 34359738368 licenseType: 'LicenseIncluded' diagnosticSettings: [ diff --git a/avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep b/avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep index f444f7bf123..2f7e279f3b5 100644 --- a/avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/sql/server/tests/e2e/waf-aligned/main.test.bicep @@ -100,7 +100,7 @@ module testDeployment '../../../main.bicep' = { collation: 'SQL_Latin1_General_CP1_CI_AS' skuTier: 'GeneralPurpose' skuName: 'ElasticPool' - capacity: 0 + skuCapacity: 0 maxSizeBytes: 34359738368 licenseType: 'LicenseIncluded' diagnosticSettings: [ From 3c32902929de734daeeee6cc90070061922a2d85 Mon Sep 17 00:00:00 2001 From: Erika Gressi <56914614+eriqua@users.noreply.github.com> Date: Fri, 27 Sep 2024 08:34:10 +0100 Subject: [PATCH 39/46] fix: Storage account readme regen (#3372) ## Description Fixing static validation pipeline run Recompiled json files to trigger module publishing, blocked for latest merge ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.storage.storage-account](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml/badge.svg?branch=users%2Feriqua%2Ffix-storage)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.storage.storage-account.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/res/storage/storage-account/README.md | 12 ++-- .../container/immutability-policy/main.json | 4 +- .../blob-service/container/main.json | 8 +-- .../storage-account/blob-service/main.json | 12 ++-- .../storage-account/file-service/README.md | 2 +- .../storage-account/file-service/main.json | 12 ++-- .../file-service/share/main.json | 8 +-- .../storage-account/local-user/README.md | 2 +- .../storage-account/local-user/main.json | 4 +- avm/res/storage/storage-account/main.json | 56 +++++++++---------- .../management-policy/main.json | 4 +- .../storage-account/queue-service/README.md | 4 +- .../storage-account/queue-service/main.json | 8 +-- .../queue-service/queue/README.md | 2 +- .../queue-service/queue/main.json | 4 +- .../storage-account/table-service/README.md | 4 +- .../storage-account/table-service/main.json | 8 +-- .../table-service/table/README.md | 2 +- .../table-service/table/main.json | 4 +- 19 files changed, 80 insertions(+), 80 deletions(-) diff --git a/avm/res/storage/storage-account/README.md b/avm/res/storage/storage-account/README.md index bafe70194ff..310335b0d29 100644 --- a/avm/res/storage/storage-account/README.md +++ b/avm/res/storage/storage-account/README.md @@ -26,14 +26,14 @@ This module deploys a Storage Account. | `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | | `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | | `Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers/immutabilityPolicies) | -| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/fileServices) | | `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | -| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/localUsers) | | `Microsoft.Storage/storageAccounts/managementPolicies` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/managementPolicies) | -| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | -| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices/tables) | ## Usage examples diff --git a/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json b/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json index e92ebe5e3fb..1a92a67f274 100644 --- a/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json +++ b/avm/res/storage/storage-account/blob-service/container/immutability-policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "7245741358008626948" + "version": "0.30.23.60470", + "templateHash": "17642721918788484059" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/blob-service/container/main.json b/avm/res/storage/storage-account/blob-service/container/main.json index 92a5cbbbd0f..1144d31c913 100644 --- a/avm/res/storage/storage-account/blob-service/container/main.json +++ b/avm/res/storage/storage-account/blob-service/container/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3558916747425087131" + "version": "0.30.23.60470", + "templateHash": "7740343838101895320" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -295,8 +295,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "7245741358008626948" + "version": "0.30.23.60470", + "templateHash": "17642721918788484059" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/blob-service/main.json b/avm/res/storage/storage-account/blob-service/main.json index 944846b041a..6ab964fa85d 100644 --- a/avm/res/storage/storage-account/blob-service/main.json +++ b/avm/res/storage/storage-account/blob-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "16657059190807174649" + "version": "0.30.23.60470", + "templateHash": "12887537147730330940" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -406,8 +406,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3558916747425087131" + "version": "0.30.23.60470", + "templateHash": "7740343838101895320" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -696,8 +696,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "7245741358008626948" + "version": "0.30.23.60470", + "templateHash": "17642721918788484059" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", diff --git a/avm/res/storage/storage-account/file-service/README.md b/avm/res/storage/storage-account/file-service/README.md index 3e9748cc67c..af032effaa1 100644 --- a/avm/res/storage/storage-account/file-service/README.md +++ b/avm/res/storage/storage-account/file-service/README.md @@ -13,7 +13,7 @@ This module deploys a Storage Account File Share Service. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/fileServices) | | `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | ## Parameters diff --git a/avm/res/storage/storage-account/file-service/main.json b/avm/res/storage/storage-account/file-service/main.json index 9375230d2f6..3e4af3b5346 100644 --- a/avm/res/storage/storage-account/file-service/main.json +++ b/avm/res/storage/storage-account/file-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1933197013743223154" + "version": "0.30.23.60470", + "templateHash": "3657184950062156101" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -286,8 +286,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "13477688809575027800" + "version": "0.30.23.60470", + "templateHash": "5694394509785243538" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -493,8 +493,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "10820882302387746924" + "version": "0.30.23.60470", + "templateHash": "11498628270290452072" } }, "parameters": { diff --git a/avm/res/storage/storage-account/file-service/share/main.json b/avm/res/storage/storage-account/file-service/share/main.json index 90dc2205607..6f8f81b2dce 100644 --- a/avm/res/storage/storage-account/file-service/share/main.json +++ b/avm/res/storage/storage-account/file-service/share/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "13477688809575027800" + "version": "0.30.23.60470", + "templateHash": "5694394509785243538" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -212,8 +212,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "10820882302387746924" + "version": "0.30.23.60470", + "templateHash": "11498628270290452072" } }, "parameters": { diff --git a/avm/res/storage/storage-account/local-user/README.md b/avm/res/storage/storage-account/local-user/README.md index f8476f2e7af..b311040c239 100644 --- a/avm/res/storage/storage-account/local-user/README.md +++ b/avm/res/storage/storage-account/local-user/README.md @@ -12,7 +12,7 @@ This module deploys a Storage Account Local User, which is used for SFTP authent | Resource Type | API Version | | :-- | :-- | -| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/localUsers) | ## Parameters diff --git a/avm/res/storage/storage-account/local-user/main.json b/avm/res/storage/storage-account/local-user/main.json index 3514e026140..8a19e11da9c 100644 --- a/avm/res/storage/storage-account/local-user/main.json +++ b/avm/res/storage/storage-account/local-user/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "18130658711251621530" + "version": "0.30.23.60470", + "templateHash": "14184905621772237225" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", diff --git a/avm/res/storage/storage-account/main.json b/avm/res/storage/storage-account/main.json index 8120b7936ee..90cce4364c2 100644 --- a/avm/res/storage/storage-account/main.json +++ b/avm/res/storage/storage-account/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "8986504733456130232" + "version": "0.30.23.60470", + "templateHash": "6607893452071234065" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account.", @@ -1937,8 +1937,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "11289787713365096902" + "version": "0.30.23.60470", + "templateHash": "16749766572958481061" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy.", @@ -2047,8 +2047,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "18130658711251621530" + "version": "0.30.23.60470", + "templateHash": "14184905621772237225" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication.", @@ -2265,8 +2265,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "16657059190807174649" + "version": "0.30.23.60470", + "templateHash": "12887537147730330940" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service.", @@ -2666,8 +2666,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3558916747425087131" + "version": "0.30.23.60470", + "templateHash": "7740343838101895320" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container.", @@ -2956,8 +2956,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "7245741358008626948" + "version": "0.30.23.60470", + "templateHash": "17642721918788484059" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy.", @@ -3136,8 +3136,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1933197013743223154" + "version": "0.30.23.60470", + "templateHash": "3657184950062156101" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service.", @@ -3417,8 +3417,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "13477688809575027800" + "version": "0.30.23.60470", + "templateHash": "5694394509785243538" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share.", @@ -3624,8 +3624,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "10820882302387746924" + "version": "0.30.23.60470", + "templateHash": "11498628270290452072" } }, "parameters": { @@ -3900,8 +3900,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9552908737955027812" + "version": "0.30.23.60470", + "templateHash": "6947504466788447852" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -4145,8 +4145,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1992900679572007532" + "version": "0.30.23.60470", + "templateHash": "6090221832347220924" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", @@ -4416,8 +4416,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15143318143658591417" + "version": "0.30.23.60470", + "templateHash": "6657632516379685259" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -4658,8 +4658,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "16017327978473583176" + "version": "0.30.23.60470", + "templateHash": "7397003163362434404" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", @@ -4916,8 +4916,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "986606208324987345" + "version": "0.30.23.60470", + "templateHash": "12263717469683062316" } }, "definitions": { diff --git a/avm/res/storage/storage-account/management-policy/main.json b/avm/res/storage/storage-account/management-policy/main.json index c348d69dafa..6acd0abce60 100644 --- a/avm/res/storage/storage-account/management-policy/main.json +++ b/avm/res/storage/storage-account/management-policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "11289787713365096902" + "version": "0.30.23.60470", + "templateHash": "16749766572958481061" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy.", diff --git a/avm/res/storage/storage-account/queue-service/README.md b/avm/res/storage/storage-account/queue-service/README.md index ce773dbe3d1..94ce2dacff9 100644 --- a/avm/res/storage/storage-account/queue-service/README.md +++ b/avm/res/storage/storage-account/queue-service/README.md @@ -14,8 +14,8 @@ This module deploys a Storage Account Queue Service. | :-- | :-- | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices/queues) | ## Parameters diff --git a/avm/res/storage/storage-account/queue-service/main.json b/avm/res/storage/storage-account/queue-service/main.json index 00065e2abef..6fc76d0b472 100644 --- a/avm/res/storage/storage-account/queue-service/main.json +++ b/avm/res/storage/storage-account/queue-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9552908737955027812" + "version": "0.30.23.60470", + "templateHash": "6947504466788447852" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service.", @@ -250,8 +250,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1992900679572007532" + "version": "0.30.23.60470", + "templateHash": "6090221832347220924" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", diff --git a/avm/res/storage/storage-account/queue-service/queue/README.md b/avm/res/storage/storage-account/queue-service/queue/README.md index ccfbd4b6352..140ca973883 100644 --- a/avm/res/storage/storage-account/queue-service/queue/README.md +++ b/avm/res/storage/storage-account/queue-service/queue/README.md @@ -13,7 +13,7 @@ This module deploys a Storage Account Queue. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices/queues) | ## Parameters diff --git a/avm/res/storage/storage-account/queue-service/queue/main.json b/avm/res/storage/storage-account/queue-service/queue/main.json index 8bea12f90ea..2aad9fefb21 100644 --- a/avm/res/storage/storage-account/queue-service/queue/main.json +++ b/avm/res/storage/storage-account/queue-service/queue/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1992900679572007532" + "version": "0.30.23.60470", + "templateHash": "6090221832347220924" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue.", diff --git a/avm/res/storage/storage-account/table-service/README.md b/avm/res/storage/storage-account/table-service/README.md index f4a529a253f..7f65d890d01 100644 --- a/avm/res/storage/storage-account/table-service/README.md +++ b/avm/res/storage/storage-account/table-service/README.md @@ -14,8 +14,8 @@ This module deploys a Storage Account Table Service. | :-- | :-- | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | | `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | -| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices/tables) | ## Parameters diff --git a/avm/res/storage/storage-account/table-service/main.json b/avm/res/storage/storage-account/table-service/main.json index 5582b11c4a1..df8cac0dbd7 100644 --- a/avm/res/storage/storage-account/table-service/main.json +++ b/avm/res/storage/storage-account/table-service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15143318143658591417" + "version": "0.30.23.60470", + "templateHash": "6657632516379685259" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service.", @@ -247,8 +247,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "16017327978473583176" + "version": "0.30.23.60470", + "templateHash": "7397003163362434404" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", diff --git a/avm/res/storage/storage-account/table-service/table/README.md b/avm/res/storage/storage-account/table-service/table/README.md index 4009c666b33..63e5d835bf6 100644 --- a/avm/res/storage/storage-account/table-service/table/README.md +++ b/avm/res/storage/storage-account/table-service/table/README.md @@ -13,7 +13,7 @@ This module deploys a Storage Account Table. | Resource Type | API Version | | :-- | :-- | | `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices/tables) | ## Parameters diff --git a/avm/res/storage/storage-account/table-service/table/main.json b/avm/res/storage/storage-account/table-service/table/main.json index 0476ee247e8..15dc63d03be 100644 --- a/avm/res/storage/storage-account/table-service/table/main.json +++ b/avm/res/storage/storage-account/table-service/table/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "16017327978473583176" + "version": "0.30.23.60470", + "templateHash": "7397003163362434404" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table.", From 74f1839e457a394954c18fae201662dfbf8ba15b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:18:34 +0100 Subject: [PATCH 40/46] fix: bump github/codeql-action from 3.25.15 to 3.26.9 (#3360) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.15 to 3.26.9.

Commits
  • 461ef6c Merge pull request #2503 from github/update-v3.26.9-f861efb2b
  • 00b1146 Update changelog for v3.26.9
  • f861efb Merge pull request #2498 from github/dependabot/npm_and_yarn/npm-9874b37b58
  • 426821d Merge pull request #2485 from github/dependabot/github_actions/actions-a88a8c...
  • 07e8133 Merge pull request #2501 from github/henrymercer/missing-autobuild-config-error
  • e0a151e Fix inconsistency in autobuild error tracking
  • 6b0ce4e revert eslint-plugin-import to 2.29.1
  • 07fd497 Merge branch 'main' into dependabot/github_actions/actions-a88a8c5a24
  • 2cddcb1 Merge pull request #2499 from github/aeisenberg/no-upload-sarif
  • 6225a95 Don't upload during cancelled jobs
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.25.15&new-version=3.26.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/platform.ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/platform.ossf-scorecard.yml b/.github/workflows/platform.ossf-scorecard.yml index 5c37e5fc09b..386dca42a61 100644 --- a/.github/workflows/platform.ossf-scorecard.yml +++ b/.github/workflows/platform.ossf-scorecard.yml @@ -68,6 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9 with: sarif_file: results.sarif From 95ee1a36aac870b74568a78361ee68a488d4784c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:22:27 +0100 Subject: [PATCH 41/46] fix: bump actions/upload-artifact from 4.3.5 to 4.4.0 (#3155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.5 to 4.4.0.
Release notes

Sourced from actions/upload-artifact's releases.

v4.4.0

Notice: Breaking Changes :warning:

We will no longer include hidden files and folders by default in the upload-artifact action of this version. This reduces the risk that credentials are accidentally uploaded into artifacts. Customers who need to continue to upload these files can use a new option, include-hidden-files, to continue to do so.

See "Notice of upcoming deprecations and breaking changes in GitHub Actions runners" changelog and this issue for more details.

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v4.3.6...v4.4.0

v4.3.6

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.3.6

Commits
  • 5076954 Merge pull request #598 from actions/joshmgross/exclude-hidden-files
  • d52396a Add a warning about enabling include-hidden-files
  • 710f362 Remove "merged" from include-hidden-files input description
  • 3b315f2 npm run release again 🙂
  • 3be2180 Remove another trailing comma
  • 453e8d0 Update glob license
  • 0a398c1 npm run release
  • a0c40cf Update to latest @actions/glob and fix tests
  • acb59e4 lint
  • cb6558b Exclude hidden files by default
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4.3.5&new-version=4.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jack Tracey <41163455+jtracey93@users.noreply.github.com> --- .github/workflows/platform.ossf-scorecard.yml | 2 +- .github/workflows/platform.publish-module-index-json.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/platform.ossf-scorecard.yml b/.github/workflows/platform.ossf-scorecard.yml index 386dca42a61..cfbec6ff1f9 100644 --- a/.github/workflows/platform.ossf-scorecard.yml +++ b/.github/workflows/platform.ossf-scorecard.yml @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/platform.publish-module-index-json.yml b/.github/workflows/platform.publish-module-index-json.yml index b6a510c1f05..9c140c7102b 100644 --- a/.github/workflows/platform.publish-module-index-json.yml +++ b/.github/workflows/platform.publish-module-index-json.yml @@ -74,7 +74,7 @@ jobs: } - name: Upload artifacts - uses: actions/upload-artifact@v4.3.5 + uses: actions/upload-artifact@v4.4.0 with: name: publish-module-index-json-artifacts path: | From abd95ca57517fe7b77b0c756e285c1ad5d029ff5 Mon Sep 17 00:00:00 2001 From: Luke Snoddy <37806411+lsnoddy@users.noreply.github.com> Date: Fri, 27 Sep 2024 06:51:17 -0500 Subject: [PATCH 42/46] fix: Added fixed uksouth and ukwest locations for `avm.res.network.trafficmanagerprofile` (#3354) ## Description This pull request introduces fixed locations (uksouth and ukwest) used to deploy App Service plans in the waf test case due to sku family limitations Fixes #3205 Closes #3205 ## Pipeline Reference | Pipeline | | -------- | [![avm.res.network.trafficmanagerprofile](https://github.com/lsnoddy/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml/badge.svg?branch=users%2Flsnoddy%2Ftrafficmanagerprovile)](https://github.com/lsnoddy/bicep-registry-modules/actions/workflows/avm.res.network.trafficmanagerprofile.yml) | | ## Type of Change - [x] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../network/trafficmanagerprofile/README.md | 8 +++--- .../network/trafficmanagerprofile/main.json | 4 +-- .../tests/e2e/waf-aligned/main.test.bicep | 27 ++++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/avm/res/network/trafficmanagerprofile/README.md b/avm/res/network/trafficmanagerprofile/README.md index c7dfa7d65c1..11fa33f6272 100644 --- a/avm/res/network/trafficmanagerprofile/README.md +++ b/avm/res/network/trafficmanagerprofile/README.md @@ -257,7 +257,7 @@ module trafficmanagerprofile 'br/public:avm/res/network/trafficmanagerprofile:' endpointStatus: 'Enabled' priority: 1 targetResourceId: '' @@ -268,7 +268,7 @@ module trafficmanagerprofile 'br/public:avm/res/network/trafficmanagerprofile:' endpointStatus: 'Enabled' priority: 2 targetResourceId: '' @@ -334,7 +334,7 @@ module trafficmanagerprofile 'br/public:avm/res/network/trafficmanagerprofile:", "endpointStatus": "Enabled", "priority": 1, "targetResourceId": "", @@ -345,7 +345,7 @@ module trafficmanagerprofile 'br/public:avm/res/network/trafficmanagerprofile:", "endpointStatus": "Enabled", "priority": 2, "targetResourceId": "", diff --git a/avm/res/network/trafficmanagerprofile/main.json b/avm/res/network/trafficmanagerprofile/main.json index 275f4d0955f..1b1e35e9373 100644 --- a/avm/res/network/trafficmanagerprofile/main.json +++ b/avm/res/network/trafficmanagerprofile/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2090813965996228671" + "version": "0.30.23.60470", + "templateHash": "5539048151819308545" }, "name": "Traffic Manager Profiles", "description": "This module deploys a Traffic Manager Profile.", diff --git a/avm/res/network/trafficmanagerprofile/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/trafficmanagerprofile/tests/e2e/waf-aligned/main.test.bicep index 32d82ac36ba..3394fe0e75b 100644 --- a/avm/res/network/trafficmanagerprofile/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/network/trafficmanagerprofile/tests/e2e/waf-aligned/main.test.bicep @@ -11,8 +11,11 @@ metadata description = 'This instance deploys the module in alignment with the b @maxLength(90) param resourceGroupName string = 'dep-${namePrefix}-network.trafficmanagerprofiles-${serviceShort}-rg' -@description('Optional. The location to deploy resources to.') -param resourceLocation string = deployment().location +#disable-next-line no-hardcoded-location +var enforcedLocation01 = 'uksouth' + +#disable-next-line no-hardcoded-location +var enforcedLocation02 = 'ukwest' @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') param serviceShort string = 'ntmpwaf' @@ -28,21 +31,21 @@ param namePrefix string = '#_namePrefix_#' // ================= resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: resourceGroupName - location: resourceLocation + location: enforcedLocation01 } module nestedDependencies 'dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + name: '${uniqueString(deployment().name, enforcedLocation01)}-nestedDependencies' params: { managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' - location: resourceLocation + location: enforcedLocation01 serverFarmName01: 'dep-${namePrefix}-sf-${serviceShort}01' serverFarmName02: 'dep-${namePrefix}-sf-${serviceShort}02' webApp01Name: 'dep-${namePrefix}-wa-${serviceShort}01' webApp02Name: 'dep-${namePrefix}-wa-${serviceShort}02' - location01: 'eastus' - location02: 'westus' + location01: enforcedLocation01 + location02: enforcedLocation02 } } @@ -50,13 +53,13 @@ module nestedDependencies 'dependencies.bicep' = { // =========== module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/templates/diagnostic.dependencies.bicep' = { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-diagnosticDependencies' + name: '${uniqueString(deployment().name, enforcedLocation01)}-diagnosticDependencies' params: { storageAccountName: 'dep${namePrefix}diasa${serviceShort}01' logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' eventHubNamespaceEventHubName: 'dep-${namePrefix}-evh-${serviceShort}' eventHubNamespaceName: 'dep-${namePrefix}-evhns-${serviceShort}' - location: resourceLocation + location: enforcedLocation01 } } @@ -67,7 +70,7 @@ module diagnosticDependencies '../../../../../../utilities/e2e-template-assets/t module testDeployment '../../../main.bicep' = [ for iteration in ['init', 'idem']: { scope: resourceGroup - name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + name: '${uniqueString(deployment().name, enforcedLocation01)}-test-${serviceShort}-${iteration}' params: { name: '${namePrefix}${serviceShort}001' location: 'global' @@ -107,7 +110,7 @@ module testDeployment '../../../main.bicep' = [ targetResourceId: nestedDependencies.outputs.webApp01ResourceId weight: 1 priority: 1 - endpointLocation: 'eastus' + endpointLocation: '${enforcedLocation01}' endpointStatus: 'Enabled' } } @@ -118,7 +121,7 @@ module testDeployment '../../../main.bicep' = [ targetResourceId: nestedDependencies.outputs.webApp02ResourceId weight: 1 priority: 2 - endpointLocation: 'westus' + endpointLocation: '${enforcedLocation02}' endpointStatus: 'Enabled' } } From 0463edbfbac951c9c9f87bebdcb10cdf8691cba6 Mon Sep 17 00:00:00 2001 From: Kris Baranek <20225789+krbar@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:42:31 +0200 Subject: [PATCH 43/46] test: `avm/res/network/service-endpoint-policy` - add RBAC test (#3182) ## Description - added `max` test case with roleAssignments test - moved `locks` test from `waf-aligned` to `max` - removed unnecessary comment lines in test cases ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.network.service-endpoint-policy](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml/badge.svg?branch=users%2Fkrbar%2FsvcEpPolicy)](https://github.com/Azure/bicep-registry-modules/actions/workflows/avm.res.network.service-endpoint-policy.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [ ] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [ ] I'm sure there are no other open Pull Requests for the same update/change - [ ] I have run `Set-AVMModule` locally to generate the supporting module files. - [ ] My corresponding pipelines / checks run clean and green without any errors or warnings Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .../network/service-endpoint-policy/README.md | 111 +++++++++++++++++- .../tests/e2e/defaults/main.test.bicep | 3 - .../tests/e2e/max/dependencies.bicep | 13 ++ .../tests/e2e/max/main.test.bicep | 87 ++++++++++++++ .../tests/e2e/waf-aligned/main.test.bicep | 7 -- 5 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 avm/res/network/service-endpoint-policy/tests/e2e/max/dependencies.bicep create mode 100644 avm/res/network/service-endpoint-policy/tests/e2e/max/main.test.bicep diff --git a/avm/res/network/service-endpoint-policy/README.md b/avm/res/network/service-endpoint-policy/README.md index 7d1097c4b2b..2fb4267d723 100644 --- a/avm/res/network/service-endpoint-policy/README.md +++ b/avm/res/network/service-endpoint-policy/README.md @@ -27,7 +27,8 @@ The following section provides usage examples for the module, which were used to >**Note**: To reference the module, please use the following syntax `br/public:avm/res/network/service-endpoint-policy:`. - [Using only defaults](#example-1-using-only-defaults) -- [WAF-aligned](#example-2-waf-aligned) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [WAF-aligned](#example-3-waf-aligned) ### Example 1: _Using only defaults_ @@ -77,9 +78,9 @@ module serviceEndpointPolicy 'br/public:avm/res/network/service-endpoint-policy:

-### Example 2: _WAF-aligned_ +### Example 2: _Using large parameter set_ -This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. +This instance deploys the module with most of its features enabled.

@@ -91,13 +92,32 @@ module serviceEndpointPolicy 'br/public:avm/res/network/service-endpoint-policy: name: 'serviceEndpointPolicyDeployment' params: { // Required parameters - name: 'nsepwaf001' + name: 'nsepmax001' // Non-required parameters location: '' lock: { kind: 'CanNotDelete' name: 'myCustomLockName' } + roleAssignments: [ + { + name: '36fbc5db-13e9-4bda-9594-1b1cc9db2d6d' + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + { + name: '' + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + ] tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' @@ -121,7 +141,7 @@ module serviceEndpointPolicy 'br/public:avm/res/network/service-endpoint-policy: "parameters": { // Required parameters "name": { - "value": "nsepwaf001" + "value": "nsepmax001" }, // Non-required parameters "location": { @@ -133,6 +153,87 @@ module serviceEndpointPolicy 'br/public:avm/res/network/service-endpoint-policy: "name": "myCustomLockName" } }, + "roleAssignments": { + "value": [ + { + "name": "36fbc5db-13e9-4bda-9594-1b1cc9db2d6d", + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + }, + { + "name": "", + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + } + ] + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

+ +### Example 3: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module serviceEndpointPolicy 'br/public:avm/res/network/service-endpoint-policy:' = { + name: 'serviceEndpointPolicyDeployment' + params: { + // Required parameters + name: 'nsepwaf001' + // Non-required parameters + location: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "name": { + "value": "nsepwaf001" + }, + // Non-required parameters + "location": { + "value": "" + }, "tags": { "value": { "Environment": "Non-Prod", diff --git a/avm/res/network/service-endpoint-policy/tests/e2e/defaults/main.test.bicep b/avm/res/network/service-endpoint-policy/tests/e2e/defaults/main.test.bicep index ba587c2c4d0..6a594af1c83 100644 --- a/avm/res/network/service-endpoint-policy/tests/e2e/defaults/main.test.bicep +++ b/avm/res/network/service-endpoint-policy/tests/e2e/defaults/main.test.bicep @@ -9,14 +9,12 @@ metadata description = 'This instance deploys the module with the minimum set of @description('Optional. The name of the resource group to deploy for testing purposes.') @maxLength(90) -// e.g., for a module 'network/private-endpoint' you could use 'dep-dev-network.privateendpoints-${serviceShort}-rg' param resourceGroupName string = 'dep-${namePrefix}-network.serviceendpointpolicy-${serviceShort}-rg' @description('Optional. The location to deploy resources to.') param resourceLocation string = deployment().location @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test param serviceShort string = 'nsepmin' @description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') @@ -42,7 +40,6 @@ module testDeployment '../../../main.bicep' = [ scope: resourceGroup name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { - // You parameters go here name: '${namePrefix}${serviceShort}001' location: resourceLocation } diff --git a/avm/res/network/service-endpoint-policy/tests/e2e/max/dependencies.bicep b/avm/res/network/service-endpoint-policy/tests/e2e/max/dependencies.bicep new file mode 100644 index 00000000000..7b3d4e8fb0a --- /dev/null +++ b/avm/res/network/service-endpoint-policy/tests/e2e/max/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: managedIdentityName + location: location +} + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId diff --git a/avm/res/network/service-endpoint-policy/tests/e2e/max/main.test.bicep b/avm/res/network/service-endpoint-policy/tests/e2e/max/main.test.bicep new file mode 100644 index 00000000000..43504b45993 --- /dev/null +++ b/avm/res/network/service-endpoint-policy/tests/e2e/max/main.test.bicep @@ -0,0 +1,87 @@ +targetScope = 'subscription' + +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module with most of its features enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-network.serviceendpointpolicy-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'nsepmax' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + location: resourceLocation + } +} + +// ============== // +// Test Execution // +// ============== // +@batchSize(1) +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 + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + name: '36fbc5db-13e9-4bda-9594-1b1cc9db2d6d' + roleDefinitionIdOrName: 'Owner' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + name: guid('Custom seed ${namePrefix}${serviceShort}') + 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' + } + ] + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +] diff --git a/avm/res/network/service-endpoint-policy/tests/e2e/waf-aligned/main.test.bicep b/avm/res/network/service-endpoint-policy/tests/e2e/waf-aligned/main.test.bicep index 0076f596691..b2c6f192811 100644 --- a/avm/res/network/service-endpoint-policy/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/network/service-endpoint-policy/tests/e2e/waf-aligned/main.test.bicep @@ -9,14 +9,12 @@ metadata description = 'This instance deploys the module in alignment with the b @description('Optional. The name of the resource group to deploy for testing purposes.') @maxLength(90) -// e.g., for a module 'network/private-endpoint' you could use 'dep-dev-network.privateendpoints-${serviceShort}-rg' param resourceGroupName string = 'dep-${namePrefix}-network.serviceendpointpolicy-${serviceShort}-rg' @description('Optional. The location to deploy resources to.') param resourceLocation string = deployment().location @description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') -// e.g., for a module 'network/private-endpoint' you could use 'npe' as a prefix and then 'waf' as a suffix for the waf-aligned test param serviceShort string = 'nsepwaf' @description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') @@ -42,13 +40,8 @@ module testDeployment '../../../main.bicep' = [ scope: resourceGroup name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' params: { - // You parameters go here name: '${namePrefix}${serviceShort}001' location: resourceLocation - lock: { - kind: 'CanNotDelete' - name: 'myCustomLockName' - } tags: { 'hidden-title': 'This is visible in the resource name' Environment: 'Non-Prod' From 9ab65b20e7c76dc2cb6fafbaa3ec2a368a9f4675 Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:23:32 +0300 Subject: [PATCH 44/46] fix: Implement resiliency by default (#3370) ## Description Default to zone-redundant SKUs Default to zone-redundant Public IP fixes #3369 ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.network.virtual-network-gateway](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml/badge.svg?branch=avm-resiliency-vnetGw)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.res.network.virtual-network-gateway.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [X] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [ ] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [X] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --- .../network/virtual-network-gateway/README.md | 170 ++++++++++-------- .../virtual-network-gateway/main.bicep | 27 ++- .../network/virtual-network-gateway/main.json | 17 +- .../tests/e2e/vpn-no-az/main.test.bicep | 3 +- 4 files changed, 115 insertions(+), 102 deletions(-) diff --git a/avm/res/network/virtual-network-gateway/README.md b/avm/res/network/virtual-network-gateway/README.md index 644177396ea..85c0d4ad2eb 100644 --- a/avm/res/network/virtual-network-gateway/README.md +++ b/avm/res/network/virtual-network-gateway/README.md @@ -62,7 +62,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgavpn001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters domainNameLabel: [ @@ -74,6 +73,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnClientAadConfiguration: { aadAudience: '41b23e61-6c1e-4545-b367-cd054e0ed4b4' aadIssuer: '' @@ -114,9 +114,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgavpn001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -136,6 +133,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnClientAadConfiguration": { "value": { "aadAudience": "41b23e61-6c1e-4545-b367-cd054e0ed4b4", @@ -178,7 +178,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgaab001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -195,6 +194,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } @@ -225,9 +225,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgaab001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -262,6 +259,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnGatewayGeneration": { "value": "Generation2" }, @@ -302,7 +302,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgaaa001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -319,6 +318,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } @@ -357,9 +357,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgaaa001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -394,6 +391,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnGatewayGeneration": { "value": "Generation2" }, @@ -426,7 +426,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgaa001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -443,6 +442,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } @@ -473,9 +473,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgaa001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -510,6 +507,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnGatewayGeneration": { "value": "Generation2" }, @@ -547,7 +547,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgapb001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -564,6 +563,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } @@ -599,9 +599,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgapb001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -636,6 +633,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnGatewayGeneration": { "value": "Generation2" }, @@ -668,7 +668,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgap001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -685,6 +684,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } @@ -715,9 +715,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgap001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -752,6 +749,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnGatewayGeneration": { "value": "Generation2" }, @@ -784,7 +784,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgmin001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters location: '' @@ -793,6 +792,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' } } ``` @@ -821,9 +821,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgmin001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -837,6 +834,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2, 3 ] + }, + "skuName": { + "value": "VpnGw2AZ" } } } @@ -864,7 +864,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'ExpressRoute' name: 'nvger001' - skuName: 'ErGw1AZ' vNetResourceId: '' // Non-required parameters domainNameLabel: [ @@ -877,6 +876,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'ErGw1AZ' } } ``` @@ -905,9 +905,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvger001" }, - "skuName": { - "value": "ErGw1AZ" - }, "vNetResourceId": { "value": "" }, @@ -929,6 +926,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2, 3 ] + }, + "skuName": { + "value": "ErGw1AZ" } } } @@ -965,7 +965,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgmax001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -1053,6 +1052,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: roleDefinitionIdOrName: '' } ] + skuName: 'VpnGw2AZ' tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' @@ -1097,9 +1097,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgmax001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -1213,6 +1210,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "tags": { "value": { "Environment": "Non-Prod", @@ -1252,10 +1252,11 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgnaz001' - skuName: 'VpnGw1' vNetResourceId: '' // Non-required parameters location: '' + publicIpZones: [] + skuName: 'VpnGw1' } } ``` @@ -1284,15 +1285,18 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgnaz001" }, - "skuName": { - "value": "VpnGw1" - }, "vNetResourceId": { "value": "" }, // Non-required parameters "location": { "value": "" + }, + "publicIpZones": { + "value": [] + }, + "skuName": { + "value": "VpnGw1" } } } @@ -1320,7 +1324,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgvpn001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -1337,6 +1340,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' vpnGatewayGeneration: 'Generation2' vpnType: 'RouteBased' } @@ -1367,9 +1371,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgvpn001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -1404,6 +1405,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "vpnGatewayGeneration": { "value": "Generation2" }, @@ -1445,7 +1449,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: } gatewayType: 'Vpn' name: 'nvgmwaf001' - skuName: 'VpnGw2AZ' vNetResourceId: '' // Non-required parameters allowRemoteVnetTraffic: true @@ -1514,6 +1517,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 2 3 ] + skuName: 'VpnGw2AZ' tags: { Environment: 'Non-Prod' 'hidden-title': 'This is visible in the resource name' @@ -1558,9 +1562,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: "name": { "value": "nvgmwaf001" }, - "skuName": { - "value": "VpnGw2AZ" - }, "vNetResourceId": { "value": "" }, @@ -1653,6 +1654,9 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: 3 ] }, + "skuName": { + "value": "VpnGw2AZ" + }, "tags": { "value": { "Environment": "Non-Prod", @@ -1682,7 +1686,6 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: | [`clusterSettings`](#parameter-clustersettings) | object | Specifies one of the following four configurations: Active-Active with (clusterMode = activeActiveBgp) or without (clusterMode = activeActiveNoBgp) BGP, Active-Passive with (clusterMode = activePassiveBgp) or without (clusterMode = activePassiveNoBgp) BGP. | | [`gatewayType`](#parameter-gatewaytype) | string | Specifies the gateway type. E.g. VPN, ExpressRoute. | | [`name`](#parameter-name) | string | Specifies the Virtual Network Gateway name. | -| [`skuName`](#parameter-skuname) | string | The SKU of the Gateway. | | [`vNetResourceId`](#parameter-vnetresourceid) | string | Virtual Network resource ID. | **Optional parameters** @@ -1709,6 +1712,7 @@ module virtualNetworkGateway 'br/public:avm/res/network/virtual-network-gateway: | [`publicIPPrefixResourceId`](#parameter-publicipprefixresourceid) | string | Resource ID of the Public IP Prefix object. This is only needed if you want your Public IPs created in a PIP Prefix. | | [`publicIpZones`](#parameter-publicipzones) | array | Specifies the zones of the Public IP address. Basic IP SKU does not support Availability Zones. | | [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`skuName`](#parameter-skuname) | string | The SKU of the Gateway. | | [`tags`](#parameter-tags) | object | Tags of the resource. | | [`vpnClientAadConfiguration`](#parameter-vpnclientaadconfiguration) | object | Configuration for AAD Authentication for P2S Tunnel Type, Cannot be configured if clientRootCertData is provided. | | [`vpnClientAddressPoolPrefix`](#parameter-vpnclientaddresspoolprefix) | string | The IP address range from which VPN clients will receive an IP address when connected. Range specified must not overlap with on-premise network. | @@ -1743,35 +1747,6 @@ Specifies the Virtual Network Gateway name. - Required: Yes - Type: string -### Parameter: `skuName` - -The SKU of the Gateway. - -- Required: Yes -- Type: string -- Allowed: - ```Bicep - [ - 'Basic' - 'ErGw1AZ' - 'ErGw2AZ' - 'ErGw3AZ' - 'HighPerformance' - 'Standard' - 'UltraPerformance' - 'VpnGw1' - 'VpnGw1AZ' - 'VpnGw2' - 'VpnGw2AZ' - 'VpnGw3' - 'VpnGw3AZ' - 'VpnGw4' - 'VpnGw4AZ' - 'VpnGw5' - 'VpnGw5AZ' - ] - ``` - ### Parameter: `vNetResourceId` Virtual Network resource ID. @@ -2233,7 +2208,14 @@ Specifies the zones of the Public IP address. Basic IP SKU does not support Avai - Required: No - Type: array -- Default: `[]` +- Default: + ```Bicep + [ + 1 + 2 + 3 + ] + ``` ### Parameter: `roleAssignments` @@ -2339,6 +2321,36 @@ The principal type of the assigned principal ID. ] ``` +### Parameter: `skuName` + +The SKU of the Gateway. + +- Required: No +- Type: string +- Default: `[if(equals(parameters('gatewayType'), 'VPN'), 'VpnGw1AZ', 'ErGw1AZ')]` +- Allowed: + ```Bicep + [ + 'Basic' + 'ErGw1AZ' + 'ErGw2AZ' + 'ErGw3AZ' + 'HighPerformance' + 'Standard' + 'UltraPerformance' + 'VpnGw1' + 'VpnGw1AZ' + 'VpnGw2' + 'VpnGw2AZ' + 'VpnGw3' + 'VpnGw3AZ' + 'VpnGw4' + 'VpnGw4AZ' + 'VpnGw5' + 'VpnGw5AZ' + ] + ``` + ### Parameter: `tags` Tags of the resource. diff --git a/avm/res/network/virtual-network-gateway/main.bicep b/avm/res/network/virtual-network-gateway/main.bicep index 15b0247f692..4cf0d87c155 100644 --- a/avm/res/network/virtual-network-gateway/main.bicep +++ b/avm/res/network/virtual-network-gateway/main.bicep @@ -15,7 +15,11 @@ param gatewayPipName string = '${name}-pip1' param publicIPPrefixResourceId string = '' @description('Optional. Specifies the zones of the Public IP address. Basic IP SKU does not support Availability Zones.') -param publicIpZones array = [] +param publicIpZones array = [ + 1 + 2 + 3 +] @description('Optional. DNS name(s) of the Public IP resource(s). If you enabled active-active configuration, you need to provide 2 DNS names, if you want to use this feature. A region specific suffix will be appended to it, e.g.: your-DNS-name.westeurope.cloudapp.azure.com.') param domainNameLabel array = [] @@ -35,7 +39,7 @@ param gatewayType string ]) param vpnGatewayGeneration string = 'None' -@description('Required. The SKU of the Gateway.') +@description('Optional. The SKU of the Gateway.') @allowed([ 'Basic' 'VpnGw1' @@ -55,7 +59,7 @@ param vpnGatewayGeneration string = 'None' 'ErGw2AZ' 'ErGw3AZ' ]) -param skuName string +param skuName string = (gatewayType == 'VPN') ? 'VpnGw1AZ' : 'ErGw1AZ' @description('Optional. Specifies the VPN type.') @allowed([ @@ -175,7 +179,6 @@ var bgpSettingsVar = isActiveActive ] } - // Potential IP configurations (active-active vs active-passive) var ipConfiguration = isActiveActive ? [ @@ -554,53 +557,45 @@ type diagnosticSettingType = { }[]? type activePassiveNoBgpType = { - clusterMode: 'activePassiveNoBgp' - } type activeActiveNoBgpType = { - clusterMode: 'activeActiveNoBgp' @description('Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it\'s not provided, a \'-pip2\' suffix will be appended to the gateway\'s name.') activeGatewayPipName: string? - } type activePassiveBgpType = { - clusterMode: 'activePassiveBgp' @description('Optional. The Autonomous System Number value. If it\'s not provided, a default \'65515\' value will be assigned to the ASN.') @minValue(0) @maxValue(4294967295) - asn: int? + asn: int? @description('Optional. The list of custom BGP IP Address (APIPA) peering addresses which belong to IP configuration.') customBgpIpAddresses: string[]? } type activeActiveBgpType = { - clusterMode: 'activeActiveBgp' @description('Optional. Specifies the name of the Public IP used by the Virtual Network Gateway when active-active configuration is required. If it\'s not provided, a \'-pip2\' suffix will be appended to the gateway\'s name.') activeGatewayPipName: string? - + @description('Optional. The Autonomous System Number value. If it\'s not provided, a default \'65515\' value will be assigned to the ASN.') @minValue(0) @maxValue(4294967295) - asn: int? + asn: int? @description('Optional. The list of custom BGP IP Address (APIPA) peering addresses which belong to IP configuration.') customBgpIpAddresses: string[]? - + @description('Optional. The list of the second custom BGP IP Address (APIPA) peering addresses which belong to IP configuration.') secondCustomBgpIpAddresses: string[]? } @discriminator('clusterMode') type clusterSettingType = activeActiveNoBgpType | activeActiveBgpType | activePassiveBgpType | activePassiveNoBgpType - - diff --git a/avm/res/network/virtual-network-gateway/main.json b/avm/res/network/virtual-network-gateway/main.json index 5e1b4bdf4cb..5badcfef955 100644 --- a/avm/res/network/virtual-network-gateway/main.json +++ b/avm/res/network/virtual-network-gateway/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "973776533492793692" + "version": "0.30.23.60470", + "templateHash": "6347373659148864152" }, "name": "Virtual Network Gateways", "description": "This module deploys a Virtual Network Gateway.", @@ -388,7 +388,11 @@ }, "publicIpZones": { "type": "array", - "defaultValue": [], + "defaultValue": [ + 1, + 2, + 3 + ], "metadata": { "description": "Optional. Specifies the zones of the Public IP address. Basic IP SKU does not support Availability Zones." } @@ -424,6 +428,7 @@ }, "skuName": { "type": "string", + "defaultValue": "[if(equals(parameters('gatewayType'), 'VPN'), 'VpnGw1AZ', 'ErGw1AZ')]", "allowedValues": [ "Basic", "VpnGw1", @@ -444,7 +449,7 @@ "ErGw3AZ" ], "metadata": { - "description": "Required. The SKU of the Gateway." + "description": "Optional. The SKU of the Gateway." } }, "vpnType": { @@ -1455,8 +1460,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "915174536118171652" + "version": "0.30.23.60470", + "templateHash": "15500017864202979057" }, "name": "VPN Gateway NAT Rules", "description": "This module deploys a Virtual Network Gateway NAT Rule.", diff --git a/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep b/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep index 514b8429ab6..fc3410af525 100644 --- a/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep +++ b/avm/res/network/virtual-network-gateway/tests/e2e/vpn-no-az/main.test.bicep @@ -56,8 +56,9 @@ module testDeployment '../../../main.bicep' = [ name: '${namePrefix}${serviceShort}001' skuName: 'VpnGw1' gatewayType: 'Vpn' + publicIpZones: [] vNetResourceId: nestedDependencies.outputs.vnetResourceId - clusterSettings:{ + clusterSettings: { clusterMode: 'activePassiveNoBgp' } } From 720edc039c8590abc785392cc209407c93f8671c Mon Sep 17 00:00:00 2001 From: Seif Bassem <38246040+sebassem@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:12:22 +0300 Subject: [PATCH 45/46] fix: updating virtualNetwork API for LZ network resource (#3366) ## Description Bump the LZ virtual network resource to avoid subnet deletion on PUT requests Fixes #3330 ## Pipeline Reference | Pipeline | | -------- | | [![avm.ptn.lz.sub-vending](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.lz.sub-vending.yml/badge.svg?branch=avm-ptn-sub-vending-network-api)](https://github.com/sebassem/bicep-registry-modules/actions/workflows/avm.ptn.lz.sub-vending.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [ ] Azure Verified Module updates: - [X] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [X] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [ ] Update to documentation ## Checklist - [X] I'm sure there are no other open Pull Requests for the same update/change - [X] I have run `Set-AVMModule` locally to generate the supporting module files. - [X] My corresponding pipelines / checks run clean and green without any errors or warnings --- avm/ptn/lz/sub-vending/README.md | 20 +- avm/ptn/lz/sub-vending/main.json | 1341 ++++++++++++----- .../modules/subResourceWrapper.bicep | 37 +- 3 files changed, 1030 insertions(+), 368 deletions(-) diff --git a/avm/ptn/lz/sub-vending/README.md b/avm/ptn/lz/sub-vending/README.md index d765a73bfc2..d1bea9021b3 100644 --- a/avm/ptn/lz/sub-vending/README.md +++ b/avm/ptn/lz/sub-vending/README.md @@ -25,9 +25,9 @@ This module deploys a subscription to accelerate deployment of landing zones. Fo | `Microsoft.Network/privateEndpoints` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints) | | `Microsoft.Network/privateEndpoints/privateDnsZoneGroups` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-04-01/privateEndpoints/privateDnsZoneGroups) | | `Microsoft.Network/virtualHubs/hubVirtualNetworkConnections` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/virtualHubs/hubVirtualNetworkConnections) | -| `Microsoft.Network/virtualNetworks` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/virtualNetworks) | -| `Microsoft.Network/virtualNetworks/subnets` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/virtualNetworks/subnets) | -| `Microsoft.Network/virtualNetworks/virtualNetworkPeerings` | [2023-11-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/2023-11-01/virtualNetworks/virtualNetworkPeerings) | +| `Microsoft.Network/virtualNetworks` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks) | +| `Microsoft.Network/virtualNetworks/subnets` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/subnets) | +| `Microsoft.Network/virtualNetworks/virtualNetworkPeerings` | [2024-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Network/virtualNetworks/virtualNetworkPeerings) | | `Microsoft.Resources/deploymentScripts` | [2023-08-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/2023-08-01/deploymentScripts) | | `Microsoft.Resources/resourceGroups` | [2021-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/2021-04-01/resourceGroups) | | `Microsoft.Resources/tags` | [2019-10-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Resources/tags) | @@ -35,14 +35,14 @@ This module deploys a subscription to accelerate deployment of landing zones. Fo | `Microsoft.Storage/storageAccounts/blobServices` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices) | | `Microsoft.Storage/storageAccounts/blobServices/containers` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers) | | `Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies` | [2022-09-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2022-09-01/storageAccounts/blobServices/containers/immutabilityPolicies) | -| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/fileServices) | +| `Microsoft.Storage/storageAccounts/fileServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/fileServices) | | `Microsoft.Storage/storageAccounts/fileServices/shares` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/fileServices/shares) | -| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/localUsers) | +| `Microsoft.Storage/storageAccounts/localUsers` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/localUsers) | | `Microsoft.Storage/storageAccounts/managementPolicies` | [2023-01-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-01-01/storageAccounts/managementPolicies) | -| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices) | -| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/queueServices/queues) | -| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices) | -| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/storageAccounts/tableServices/tables) | +| `Microsoft.Storage/storageAccounts/queueServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices) | +| `Microsoft.Storage/storageAccounts/queueServices/queues` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/queueServices/queues) | +| `Microsoft.Storage/storageAccounts/tableServices` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices) | +| `Microsoft.Storage/storageAccounts/tableServices/tables` | [2023-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Storage/2023-04-01/storageAccounts/tableServices/tables) | | `Microsoft.Subscription/aliases` | [2021-10-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Subscription/2021-10-01/aliases) | ## Usage examples @@ -949,7 +949,7 @@ This section gives you an overview of all local-referenced module files (i.e., o | `br/public:avm/ptn/authorization/role-assignment:0.1.0` | Remote reference | | `br/public:avm/res/managed-identity/user-assigned-identity:0.2.2` | Remote reference | | `br/public:avm/res/network/network-security-group:0.3.0` | Remote reference | -| `br/public:avm/res/network/virtual-network:0.1.7` | Remote reference | +| `br/public:avm/res/network/virtual-network:0.4.0` | Remote reference | | `br/public:avm/res/resources/deployment-script:0.2.3` | Remote reference | | `br/public:avm/res/resources/resource-group:0.2.4` | Remote reference | | `br/public:avm/res/storage/storage-account:0.9.1` | Remote reference | diff --git a/avm/ptn/lz/sub-vending/main.json b/avm/ptn/lz/sub-vending/main.json index 35687dd6eac..e42574e3c15 100644 --- a/avm/ptn/lz/sub-vending/main.json +++ b/avm/ptn/lz/sub-vending/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2409780926109914899" + "version": "0.30.23.60470", + "templateHash": "2343164809013150587" }, "name": "Sub-vending", "description": "This module deploys a subscription to accelerate deployment of landing zones. For more information on how to use it, please visit this [Wiki](https://github.com/Azure/bicep-lz-vending/wiki).", @@ -445,8 +445,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3759867594724381121" + "version": "0.30.23.60470", + "templateHash": "1611270751895734589" } }, "parameters": { @@ -652,8 +652,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "10394721304346895394" + "version": "0.30.23.60470", + "templateHash": "12288692280280036332" }, "name": "`/subResourcesWrapper/deploy.bicep` Parameters", "description": "This module is used by the [`bicep-lz-vending`](https://aka.ms/sub-vending/bicep) module to help orchestrate the deployment", @@ -1028,8 +1028,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "13961984943235030681" + "version": "0.30.23.60470", + "templateHash": "15074465703139369012" } }, "parameters": { @@ -1086,8 +1086,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "6592820624705341105" + "version": "0.30.23.60470", + "templateHash": "15410141635305926698" } }, "parameters": { @@ -1146,8 +1146,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3589833223987550845" + "version": "0.30.23.60470", + "templateHash": "5472979603320584709" } }, "parameters": { @@ -1202,8 +1202,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15687156082548283745" + "version": "0.30.23.60470", + "templateHash": "11343593259864722989" } }, "parameters": { @@ -1280,8 +1280,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2885361202003966670" + "version": "0.30.23.60470", + "templateHash": "13884963778440627255" } }, "parameters": { @@ -1335,8 +1335,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "4327209924100539632" + "version": "0.30.23.60470", + "templateHash": "4428652978548820109" } }, "parameters": { @@ -1915,8 +1915,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "6592820624705341105" + "version": "0.30.23.60470", + "templateHash": "15410141635305926698" } }, "parameters": { @@ -1975,8 +1975,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3589833223987550845" + "version": "0.30.23.60470", + "templateHash": "5472979603320584709" } }, "parameters": { @@ -2031,8 +2031,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15687156082548283745" + "version": "0.30.23.60470", + "templateHash": "11343593259864722989" } }, "parameters": { @@ -2109,8 +2109,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2885361202003966670" + "version": "0.30.23.60470", + "templateHash": "13884963778440627255" } }, "parameters": { @@ -2164,8 +2164,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "4327209924100539632" + "version": "0.30.23.60470", + "templateHash": "4428652978548820109" } }, "parameters": { @@ -2285,7 +2285,7 @@ "ddosProtectionPlanResourceId": { "value": "[parameters('virtualNetworkDdosPlanResourceId')]" }, - "peerings": "[if(and(and(and(and(and(and(parameters('virtualNetworkEnabled'), parameters('virtualNetworkPeeringEnabled')), not(empty(variables('hubVirtualNetworkResourceIdChecked')))), not(empty(parameters('virtualNetworkName')))), not(empty(parameters('virtualNetworkAddressSpace')))), not(empty(parameters('virtualNetworkLocation')))), not(empty(parameters('virtualNetworkResourceGroupName')))), createObject('value', createArray(createObject('allowForwardedTraffic', true(), 'allowVirtualNetworkAccess', true(), 'allowGatewayTransit', false(), 'useRemoteGateways', parameters('virtualNetworkUseRemoteGateways'), 'remotePeeringEnabled', parameters('virtualNetworkPeeringEnabled'), 'remoteVirtualNetworkId', variables('hubVirtualNetworkResourceIdChecked'), 'remotePeeringAllowForwardedTraffic', true(), 'remotePeeringAllowVirtualNetworkAccess', true(), 'remotePeeringAllowGatewayTransit', true(), 'remotePeeringUseRemoteGateways', false()))), createObject('value', createArray()))]", + "peerings": "[if(and(and(and(and(and(and(parameters('virtualNetworkEnabled'), parameters('virtualNetworkPeeringEnabled')), not(empty(variables('hubVirtualNetworkResourceIdChecked')))), not(empty(parameters('virtualNetworkName')))), not(empty(parameters('virtualNetworkAddressSpace')))), not(empty(parameters('virtualNetworkLocation')))), not(empty(parameters('virtualNetworkResourceGroupName')))), createObject('value', createArray(createObject('remoteVirtualNetworkResourceId', variables('hubVirtualNetworkResourceIdChecked'), 'allowForwardedTraffic', true(), 'allowVirtualNetworkAccess', true(), 'allowGatewayTransit', false(), 'useRemoteGateways', parameters('virtualNetworkUseRemoteGateways'), 'remotePeeringEnabled', parameters('virtualNetworkPeeringEnabled'), 'remotePeeringAllowForwardedTraffic', true(), 'remotePeeringAllowVirtualNetworkAccess', true(), 'remotePeeringAllowGatewayTransit', true(), 'remotePeeringUseRemoteGateways', false()))), createObject('value', null()))]", "enableTelemetry": { "value": "[parameters('enableTelemetry')]" } @@ -2297,8 +2297,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "16637670595978489426" + "version": "0.29.47.4906", + "templateHash": "15949466154563447171" }, "name": "Virtual Networks", "description": "This module deploys a Virtual Network (vNet).", @@ -2335,6 +2335,13 @@ "items": { "type": "object", "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, "roleDefinitionIdOrName": { "type": "string", "metadata": { @@ -2515,6 +2522,242 @@ } }, "nullable": true + }, + "peeringType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be peer-localVnetName-remoteVnetName." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + }, + "remotePeeringEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Deploy the outbound and the inbound peering." + } + }, + "remotePeeringName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the VNET Peering resource in the remove Virtual Network. If not provided, default value will be peer-remoteVnetName-localVnetName." + } + }, + "remotePeeringAllowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "remotePeeringAllowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "remotePeeringAllowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "remotePeeringDoNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "remotePeeringUseRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private link service in the subnet." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "routeTableResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "serviceEndpoints": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + } + } } }, "parameters": { @@ -2537,32 +2780,48 @@ "description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network." } }, + "virtualNetworkBgpCommunity": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The BGP community associated with the virtual network." + } + }, "subnets": { "type": "array", - "defaultValue": [], + "items": { + "$ref": "#/definitions/subnetType" + }, + "nullable": true, "metadata": { "description": "Optional. An Array of subnets to deploy to the Virtual Network." } }, "dnsServers": { "type": "array", - "defaultValue": [], + "items": { + "type": "string" + }, + "nullable": true, "metadata": { "description": "Optional. DNS Servers associated to the Virtual Network." } }, "ddosProtectionPlanResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. Resource ID of the DDoS protection plan to assign the VNET to. If it's left blank, DDoS protection will not be configured. If it's provided, the VNET created by this template will be attached to the referenced DDoS protection plan. The DDoS protection plan can exist in the same or in a different subscription." } }, "peerings": { "type": "array", - "defaultValue": [], + "items": { + "$ref": "#/definitions/peeringType" + }, + "nullable": true, "metadata": { - "description": "Optional. Virtual Network Peerings configurations." + "description": "Optional. Virtual Network Peering configurations." } }, "vnetEncryption": { @@ -2622,15 +2881,29 @@ "metadata": { "description": "Optional. Enable/Disable usage telemetry for module." } + }, + "enableVmProtection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates if VM protection is enabled for all the subnets in the virtual network." + } } }, "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -2638,8 +2911,8 @@ "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2023-07-01", - "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.1.7', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -2657,42 +2930,21 @@ }, "virtualNetwork": { "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", "properties": { - "copy": [ - { - "name": "subnets", - "count": "[length(parameters('subnets'))]", - "input": { - "name": "[parameters('subnets')[copyIndex('subnets')].name]", - "properties": { - "addressPrefix": "[parameters('subnets')[copyIndex('subnets')].addressPrefix]", - "addressPrefixes": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'addressPrefixes'), parameters('subnets')[copyIndex('subnets')].addressPrefixes, createArray())]", - "applicationGatewayIPConfigurations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'applicationGatewayIPConfigurations'), parameters('subnets')[copyIndex('subnets')].applicationGatewayIPConfigurations, createArray())]", - "delegations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'delegations'), parameters('subnets')[copyIndex('subnets')].delegations, createArray())]", - "ipAllocations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'ipAllocations'), parameters('subnets')[copyIndex('subnets')].ipAllocations, createArray())]", - "natGateway": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'natGatewayResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].natGatewayResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].natGatewayResourceId), null())]", - "networkSecurityGroup": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'networkSecurityGroupResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].networkSecurityGroupResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].networkSecurityGroupResourceId), null())]", - "privateEndpointNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'privateEndpointNetworkPolicies'), parameters('subnets')[copyIndex('subnets')].privateEndpointNetworkPolicies, null())]", - "privateLinkServiceNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'privateLinkServiceNetworkPolicies'), parameters('subnets')[copyIndex('subnets')].privateLinkServiceNetworkPolicies, null())]", - "routeTable": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'routeTableResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].routeTableResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].routeTableResourceId), null())]", - "serviceEndpoints": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'serviceEndpoints'), parameters('subnets')[copyIndex('subnets')].serviceEndpoints, createArray())]", - "serviceEndpointPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'serviceEndpointPolicies'), parameters('subnets')[copyIndex('subnets')].serviceEndpointPolicies, createArray())]" - } - } - } - ], "addressSpace": { "addressPrefixes": "[parameters('addressPrefixes')]" }, + "bgpCommunities": "[if(not(empty(parameters('virtualNetworkBgpCommunity'))), createObject('virtualNetworkCommunity', parameters('virtualNetworkBgpCommunity')), null())]", "ddosProtectionPlan": "[if(not(empty(parameters('ddosProtectionPlanResourceId'))), createObject('id', parameters('ddosProtectionPlanResourceId')), null())]", "dhcpOptions": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', array(parameters('dnsServers'))), null())]", "enableDdosProtection": "[not(empty(parameters('ddosProtectionPlanResourceId')))]", "encryption": "[if(equals(parameters('vnetEncryption'), true()), createObject('enabled', parameters('vnetEncryption'), 'enforcement', parameters('vnetEncryptionEnforcement')), null())]", - "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]" + "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]", + "enableVmProtection": "[parameters('enableVmProtection')]" } }, "virtualNetwork_lock": { @@ -2753,20 +3005,20 @@ "virtualNetwork_roleAssignments": { "copy": { "name": "virtualNetwork_roleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", - "name": "[guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", - "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ "virtualNetwork" @@ -2775,7 +3027,7 @@ "virtualNetwork_subnets": { "copy": { "name": "virtualNetwork_subnets", - "count": "[length(parameters('subnets'))]", + "count": "[length(coalesce(parameters('subnets'), createArray()))]", "mode": "serial", "batchSize": 1 }, @@ -2792,23 +3044,50 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('subnets')[copyIndex()].name]" + "value": "[coalesce(parameters('subnets'), createArray())[copyIndex()].name]" }, "addressPrefix": { - "value": "[parameters('subnets')[copyIndex()].addressPrefix]" - }, - "addressPrefixes": "[if(contains(parameters('subnets')[copyIndex()], 'addressPrefixes'), createObject('value', parameters('subnets')[copyIndex()].addressPrefixes), createObject('value', createArray()))]", - "applicationGatewayIPConfigurations": "[if(contains(parameters('subnets')[copyIndex()], 'applicationGatewayIPConfigurations'), createObject('value', parameters('subnets')[copyIndex()].applicationGatewayIPConfigurations), createObject('value', createArray()))]", - "delegations": "[if(contains(parameters('subnets')[copyIndex()], 'delegations'), createObject('value', parameters('subnets')[copyIndex()].delegations), createObject('value', createArray()))]", - "ipAllocations": "[if(contains(parameters('subnets')[copyIndex()], 'ipAllocations'), createObject('value', parameters('subnets')[copyIndex()].ipAllocations), createObject('value', createArray()))]", - "natGatewayResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'natGatewayResourceId'), createObject('value', parameters('subnets')[copyIndex()].natGatewayResourceId), createObject('value', ''))]", - "networkSecurityGroupResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'networkSecurityGroupResourceId'), createObject('value', parameters('subnets')[copyIndex()].networkSecurityGroupResourceId), createObject('value', ''))]", - "privateEndpointNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'privateEndpointNetworkPolicies'), createObject('value', parameters('subnets')[copyIndex()].privateEndpointNetworkPolicies), createObject('value', ''))]", - "privateLinkServiceNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'privateLinkServiceNetworkPolicies'), createObject('value', parameters('subnets')[copyIndex()].privateLinkServiceNetworkPolicies), createObject('value', ''))]", - "roleAssignments": "[if(contains(parameters('subnets')[copyIndex()], 'roleAssignments'), createObject('value', parameters('subnets')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "routeTableResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'routeTableResourceId'), createObject('value', parameters('subnets')[copyIndex()].routeTableResourceId), createObject('value', ''))]", - "serviceEndpointPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'serviceEndpointPolicies'), createObject('value', parameters('subnets')[copyIndex()].serviceEndpointPolicies), createObject('value', createArray()))]", - "serviceEndpoints": "[if(contains(parameters('subnets')[copyIndex()], 'serviceEndpoints'), createObject('value', parameters('subnets')[copyIndex()].serviceEndpoints), createObject('value', createArray()))]" + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefix')]" + }, + "addressPrefixes": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefixes')]" + }, + "applicationGatewayIPConfigurations": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'applicationGatewayIPConfigurations')]" + }, + "delegation": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'delegation')]" + }, + "natGatewayResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'natGatewayResourceId')]" + }, + "networkSecurityGroupResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'networkSecurityGroupResourceId')]" + }, + "privateEndpointNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateEndpointNetworkPolicies')]" + }, + "privateLinkServiceNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateLinkServiceNetworkPolicies')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "routeTableResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'routeTableResourceId')]" + }, + "serviceEndpointPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpointPolicies')]" + }, + "serviceEndpoints": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpoints')]" + }, + "defaultOutboundAccess": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'defaultOutboundAccess')]" + }, + "sharingScope": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'sharingScope')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2817,8 +3096,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "9634407864982934565" + "version": "0.29.47.4906", + "templateHash": "5699372618313647761" }, "name": "Virtual Network Subnets", "description": "This module deploys a Virtual Network Subnet.", @@ -2830,6 +3109,13 @@ "items": { "type": "object", "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, "roleDefinitionIdOrName": { "type": "string", "metadata": { @@ -2896,7 +3182,7 @@ "name": { "type": "string", "metadata": { - "description": "Optional. The Name of the subnet resource." + "description": "Requird. The Name of the subnet resource." } }, "virtualNetworkName": { @@ -2907,41 +3193,45 @@ }, "addressPrefix": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The address prefix for the subnet." + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." } }, "networkSecurityGroupResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID of the network security group to assign to the subnet." } }, "routeTableResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID of the route table to assign to the subnet." } }, "serviceEndpoints": { "type": "array", + "items": { + "type": "string" + }, "defaultValue": [], "metadata": { "description": "Optional. The service endpoints to enable on the subnet." } }, - "delegations": { - "type": "array", - "defaultValue": [], + "delegation": { + "type": "string", + "nullable": true, "metadata": { - "description": "Optional. The delegations to enable on the subnet." + "description": "Optional. The delegation to enable on the subnet." } }, "natGatewayResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." } @@ -2955,7 +3245,7 @@ "" ], "metadata": { - "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." } }, "privateLinkServiceNetworkPolicies": { @@ -2967,28 +3257,42 @@ "" ], "metadata": { - "description": "Optional. enable or disable apply network policies on private link service in the subnet." + "description": "Optional. Enable or disable apply network policies on private link service in the subnet." } }, "addressPrefixes": { "type": "array", - "defaultValue": [], + "items": { + "type": "string" + }, + "nullable": true, "metadata": { - "description": "Optional. List of address prefixes for the subnet." + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." } }, - "applicationGatewayIPConfigurations": { - "type": "array", - "defaultValue": [], + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, "metadata": { - "description": "Optional. Application gateway IP configurations of virtual network resource." + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." } }, - "ipAllocations": { + "applicationGatewayIPConfigurations": { "type": "array", "defaultValue": [], "metadata": { - "description": "Optional. Array of IpAllocation which reference this subnet." + "description": "Optional. Application gateway IP configurations of virtual network resource." } }, "serviceEndpointPolicies": { @@ -3006,12 +3310,19 @@ } }, "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -3019,26 +3330,35 @@ "virtualNetwork": { "existing": true, "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[parameters('virtualNetworkName')]" }, "subnet": { "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]", "properties": { + "copy": [ + { + "name": "serviceEndpoints", + "count": "[length(parameters('serviceEndpoints'))]", + "input": { + "service": "[parameters('serviceEndpoints')[copyIndex('serviceEndpoints')]]" + } + } + ], "addressPrefix": "[parameters('addressPrefix')]", + "addressPrefixes": "[parameters('addressPrefixes')]", "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]", "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]", "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]", - "serviceEndpoints": "[parameters('serviceEndpoints')]", - "delegations": "[parameters('delegations')]", + "delegations": "[if(not(empty(parameters('delegation'))), createArray(createObject('name', parameters('delegation'), 'properties', createObject('serviceName', parameters('delegation')))), createArray())]", "privateEndpointNetworkPolicies": "[if(not(empty(parameters('privateEndpointNetworkPolicies'))), parameters('privateEndpointNetworkPolicies'), null())]", "privateLinkServiceNetworkPolicies": "[if(not(empty(parameters('privateLinkServiceNetworkPolicies'))), parameters('privateLinkServiceNetworkPolicies'), null())]", - "addressPrefixes": "[parameters('addressPrefixes')]", "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", - "ipAllocations": "[parameters('ipAllocations')]", - "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]" + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" }, "dependsOn": [ "virtualNetwork" @@ -3047,20 +3367,20 @@ "subnet_roleAssignments": { "copy": { "name": "subnet_roleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]", - "name": "[guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", - "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ "subnet" @@ -3089,19 +3409,19 @@ }, "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]" }, - "subnetAddressPrefix": { + "addressPrefix": { "type": "string", "metadata": { "description": "The address prefix for the subnet." }, - "value": "[reference('subnet').addressPrefix]" + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefix'), '')]" }, - "subnetAddressPrefixes": { + "addressPrefixes": { "type": "array", "metadata": { "description": "List of address prefixes for the subnet." }, - "value": "[if(not(empty(parameters('addressPrefixes'))), reference('subnet').addressPrefixes, createArray())]" + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefixes'), createArray())]" } } } @@ -3113,7 +3433,7 @@ "virtualNetwork_peering_local": { "copy": { "name": "virtualNetwork_peering_local", - "count": "[length(parameters('peerings'))]" + "count": "[length(coalesce(parameters('peerings'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -3127,15 +3447,27 @@ "localVnetName": { "value": "[parameters('name')]" }, - "remoteVirtualNetworkId": { - "value": "[parameters('peerings')[copyIndex()].remoteVirtualNetworkId]" + "remoteVirtualNetworkResourceId": { + "value": "[coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'name')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'doNotVerifyRemoteGateways')]" }, - "name": "[if(contains(parameters('peerings')[copyIndex()], 'name'), createObject('value', parameters('peerings')[copyIndex()].name), createObject('value', format('{0}-{1}', parameters('name'), last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')))))]", - "allowForwardedTraffic": "[if(contains(parameters('peerings')[copyIndex()], 'allowForwardedTraffic'), createObject('value', parameters('peerings')[copyIndex()].allowForwardedTraffic), createObject('value', true()))]", - "allowGatewayTransit": "[if(contains(parameters('peerings')[copyIndex()], 'allowGatewayTransit'), createObject('value', parameters('peerings')[copyIndex()].allowGatewayTransit), createObject('value', false()))]", - "allowVirtualNetworkAccess": "[if(contains(parameters('peerings')[copyIndex()], 'allowVirtualNetworkAccess'), createObject('value', parameters('peerings')[copyIndex()].allowVirtualNetworkAccess), createObject('value', true()))]", - "doNotVerifyRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'doNotVerifyRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].doNotVerifyRemoteGateways), createObject('value', true()))]", - "useRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'useRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].useRemoteGateways), createObject('value', false()))]" + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'useRemoteGateways')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -3143,8 +3475,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "39994426069187924" + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" }, "name": "Virtual Network Peerings", "description": "This module deploys a Virtual Network Peering.", @@ -3153,9 +3485,9 @@ "parameters": { "name": { "type": "string", - "defaultValue": "[format('{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkId'), '/')))]", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", "metadata": { - "description": "Optional. The Name of Vnet Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." } }, "localVnetName": { @@ -3164,7 +3496,7 @@ "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." } }, - "remoteVirtualNetworkId": { + "remoteVirtualNetworkResourceId": { "type": "string", "metadata": { "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." @@ -3209,7 +3541,7 @@ "resources": [ { "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", "properties": { "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", @@ -3218,7 +3550,7 @@ "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", "useRemoteGateways": "[parameters('useRemoteGateways')]", "remoteVirtualNetwork": { - "id": "[parameters('remoteVirtualNetworkId')]" + "id": "[parameters('remoteVirtualNetworkResourceId')]" } } } @@ -3255,14 +3587,14 @@ "virtualNetwork_peering_remote": { "copy": { "name": "virtualNetwork_peering_remote", - "count": "[length(parameters('peerings'))]" + "count": "[length(coalesce(parameters('peerings'), createArray()))]" }, - "condition": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringEnabled'), equals(parameters('peerings')[copyIndex()].remotePeeringEnabled, true()), false())]", + "condition": "[coalesce(tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringEnabled'), false())]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('{0}-virtualNetworkPeering-remote-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "subscriptionId": "[split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')[2]]", - "resourceGroup": "[split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')[4]]", + "subscriptionId": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[2]]", + "resourceGroup": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[4]]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -3270,17 +3602,29 @@ "mode": "Incremental", "parameters": { "localVnetName": { - "value": "[last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/'))]" + "value": "[last(split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/'))]" }, - "remoteVirtualNetworkId": { + "remoteVirtualNetworkResourceId": { "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" }, - "name": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringName'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringName), createObject('value', format('{0}-{1}', last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')), parameters('name'))))]", - "allowForwardedTraffic": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowForwardedTraffic'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowForwardedTraffic), createObject('value', true()))]", - "allowGatewayTransit": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowGatewayTransit'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowGatewayTransit), createObject('value', false()))]", - "allowVirtualNetworkAccess": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowVirtualNetworkAccess), createObject('value', true()))]", - "doNotVerifyRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringDoNotVerifyRemoteGateways), createObject('value', true()))]", - "useRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringUseRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringUseRemoteGateways), createObject('value', false()))]" + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringName')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways')]" + }, + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringUseRemoteGateways')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -3288,8 +3632,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "39994426069187924" + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" }, "name": "Virtual Network Peerings", "description": "This module deploys a Virtual Network Peering.", @@ -3298,9 +3642,9 @@ "parameters": { "name": { "type": "string", - "defaultValue": "[format('{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkId'), '/')))]", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", "metadata": { - "description": "Optional. The Name of Vnet Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." } }, "localVnetName": { @@ -3309,7 +3653,7 @@ "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." } }, - "remoteVirtualNetworkId": { + "remoteVirtualNetworkResourceId": { "type": "string", "metadata": { "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." @@ -3354,7 +3698,7 @@ "resources": [ { "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", "properties": { "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", @@ -3363,7 +3707,7 @@ "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", "useRemoteGateways": "[parameters('useRemoteGateways')]", "remoteVirtualNetwork": { - "id": "[parameters('remoteVirtualNetworkId')]" + "id": "[parameters('remoteVirtualNetworkResourceId')]" } } } @@ -3426,8 +3770,8 @@ "description": "The names of the deployed subnets." }, "copy": { - "count": "[length(parameters('subnets'))]", - "input": "[parameters('subnets')[copyIndex()].name]" + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.name.value]" } }, "subnetResourceIds": { @@ -3436,8 +3780,8 @@ "description": "The resource IDs of the deployed subnets." }, "copy": { - "count": "[length(parameters('subnets'))]", - "input": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), parameters('subnets')[copyIndex()].name)]" + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.resourceId.value]" } }, "location": { @@ -3445,7 +3789,7 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('virtualNetwork', '2023-11-01', 'full').location]" + "value": "[reference('virtualNetwork', '2024-01-01', 'full').location]" } } } @@ -3487,8 +3831,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3320668363507906643" + "version": "0.30.23.60470", + "templateHash": "15250207882926040999" } }, "parameters": { @@ -13299,28 +13643,7 @@ "[parameters('virtualNetworkDeploymentScriptAddressPrefix')]" ] }, - "subnets": { - "value": [ - { - "addressPrefix": "[if(not(empty(parameters('resourceProviders'))), cidrSubnet(parameters('virtualNetworkDeploymentScriptAddressPrefix'), 24, 0), null())]", - "name": "ds-subnet-001", - "networkSecurityGroupResourceId": "[if(not(empty(parameters('resourceProviders'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('deploymentScriptResourceGroupName')), 'Microsoft.Resources/deployments', variables('deploymentNames').createDsNsg), '2022-09-01').outputs.resourceId.value, null())]", - "serviceEndpoints": [ - { - "service": "Microsoft.Storage" - } - ], - "delegations": [ - { - "name": "Microsoft.ContainerInstance.containerGroups", - "properties": { - "serviceName": "Microsoft.ContainerInstance/containerGroups" - } - } - ] - } - ] - }, + "subnets": "[if(not(empty(parameters('resourceProviders'))), createObject('value', createArray(createObject('addressPrefix', if(not(empty(parameters('resourceProviders'))), cidrSubnet(parameters('virtualNetworkDeploymentScriptAddressPrefix'), 24, 0), null()), 'name', 'ds-subnet-001', 'networkSecurityGroupResourceId', if(not(empty(parameters('resourceProviders'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('subscriptionId'), parameters('deploymentScriptResourceGroupName')), 'Microsoft.Resources/deployments', variables('deploymentNames').createDsNsg), '2022-09-01').outputs.resourceId.value, null()), 'serviceEndpoints', createArray('Microsoft.Storage'), 'delegation', 'Microsoft.ContainerInstance/containerGroups'))), createObject('value', null()))]", "enableTelemetry": { "value": "[parameters('enableTelemetry')]" } @@ -13332,8 +13655,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "16637670595978489426" + "version": "0.29.47.4906", + "templateHash": "15949466154563447171" }, "name": "Virtual Networks", "description": "This module deploys a Virtual Network (vNet).", @@ -13370,6 +13693,13 @@ "items": { "type": "object", "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, "roleDefinitionIdOrName": { "type": "string", "metadata": { @@ -13550,6 +13880,242 @@ } }, "nullable": true + }, + "peeringType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be peer-localVnetName-remoteVnetName." + } + }, + "remoteVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." + } + }, + "allowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "allowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "allowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "doNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "useRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + }, + "remotePeeringEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Deploy the outbound and the inbound peering." + } + }, + "remotePeeringName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the VNET Peering resource in the remove Virtual Network. If not provided, default value will be peer-remoteVnetName-localVnetName." + } + }, + "remotePeeringAllowForwardedTraffic": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the forwarded traffic from the VMs in the local virtual network will be allowed/disallowed in remote virtual network. Default is true." + } + }, + "remotePeeringAllowGatewayTransit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If gateway links can be used in remote virtual networking to link to this virtual network. Default is false." + } + }, + "remotePeeringAllowVirtualNetworkAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether the VMs in the local virtual network space would be able to access the VMs in remote virtual network space. Default is true." + } + }, + "remotePeeringDoNotVerifyRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Do not verify the provisioning state of the remote gateway. Default is true." + } + }, + "remotePeeringUseRemoteGateways": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If remote gateways can be used on this virtual network. If the flag is set to true, and allowGatewayTransit on remote peering is also true, virtual network will use gateways of remote virtual network for transit. Only one peering can have this flag set to true. This flag cannot be set if virtual network already has a gateway. Default is false." + } + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." + } + }, + "applicationGatewayIPConfigurations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application gateway IP configurations of virtual network resource." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "natGatewayResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the network security group to assign to the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "allowedValues": [ + "", + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. enable or disable apply network policies on private link service in the subnet." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "routeTableResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the route table to assign to the subnet." + } + }, + "serviceEndpointPolicies": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of service endpoint policies." + } + }, + "serviceEndpoints": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The service endpoints to enable on the subnet." + } + }, + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." + } + }, + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + } + } } }, "parameters": { @@ -13572,32 +14138,48 @@ "description": "Required. An Array of 1 or more IP Address Prefixes for the Virtual Network." } }, + "virtualNetworkBgpCommunity": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The BGP community associated with the virtual network." + } + }, "subnets": { "type": "array", - "defaultValue": [], + "items": { + "$ref": "#/definitions/subnetType" + }, + "nullable": true, "metadata": { "description": "Optional. An Array of subnets to deploy to the Virtual Network." } }, "dnsServers": { "type": "array", - "defaultValue": [], + "items": { + "type": "string" + }, + "nullable": true, "metadata": { "description": "Optional. DNS Servers associated to the Virtual Network." } }, "ddosProtectionPlanResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. Resource ID of the DDoS protection plan to assign the VNET to. If it's left blank, DDoS protection will not be configured. If it's provided, the VNET created by this template will be attached to the referenced DDoS protection plan. The DDoS protection plan can exist in the same or in a different subscription." } }, "peerings": { "type": "array", - "defaultValue": [], + "items": { + "$ref": "#/definitions/peeringType" + }, + "nullable": true, "metadata": { - "description": "Optional. Virtual Network Peerings configurations." + "description": "Optional. Virtual Network Peering configurations." } }, "vnetEncryption": { @@ -13657,15 +14239,29 @@ "metadata": { "description": "Optional. Enable/Disable usage telemetry for module." } + }, + "enableVmProtection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates if VM protection is enabled for all the subnets in the virtual network." + } } }, "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -13673,8 +14269,8 @@ "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2023-07-01", - "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.1.7', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-virtualnetwork.{0}.{1}', replace('0.4.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -13692,42 +14288,21 @@ }, "virtualNetwork": { "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", "properties": { - "copy": [ - { - "name": "subnets", - "count": "[length(parameters('subnets'))]", - "input": { - "name": "[parameters('subnets')[copyIndex('subnets')].name]", - "properties": { - "addressPrefix": "[parameters('subnets')[copyIndex('subnets')].addressPrefix]", - "addressPrefixes": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'addressPrefixes'), parameters('subnets')[copyIndex('subnets')].addressPrefixes, createArray())]", - "applicationGatewayIPConfigurations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'applicationGatewayIPConfigurations'), parameters('subnets')[copyIndex('subnets')].applicationGatewayIPConfigurations, createArray())]", - "delegations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'delegations'), parameters('subnets')[copyIndex('subnets')].delegations, createArray())]", - "ipAllocations": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'ipAllocations'), parameters('subnets')[copyIndex('subnets')].ipAllocations, createArray())]", - "natGateway": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'natGatewayResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].natGatewayResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].natGatewayResourceId), null())]", - "networkSecurityGroup": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'networkSecurityGroupResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].networkSecurityGroupResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].networkSecurityGroupResourceId), null())]", - "privateEndpointNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'privateEndpointNetworkPolicies'), parameters('subnets')[copyIndex('subnets')].privateEndpointNetworkPolicies, null())]", - "privateLinkServiceNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'privateLinkServiceNetworkPolicies'), parameters('subnets')[copyIndex('subnets')].privateLinkServiceNetworkPolicies, null())]", - "routeTable": "[if(and(contains(parameters('subnets')[copyIndex('subnets')], 'routeTableResourceId'), not(empty(parameters('subnets')[copyIndex('subnets')].routeTableResourceId))), createObject('id', parameters('subnets')[copyIndex('subnets')].routeTableResourceId), null())]", - "serviceEndpoints": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'serviceEndpoints'), parameters('subnets')[copyIndex('subnets')].serviceEndpoints, createArray())]", - "serviceEndpointPolicies": "[if(contains(parameters('subnets')[copyIndex('subnets')], 'serviceEndpointPolicies'), parameters('subnets')[copyIndex('subnets')].serviceEndpointPolicies, createArray())]" - } - } - } - ], "addressSpace": { "addressPrefixes": "[parameters('addressPrefixes')]" }, + "bgpCommunities": "[if(not(empty(parameters('virtualNetworkBgpCommunity'))), createObject('virtualNetworkCommunity', parameters('virtualNetworkBgpCommunity')), null())]", "ddosProtectionPlan": "[if(not(empty(parameters('ddosProtectionPlanResourceId'))), createObject('id', parameters('ddosProtectionPlanResourceId')), null())]", "dhcpOptions": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', array(parameters('dnsServers'))), null())]", "enableDdosProtection": "[not(empty(parameters('ddosProtectionPlanResourceId')))]", "encryption": "[if(equals(parameters('vnetEncryption'), true()), createObject('enabled', parameters('vnetEncryption'), 'enforcement', parameters('vnetEncryptionEnforcement')), null())]", - "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]" + "flowTimeoutInMinutes": "[if(not(equals(parameters('flowTimeoutInMinutes'), 0)), parameters('flowTimeoutInMinutes'), null())]", + "enableVmProtection": "[parameters('enableVmProtection')]" } }, "virtualNetwork_lock": { @@ -13788,20 +14363,20 @@ "virtualNetwork_roleAssignments": { "copy": { "name": "virtualNetwork_roleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "scope": "[format('Microsoft.Network/virtualNetworks/{0}', parameters('name'))]", - "name": "[guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", - "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ "virtualNetwork" @@ -13810,7 +14385,7 @@ "virtualNetwork_subnets": { "copy": { "name": "virtualNetwork_subnets", - "count": "[length(parameters('subnets'))]", + "count": "[length(coalesce(parameters('subnets'), createArray()))]", "mode": "serial", "batchSize": 1 }, @@ -13827,23 +14402,50 @@ "value": "[parameters('name')]" }, "name": { - "value": "[parameters('subnets')[copyIndex()].name]" + "value": "[coalesce(parameters('subnets'), createArray())[copyIndex()].name]" }, "addressPrefix": { - "value": "[parameters('subnets')[copyIndex()].addressPrefix]" - }, - "addressPrefixes": "[if(contains(parameters('subnets')[copyIndex()], 'addressPrefixes'), createObject('value', parameters('subnets')[copyIndex()].addressPrefixes), createObject('value', createArray()))]", - "applicationGatewayIPConfigurations": "[if(contains(parameters('subnets')[copyIndex()], 'applicationGatewayIPConfigurations'), createObject('value', parameters('subnets')[copyIndex()].applicationGatewayIPConfigurations), createObject('value', createArray()))]", - "delegations": "[if(contains(parameters('subnets')[copyIndex()], 'delegations'), createObject('value', parameters('subnets')[copyIndex()].delegations), createObject('value', createArray()))]", - "ipAllocations": "[if(contains(parameters('subnets')[copyIndex()], 'ipAllocations'), createObject('value', parameters('subnets')[copyIndex()].ipAllocations), createObject('value', createArray()))]", - "natGatewayResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'natGatewayResourceId'), createObject('value', parameters('subnets')[copyIndex()].natGatewayResourceId), createObject('value', ''))]", - "networkSecurityGroupResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'networkSecurityGroupResourceId'), createObject('value', parameters('subnets')[copyIndex()].networkSecurityGroupResourceId), createObject('value', ''))]", - "privateEndpointNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'privateEndpointNetworkPolicies'), createObject('value', parameters('subnets')[copyIndex()].privateEndpointNetworkPolicies), createObject('value', ''))]", - "privateLinkServiceNetworkPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'privateLinkServiceNetworkPolicies'), createObject('value', parameters('subnets')[copyIndex()].privateLinkServiceNetworkPolicies), createObject('value', ''))]", - "roleAssignments": "[if(contains(parameters('subnets')[copyIndex()], 'roleAssignments'), createObject('value', parameters('subnets')[copyIndex()].roleAssignments), createObject('value', createArray()))]", - "routeTableResourceId": "[if(contains(parameters('subnets')[copyIndex()], 'routeTableResourceId'), createObject('value', parameters('subnets')[copyIndex()].routeTableResourceId), createObject('value', ''))]", - "serviceEndpointPolicies": "[if(contains(parameters('subnets')[copyIndex()], 'serviceEndpointPolicies'), createObject('value', parameters('subnets')[copyIndex()].serviceEndpointPolicies), createObject('value', createArray()))]", - "serviceEndpoints": "[if(contains(parameters('subnets')[copyIndex()], 'serviceEndpoints'), createObject('value', parameters('subnets')[copyIndex()].serviceEndpoints), createObject('value', createArray()))]" + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefix')]" + }, + "addressPrefixes": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'addressPrefixes')]" + }, + "applicationGatewayIPConfigurations": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'applicationGatewayIPConfigurations')]" + }, + "delegation": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'delegation')]" + }, + "natGatewayResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'natGatewayResourceId')]" + }, + "networkSecurityGroupResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'networkSecurityGroupResourceId')]" + }, + "privateEndpointNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateEndpointNetworkPolicies')]" + }, + "privateLinkServiceNetworkPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'privateLinkServiceNetworkPolicies')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "routeTableResourceId": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'routeTableResourceId')]" + }, + "serviceEndpointPolicies": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpointPolicies')]" + }, + "serviceEndpoints": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'serviceEndpoints')]" + }, + "defaultOutboundAccess": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'defaultOutboundAccess')]" + }, + "sharingScope": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'sharingScope')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -13852,8 +14454,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "9634407864982934565" + "version": "0.29.47.4906", + "templateHash": "5699372618313647761" }, "name": "Virtual Network Subnets", "description": "This module deploys a Virtual Network Subnet.", @@ -13865,6 +14467,13 @@ "items": { "type": "object", "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, "roleDefinitionIdOrName": { "type": "string", "metadata": { @@ -13931,7 +14540,7 @@ "name": { "type": "string", "metadata": { - "description": "Optional. The Name of the subnet resource." + "description": "Requird. The Name of the subnet resource." } }, "virtualNetworkName": { @@ -13942,41 +14551,45 @@ }, "addressPrefix": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The address prefix for the subnet." + "description": "Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty." } }, "networkSecurityGroupResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID of the network security group to assign to the subnet." } }, "routeTableResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID of the route table to assign to the subnet." } }, "serviceEndpoints": { "type": "array", + "items": { + "type": "string" + }, "defaultValue": [], "metadata": { "description": "Optional. The service endpoints to enable on the subnet." } }, - "delegations": { - "type": "array", - "defaultValue": [], + "delegation": { + "type": "string", + "nullable": true, "metadata": { - "description": "Optional. The delegations to enable on the subnet." + "description": "Optional. The delegation to enable on the subnet." } }, "natGatewayResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The resource ID of the NAT Gateway to use for the subnet." } @@ -13990,7 +14603,7 @@ "" ], "metadata": { - "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." } }, "privateLinkServiceNetworkPolicies": { @@ -14002,28 +14615,42 @@ "" ], "metadata": { - "description": "Optional. enable or disable apply network policies on private link service in the subnet." + "description": "Optional. Enable or disable apply network policies on private link service in the subnet." } }, "addressPrefixes": { "type": "array", - "defaultValue": [], + "items": { + "type": "string" + }, + "nullable": true, "metadata": { - "description": "Optional. List of address prefixes for the subnet." + "description": "Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty." } }, - "applicationGatewayIPConfigurations": { - "type": "array", - "defaultValue": [], + "defaultOutboundAccess": { + "type": "bool", + "nullable": true, "metadata": { - "description": "Optional. Application gateway IP configurations of virtual network resource." + "description": "Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet." } }, - "ipAllocations": { + "sharingScope": { + "type": "string", + "allowedValues": [ + "DelegatedServices", + "Tenant" + ], + "nullable": true, + "metadata": { + "description": "Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty." + } + }, + "applicationGatewayIPConfigurations": { "type": "array", "defaultValue": [], "metadata": { - "description": "Optional. Array of IpAllocation which reference this subnet." + "description": "Optional. Application gateway IP configurations of virtual network resource." } }, "serviceEndpointPolicies": { @@ -14041,12 +14668,19 @@ } }, "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", "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')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, @@ -14054,26 +14688,35 @@ "virtualNetwork": { "existing": true, "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[parameters('virtualNetworkName')]" }, "subnet": { "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[format('{0}/{1}', parameters('virtualNetworkName'), parameters('name'))]", "properties": { + "copy": [ + { + "name": "serviceEndpoints", + "count": "[length(parameters('serviceEndpoints'))]", + "input": { + "service": "[parameters('serviceEndpoints')[copyIndex('serviceEndpoints')]]" + } + } + ], "addressPrefix": "[parameters('addressPrefix')]", + "addressPrefixes": "[parameters('addressPrefixes')]", "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]", "routeTable": "[if(not(empty(parameters('routeTableResourceId'))), createObject('id', parameters('routeTableResourceId')), null())]", "natGateway": "[if(not(empty(parameters('natGatewayResourceId'))), createObject('id', parameters('natGatewayResourceId')), null())]", - "serviceEndpoints": "[parameters('serviceEndpoints')]", - "delegations": "[parameters('delegations')]", + "delegations": "[if(not(empty(parameters('delegation'))), createArray(createObject('name', parameters('delegation'), 'properties', createObject('serviceName', parameters('delegation')))), createArray())]", "privateEndpointNetworkPolicies": "[if(not(empty(parameters('privateEndpointNetworkPolicies'))), parameters('privateEndpointNetworkPolicies'), null())]", "privateLinkServiceNetworkPolicies": "[if(not(empty(parameters('privateLinkServiceNetworkPolicies'))), parameters('privateLinkServiceNetworkPolicies'), null())]", - "addressPrefixes": "[parameters('addressPrefixes')]", "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", - "ipAllocations": "[parameters('ipAllocations')]", - "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]" + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" }, "dependsOn": [ "virtualNetwork" @@ -14082,20 +14725,20 @@ "subnet_roleAssignments": { "copy": { "name": "subnet_roleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]" + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "scope": "[format('Microsoft.Network/virtualNetworks/{0}/subnets/{1}', parameters('virtualNetworkName'), parameters('name'))]", - "name": "[guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId, coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { - "roleDefinitionId": "[if(contains(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName), variables('builtInRoleNames')[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName], coalesce(parameters('roleAssignments'), createArray())[copyIndex()].roleDefinitionIdOrName)]", - "principalId": "[coalesce(parameters('roleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(parameters('roleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ "subnet" @@ -14124,19 +14767,19 @@ }, "value": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworkName'), parameters('name'))]" }, - "subnetAddressPrefix": { + "addressPrefix": { "type": "string", "metadata": { "description": "The address prefix for the subnet." }, - "value": "[reference('subnet').addressPrefix]" + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefix'), '')]" }, - "subnetAddressPrefixes": { + "addressPrefixes": { "type": "array", "metadata": { "description": "List of address prefixes for the subnet." }, - "value": "[if(not(empty(parameters('addressPrefixes'))), reference('subnet').addressPrefixes, createArray())]" + "value": "[coalesce(tryGet(reference('subnet'), 'addressPrefixes'), createArray())]" } } } @@ -14148,7 +14791,7 @@ "virtualNetwork_peering_local": { "copy": { "name": "virtualNetwork_peering_local", - "count": "[length(parameters('peerings'))]" + "count": "[length(coalesce(parameters('peerings'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -14162,15 +14805,27 @@ "localVnetName": { "value": "[parameters('name')]" }, - "remoteVirtualNetworkId": { - "value": "[parameters('peerings')[copyIndex()].remoteVirtualNetworkId]" + "remoteVirtualNetworkResourceId": { + "value": "[coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'name')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'allowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'doNotVerifyRemoteGateways')]" }, - "name": "[if(contains(parameters('peerings')[copyIndex()], 'name'), createObject('value', parameters('peerings')[copyIndex()].name), createObject('value', format('{0}-{1}', parameters('name'), last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')))))]", - "allowForwardedTraffic": "[if(contains(parameters('peerings')[copyIndex()], 'allowForwardedTraffic'), createObject('value', parameters('peerings')[copyIndex()].allowForwardedTraffic), createObject('value', true()))]", - "allowGatewayTransit": "[if(contains(parameters('peerings')[copyIndex()], 'allowGatewayTransit'), createObject('value', parameters('peerings')[copyIndex()].allowGatewayTransit), createObject('value', false()))]", - "allowVirtualNetworkAccess": "[if(contains(parameters('peerings')[copyIndex()], 'allowVirtualNetworkAccess'), createObject('value', parameters('peerings')[copyIndex()].allowVirtualNetworkAccess), createObject('value', true()))]", - "doNotVerifyRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'doNotVerifyRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].doNotVerifyRemoteGateways), createObject('value', true()))]", - "useRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'useRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].useRemoteGateways), createObject('value', false()))]" + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'useRemoteGateways')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -14178,8 +14833,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "39994426069187924" + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" }, "name": "Virtual Network Peerings", "description": "This module deploys a Virtual Network Peering.", @@ -14188,9 +14843,9 @@ "parameters": { "name": { "type": "string", - "defaultValue": "[format('{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkId'), '/')))]", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", "metadata": { - "description": "Optional. The Name of Vnet Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." } }, "localVnetName": { @@ -14199,7 +14854,7 @@ "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." } }, - "remoteVirtualNetworkId": { + "remoteVirtualNetworkResourceId": { "type": "string", "metadata": { "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." @@ -14244,7 +14899,7 @@ "resources": [ { "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", "properties": { "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", @@ -14253,7 +14908,7 @@ "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", "useRemoteGateways": "[parameters('useRemoteGateways')]", "remoteVirtualNetwork": { - "id": "[parameters('remoteVirtualNetworkId')]" + "id": "[parameters('remoteVirtualNetworkResourceId')]" } } } @@ -14290,14 +14945,14 @@ "virtualNetwork_peering_remote": { "copy": { "name": "virtualNetwork_peering_remote", - "count": "[length(parameters('peerings'))]" + "count": "[length(coalesce(parameters('peerings'), createArray()))]" }, - "condition": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringEnabled'), equals(parameters('peerings')[copyIndex()].remotePeeringEnabled, true()), false())]", + "condition": "[coalesce(tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringEnabled'), false())]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('{0}-virtualNetworkPeering-remote-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "subscriptionId": "[split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')[2]]", - "resourceGroup": "[split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')[4]]", + "subscriptionId": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[2]]", + "resourceGroup": "[split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/')[4]]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -14305,17 +14960,29 @@ "mode": "Incremental", "parameters": { "localVnetName": { - "value": "[last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/'))]" + "value": "[last(split(coalesce(parameters('peerings'), createArray())[copyIndex()].remoteVirtualNetworkResourceId, '/'))]" }, - "remoteVirtualNetworkId": { + "remoteVirtualNetworkResourceId": { "value": "[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]" }, - "name": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringName'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringName), createObject('value', format('{0}-{1}', last(split(parameters('peerings')[copyIndex()].remoteVirtualNetworkId, '/')), parameters('name'))))]", - "allowForwardedTraffic": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowForwardedTraffic'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowForwardedTraffic), createObject('value', true()))]", - "allowGatewayTransit": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowGatewayTransit'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowGatewayTransit), createObject('value', false()))]", - "allowVirtualNetworkAccess": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringAllowVirtualNetworkAccess), createObject('value', true()))]", - "doNotVerifyRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringDoNotVerifyRemoteGateways), createObject('value', true()))]", - "useRemoteGateways": "[if(contains(parameters('peerings')[copyIndex()], 'remotePeeringUseRemoteGateways'), createObject('value', parameters('peerings')[copyIndex()].remotePeeringUseRemoteGateways), createObject('value', false()))]" + "name": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringName')]" + }, + "allowForwardedTraffic": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowForwardedTraffic')]" + }, + "allowGatewayTransit": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowGatewayTransit')]" + }, + "allowVirtualNetworkAccess": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringAllowVirtualNetworkAccess')]" + }, + "doNotVerifyRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringDoNotVerifyRemoteGateways')]" + }, + "useRemoteGateways": { + "value": "[tryGet(coalesce(parameters('peerings'), createArray())[copyIndex()], 'remotePeeringUseRemoteGateways')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -14323,8 +14990,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.28.1.47646", - "templateHash": "39994426069187924" + "version": "0.29.47.4906", + "templateHash": "5206620163504251868" }, "name": "Virtual Network Peerings", "description": "This module deploys a Virtual Network Peering.", @@ -14333,9 +15000,9 @@ "parameters": { "name": { "type": "string", - "defaultValue": "[format('{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkId'), '/')))]", + "defaultValue": "[format('peer-{0}-{1}', parameters('localVnetName'), last(split(parameters('remoteVirtualNetworkResourceId'), '/')))]", "metadata": { - "description": "Optional. The Name of Vnet Peering resource. If not provided, default value will be localVnetName-remoteVnetName." + "description": "Optional. The Name of VNET Peering resource. If not provided, default value will be localVnetName-remoteVnetName." } }, "localVnetName": { @@ -14344,7 +15011,7 @@ "description": "Conditional. The name of the parent Virtual Network to add the peering to. Required if the template is used in a standalone deployment." } }, - "remoteVirtualNetworkId": { + "remoteVirtualNetworkResourceId": { "type": "string", "metadata": { "description": "Required. The Resource ID of the VNet that is this Local VNet is being peered to. Should be in the format of a Resource ID." @@ -14389,7 +15056,7 @@ "resources": [ { "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", - "apiVersion": "2023-11-01", + "apiVersion": "2024-01-01", "name": "[format('{0}/{1}', parameters('localVnetName'), parameters('name'))]", "properties": { "allowForwardedTraffic": "[parameters('allowForwardedTraffic')]", @@ -14398,7 +15065,7 @@ "doNotVerifyRemoteGateways": "[parameters('doNotVerifyRemoteGateways')]", "useRemoteGateways": "[parameters('useRemoteGateways')]", "remoteVirtualNetwork": { - "id": "[parameters('remoteVirtualNetworkId')]" + "id": "[parameters('remoteVirtualNetworkResourceId')]" } } } @@ -14461,8 +15128,8 @@ "description": "The names of the deployed subnets." }, "copy": { - "count": "[length(parameters('subnets'))]", - "input": "[parameters('subnets')[copyIndex()].name]" + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.name.value]" } }, "subnetResourceIds": { @@ -14471,8 +15138,8 @@ "description": "The resource IDs of the deployed subnets." }, "copy": { - "count": "[length(parameters('subnets'))]", - "input": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('name'), parameters('subnets')[copyIndex()].name)]" + "count": "[length(coalesce(parameters('subnets'), createArray()))]", + "input": "[reference(format('virtualNetwork_subnets[{0}]', copyIndex())).outputs.resourceId.value]" } }, "location": { @@ -14480,7 +15147,7 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('virtualNetwork', '2023-11-01', 'full').location]" + "value": "[reference('virtualNetwork', '2024-01-01', 'full').location]" } } } diff --git a/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep b/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep index bd8fddf1904..ab9684a5a64 100644 --- a/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep +++ b/avm/ptn/lz/sub-vending/modules/subResourceWrapper.bicep @@ -361,7 +361,7 @@ module tagResourceGroup 'tags.bicep' = if (virtualNetworkEnabled && !empty(virtu } } -module createLzVnet 'br/public:avm/res/network/virtual-network:0.1.7' = if (virtualNetworkEnabled && !empty(virtualNetworkName) && !empty(virtualNetworkAddressSpace) && !empty(virtualNetworkLocation) && !empty(virtualNetworkResourceGroupName)) { +module createLzVnet 'br/public:avm/res/network/virtual-network:0.4.0' = if (virtualNetworkEnabled && !empty(virtualNetworkName) && !empty(virtualNetworkAddressSpace) && !empty(virtualNetworkLocation) && !empty(virtualNetworkResourceGroupName)) { dependsOn: [ createResourceGroupForLzNetworking ] @@ -377,19 +377,19 @@ module createLzVnet 'br/public:avm/res/network/virtual-network:0.1.7' = if (virt peerings: (virtualNetworkEnabled && virtualNetworkPeeringEnabled && !empty(hubVirtualNetworkResourceIdChecked) && !empty(virtualNetworkName) && !empty(virtualNetworkAddressSpace) && !empty(virtualNetworkLocation) && !empty(virtualNetworkResourceGroupName)) ? [ { + remoteVirtualNetworkResourceId: hubVirtualNetworkResourceIdChecked allowForwardedTraffic: true allowVirtualNetworkAccess: true allowGatewayTransit: false useRemoteGateways: virtualNetworkUseRemoteGateways remotePeeringEnabled: virtualNetworkPeeringEnabled - remoteVirtualNetworkId: hubVirtualNetworkResourceIdChecked remotePeeringAllowForwardedTraffic: true remotePeeringAllowVirtualNetworkAccess: true remotePeeringAllowGatewayTransit: true remotePeeringUseRemoteGateways: false } ] - : [] + : null enableTelemetry: enableTelemetry } } @@ -552,7 +552,7 @@ module createDsStorageAccount 'br/public:avm/res/storage/storage-account:0.9.1' } } -module createDsVnet 'br/public:avm/res/network/virtual-network:0.1.7' = if (!empty(resourceProviders)) { +module createDsVnet 'br/public:avm/res/network/virtual-network:0.4.0' = if (!empty(resourceProviders)) { scope: resourceGroup(subscriptionId, deploymentScriptResourceGroupName) name: deploymentNames.createdsVnet params: { @@ -561,26 +561,21 @@ module createDsVnet 'br/public:avm/res/network/virtual-network:0.1.7' = if (!emp addressPrefixes: [ virtualNetworkDeploymentScriptAddressPrefix ] - subnets: [ - { - addressPrefix: !empty(resourceProviders) ? cidrSubnet(virtualNetworkDeploymentScriptAddressPrefix, 24, 0) : null - name: 'ds-subnet-001' - networkSecurityGroupResourceId: !empty(resourceProviders) ? createDsNsg.outputs.resourceId : null - serviceEndpoints: [ - { - service: 'Microsoft.Storage' - } - ] - delegations: [ + subnets: !empty(resourceProviders) + ? [ { - name: 'Microsoft.ContainerInstance.containerGroups' - properties: { - serviceName: 'Microsoft.ContainerInstance/containerGroups' - } + addressPrefix: !empty(resourceProviders) + ? cidrSubnet(virtualNetworkDeploymentScriptAddressPrefix, 24, 0) + : null + name: 'ds-subnet-001' + networkSecurityGroupResourceId: !empty(resourceProviders) ? createDsNsg.outputs.resourceId : null + serviceEndpoints: [ + 'Microsoft.Storage' + ] + delegation: 'Microsoft.ContainerInstance/containerGroups' } ] - } - ] + : null enableTelemetry: enableTelemetry } } From 2a435324edf8b63919af3c97b20eea38f14ef218 Mon Sep 17 00:00:00 2001 From: Tony Box Date: Mon, 30 Sep 2024 14:35:24 -0700 Subject: [PATCH 46/46] fix: add displayName to more submodules (#3342) ## Description Fixes #3328 - Add option to set displayName for authorizationserver, product, subscription resources ## Pipeline Reference | Pipeline | | -------- | | [![avm.res.api-management.service](https://github.com/tony-box/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml/badge.svg?branch=feat%2Fapim_apiupdate)](https://github.com/tony-box/bicep-registry-modules/actions/workflows/avm.res.api-management.service.yml) | ## Type of Change - [ ] Update to CI Environment or utilities (Non-module affecting changes) - [x] Azure Verified Module updates: - [x] Bugfix containing backwards-compatible bug fixes, and I have NOT bumped the MAJOR or MINOR version in `version.json`: - [x] Someone has opened a bug report issue, and I have included "Closes #{bug_report_issue_number}" in the PR description. - [ ] The bug was found by the module author, and no one has opened an issue to report it yet. - [ ] Feature update backwards compatible feature updates, and I have bumped the MINOR version in `version.json`. - [ ] Breaking changes and I have bumped the MAJOR version in `version.json`. - [x] Update to documentation ## Checklist - [x] I'm sure there are no other open Pull Requests for the same update/change - [x] I have run `Set-AVMModule` locally to generate the supporting module files. - [x] My corresponding pipelines / checks run clean and green without any errors or warnings --------- Co-authored-by: Tony Box --- avm/res/api-management/service/README.md | 12 ++ .../service/api-version-set/main.json | 4 +- .../service/api/diagnostics/main.json | 4 +- avm/res/api-management/service/api/main.json | 12 +- .../service/api/policy/main.json | 4 +- .../service/authorization-server/README.md | 8 ++ .../service/authorization-server/main.bicep | 6 +- .../service/authorization-server/main.json | 13 ++- .../api-management/service/backend/main.json | 4 +- .../api-management/service/cache/main.json | 4 +- .../service/identity-provider/main.json | 4 +- .../api-management/service/loggers/main.json | 4 +- avm/res/api-management/service/main.bicep | 3 + avm/res/api-management/service/main.json | 108 +++++++++++------- .../service/named-value/main.json | 4 +- .../api-management/service/policy/main.json | 4 +- .../service/portalsetting/main.json | 4 +- .../api-management/service/product/README.md | 8 ++ .../service/product/api/main.json | 4 +- .../service/product/group/main.json | 4 +- .../api-management/service/product/main.bicep | 6 +- .../api-management/service/product/main.json | 21 ++-- .../service/subscription/README.md | 8 ++ .../service/subscription/main.bicep | 6 +- .../service/subscription/main.json | 13 ++- .../service/tests/e2e/max/main.test.bicep | 3 + .../tests/e2e/waf-aligned/main.test.bicep | 2 + avm/res/api-management/service/version.json | 4 +- 28 files changed, 194 insertions(+), 87 deletions(-) diff --git a/avm/res/api-management/service/README.md b/avm/res/api-management/service/README.md index c219b2de19f..b95709dc50a 100644 --- a/avm/res/api-management/service/README.md +++ b/avm/res/api-management/service/README.md @@ -290,6 +290,7 @@ module service 'br/public:avm/res/api-management/service:' = { clientId: 'apimclientid' clientRegistrationEndpoint: 'http://localhost' clientSecret: '' + displayName: 'AuthServer1' grantTypes: [ 'authorizationCode' ] @@ -404,6 +405,7 @@ module service 'br/public:avm/res/api-management/service:' = { } ] approvalRequired: false + displayName: 'Starter' groups: [ { name: 'developers' @@ -436,6 +438,7 @@ module service 'br/public:avm/res/api-management/service:' = { subnetResourceId: '' subscriptions: [ { + displayName: 'testArmSubscriptionAllApis' name: 'testArmSubscriptionAllApis' scope: '/apis' } @@ -525,6 +528,7 @@ module service 'br/public:avm/res/api-management/service:' = { "clientId": "apimclientid", "clientRegistrationEndpoint": "http://localhost", "clientSecret": "", + "displayName": "AuthServer1", "grantTypes": [ "authorizationCode" ], @@ -663,6 +667,7 @@ module service 'br/public:avm/res/api-management/service:' = { } ], "approvalRequired": false, + "displayName": "Starter", "groups": [ { "name": "developers" @@ -703,6 +708,7 @@ module service 'br/public:avm/res/api-management/service:' = { "subscriptions": { "value": [ { + "displayName": "testArmSubscriptionAllApis", "name": "testArmSubscriptionAllApis", "scope": "/apis" } @@ -837,6 +843,7 @@ module service 'br/public:avm/res/api-management/service:' = { clientId: 'apimClientid' clientRegistrationEndpoint: 'https://localhost' clientSecret: '' + displayName: 'AuthServer1' grantTypes: [ 'authorizationCode' ] @@ -973,6 +980,7 @@ module service 'br/public:avm/res/api-management/service:' = { ] subscriptions: [ { + displayName: 'testArmSubscriptionAllApis' name: 'testArmSubscriptionAllApis' scope: '/apis' } @@ -1048,6 +1056,7 @@ module service 'br/public:avm/res/api-management/service:' = { "clientId": "apimClientid", "clientRegistrationEndpoint": "https://localhost", "clientSecret": "", + "displayName": "AuthServer1", "grantTypes": [ "authorizationCode" ], @@ -1212,6 +1221,7 @@ module service 'br/public:avm/res/api-management/service:' = { "subscriptions": { "value": [ { + "displayName": "testArmSubscriptionAllApis", "name": "testArmSubscriptionAllApis", "scope": "/apis" } @@ -1927,6 +1937,8 @@ A list of availability zones denoting where the resource needs to come from. Onl ## Notes +The latest version of this module only includes supported versions of the API Management resource. All unsupported versions of API Management have been removed from the related parameters. See the [API Management stv1 platform retirement](!https://learn.microsoft.com/en-us/azure/api-management/breaking-changes/stv1-platform-retirement-august-2024) article for more details. + ### Parameter Usage: `apiManagementServicePolicy`
diff --git a/avm/res/api-management/service/api-version-set/main.json b/avm/res/api-management/service/api-version-set/main.json index 85639acf5c3..061641030cb 100644 --- a/avm/res/api-management/service/api-version-set/main.json +++ b/avm/res/api-management/service/api-version-set/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "17159723717884761443" + "version": "0.30.23.60470", + "templateHash": "2492486199367242598" }, "name": "API Management Service API Version Sets", "description": "This module deploys an API Management Service API Version Set.", diff --git a/avm/res/api-management/service/api/diagnostics/main.json b/avm/res/api-management/service/api/diagnostics/main.json index 83e2b3a0038..6db7e0f4004 100644 --- a/avm/res/api-management/service/api/diagnostics/main.json +++ b/avm/res/api-management/service/api/diagnostics/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15630166564208731013" + "version": "0.30.23.60470", + "templateHash": "2531959928497745895" }, "name": "API Management Service APIs Diagnostics.", "description": "This module deploys an API Management Service API Diagnostics.", diff --git a/avm/res/api-management/service/api/main.json b/avm/res/api-management/service/api/main.json index 970b83350dc..a87b3409db5 100644 --- a/avm/res/api-management/service/api/main.json +++ b/avm/res/api-management/service/api/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "17160750790361326516" + "version": "0.30.23.60470", + "templateHash": "17036957862982683599" }, "name": "API Management Service APIs", "description": "This module deploys an API Management Service API.", @@ -283,8 +283,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2474188503939052987" + "version": "0.30.23.60470", + "templateHash": "5643177447182050438" }, "name": "API Management Service APIs Policies", "description": "This module deploys an API Management Service API Policy.", @@ -430,8 +430,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15630166564208731013" + "version": "0.30.23.60470", + "templateHash": "2531959928497745895" }, "name": "API Management Service APIs Diagnostics.", "description": "This module deploys an API Management Service API Diagnostics.", diff --git a/avm/res/api-management/service/api/policy/main.json b/avm/res/api-management/service/api/policy/main.json index 6defcce4a37..af5ae113079 100644 --- a/avm/res/api-management/service/api/policy/main.json +++ b/avm/res/api-management/service/api/policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2474188503939052987" + "version": "0.30.23.60470", + "templateHash": "5643177447182050438" }, "name": "API Management Service APIs Policies", "description": "This module deploys an API Management Service API Policy.", diff --git a/avm/res/api-management/service/authorization-server/README.md b/avm/res/api-management/service/authorization-server/README.md index d6996e57a16..234576e93d6 100644 --- a/avm/res/api-management/service/authorization-server/README.md +++ b/avm/res/api-management/service/authorization-server/README.md @@ -23,6 +23,7 @@ This module deploys an API Management Service Authorization Server. | [`authorizationEndpoint`](#parameter-authorizationendpoint) | string | OAuth authorization endpoint. See . | | [`clientId`](#parameter-clientid) | securestring | Client or app ID registered with this authorization server. | | [`clientSecret`](#parameter-clientsecret) | securestring | Client or app secret registered with this authorization server. This property will not be filled on 'GET' operations! Use '/listSecrets' POST request to get the value. | +| [`displayName`](#parameter-displayname) | string | API Management Service Authorization Servers name. Must be 1 to 50 characters long. | | [`grantTypes`](#parameter-granttypes) | array | Form of an authorization grant, which the client uses to request the access token. - authorizationCode, implicit, resourceOwnerPassword, clientCredentials. | | [`name`](#parameter-name) | string | Identifier of the authorization server. | @@ -69,6 +70,13 @@ Client or app secret registered with this authorization server. This property wi - Required: Yes - Type: securestring +### Parameter: `displayName` + +API Management Service Authorization Servers name. Must be 1 to 50 characters long. + +- Required: Yes +- Type: string + ### Parameter: `grantTypes` Form of an authorization grant, which the client uses to request the access token. - authorizationCode, implicit, resourceOwnerPassword, clientCredentials. diff --git a/avm/res/api-management/service/authorization-server/main.bicep b/avm/res/api-management/service/authorization-server/main.bicep index 3be6ae0b894..afe57ae5cd6 100644 --- a/avm/res/api-management/service/authorization-server/main.bicep +++ b/avm/res/api-management/service/authorization-server/main.bicep @@ -5,6 +5,10 @@ metadata owner = 'Azure/module-maintainers' @description('Required. Identifier of the authorization server.') param name string +@description('Required. API Management Service Authorization Servers name. Must be 1 to 50 characters long.') +@maxLength(50) +param displayName string + @description('Conditional. The name of the parent API Management service. Required if the template is used in a standalone deployment.') param apiManagementServiceName string @@ -85,7 +89,7 @@ resource authorizationServer 'Microsoft.ApiManagement/service/authorizationServe bearerTokenSendingMethods: bearerTokenSendingMethods resourceOwnerUsername: resourceOwnerUsername resourceOwnerPassword: resourceOwnerPassword - displayName: name + displayName: displayName clientRegistrationEndpoint: clientRegistrationEndpoint authorizationEndpoint: authorizationEndpoint grantTypes: grantTypes diff --git a/avm/res/api-management/service/authorization-server/main.json b/avm/res/api-management/service/authorization-server/main.json index e966a03d7fb..50d0897a93c 100644 --- a/avm/res/api-management/service/authorization-server/main.json +++ b/avm/res/api-management/service/authorization-server/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "4256977187793378377" + "version": "0.30.23.60470", + "templateHash": "17927787726774417819" }, "name": "API Management Service Authorization Servers", "description": "This module deploys an API Management Service Authorization Server.", @@ -18,6 +18,13 @@ "description": "Required. Identifier of the authorization server." } }, + "displayName": { + "type": "string", + "maxLength": 50, + "metadata": { + "description": "Required. API Management Service Authorization Servers name. Must be 1 to 50 characters long." + } + }, "apiManagementServiceName": { "type": "string", "metadata": { @@ -154,7 +161,7 @@ "bearerTokenSendingMethods": "[parameters('bearerTokenSendingMethods')]", "resourceOwnerUsername": "[parameters('resourceOwnerUsername')]", "resourceOwnerPassword": "[parameters('resourceOwnerPassword')]", - "displayName": "[parameters('name')]", + "displayName": "[parameters('displayName')]", "clientRegistrationEndpoint": "[parameters('clientRegistrationEndpoint')]", "authorizationEndpoint": "[parameters('authorizationEndpoint')]", "grantTypes": "[parameters('grantTypes')]", diff --git a/avm/res/api-management/service/backend/main.json b/avm/res/api-management/service/backend/main.json index 2a5ea70d52d..c3ae5f49b2e 100644 --- a/avm/res/api-management/service/backend/main.json +++ b/avm/res/api-management/service/backend/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2365531440872951056" + "version": "0.30.23.60470", + "templateHash": "14706757128951530017" }, "name": "API Management Service Backends", "description": "This module deploys an API Management Service Backend.", diff --git a/avm/res/api-management/service/cache/main.json b/avm/res/api-management/service/cache/main.json index b66a3778333..285f53b0fb4 100644 --- a/avm/res/api-management/service/cache/main.json +++ b/avm/res/api-management/service/cache/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3234729148013684780" + "version": "0.30.23.60470", + "templateHash": "2750555671183513052" }, "name": "API Management Service Caches", "description": "This module deploys an API Management Service Cache.", diff --git a/avm/res/api-management/service/identity-provider/main.json b/avm/res/api-management/service/identity-provider/main.json index f9e6cbe0869..6768ba8a3ee 100644 --- a/avm/res/api-management/service/identity-provider/main.json +++ b/avm/res/api-management/service/identity-provider/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "12757169124799431378" + "version": "0.30.23.60470", + "templateHash": "1342690797398622979" }, "name": "API Management Service Identity Providers", "description": "This module deploys an API Management Service Identity Provider.", diff --git a/avm/res/api-management/service/loggers/main.json b/avm/res/api-management/service/loggers/main.json index 9a6b6378bd1..7d3305a3cd9 100644 --- a/avm/res/api-management/service/loggers/main.json +++ b/avm/res/api-management/service/loggers/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "12986610229102962453" + "version": "0.30.23.60470", + "templateHash": "12834599511984803283" }, "name": "API Management Service Loggers", "description": "This module deploys an API Management Service Logger.", diff --git a/avm/res/api-management/service/main.bicep b/avm/res/api-management/service/main.bicep index 1e691994172..37a49b86c06 100644 --- a/avm/res/api-management/service/main.bicep +++ b/avm/res/api-management/service/main.bicep @@ -310,6 +310,7 @@ module service_authorizationServers 'authorization-server/main.bicep' = [ params: { apiManagementServiceName: service.name name: authorizationServer.name + displayName: authorizationServer.displayName authorizationEndpoint: authorizationServer.authorizationEndpoint authorizationMethods: authorizationServer.?authorizationMethods ?? ['GET'] bearerTokenSendingMethods: authorizationServer.?bearerTokenSendingMethods ?? ['authorizationHeader'] @@ -467,6 +468,7 @@ module service_products 'product/main.bicep' = [ for (product, index) in products: { name: '${uniqueString(deployment().name, location)}-Apim-Product-${index}' params: { + displayName: product.displayName apiManagementServiceName: service.name apis: product.?apis ?? [] approvalRequired: product.?approvalRequired ?? false @@ -490,6 +492,7 @@ module service_subscriptions 'subscription/main.bicep' = [ params: { apiManagementServiceName: service.name name: subscription.name + displayName: subscription.displayName allowTracing: subscription.?allowTracing ownerId: subscription.?ownerId primaryKey: subscription.?primaryKey diff --git a/avm/res/api-management/service/main.json b/avm/res/api-management/service/main.json index 64675ab0235..fc42a719667 100644 --- a/avm/res/api-management/service/main.json +++ b/avm/res/api-management/service/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "5150103142771299599" + "version": "0.30.23.60470", + "templateHash": "7676062632439815762" }, "name": "API Management Services", "description": "This module deploys an API Management Service. The default deployment is set to use a Premium SKU to align with Microsoft WAF-aligned best practices. In most cases, non-prod deployments should use a lower-tier SKU.", @@ -791,8 +791,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "17160750790361326516" + "version": "0.30.23.60470", + "templateHash": "17036957862982683599" }, "name": "API Management Service APIs", "description": "This module deploys an API Management Service API.", @@ -1069,8 +1069,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2474188503939052987" + "version": "0.30.23.60470", + "templateHash": "5643177447182050438" }, "name": "API Management Service APIs Policies", "description": "This module deploys an API Management Service API Policy.", @@ -1216,8 +1216,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15630166564208731013" + "version": "0.30.23.60470", + "templateHash": "2531959928497745895" }, "name": "API Management Service APIs Diagnostics.", "description": "This module deploys an API Management Service API Diagnostics.", @@ -1444,8 +1444,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "17159723717884761443" + "version": "0.30.23.60470", + "templateHash": "2492486199367242598" }, "name": "API Management Service API Version Sets", "description": "This module deploys an API Management Service API Version Set.", @@ -1530,6 +1530,9 @@ "name": { "value": "[variables('authorizationServerList')[copyIndex()].name]" }, + "displayName": { + "value": "[variables('authorizationServerList')[copyIndex()].displayName]" + }, "authorizationEndpoint": { "value": "[variables('authorizationServerList')[copyIndex()].authorizationEndpoint]" }, @@ -1582,8 +1585,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "4256977187793378377" + "version": "0.30.23.60470", + "templateHash": "17927787726774417819" }, "name": "API Management Service Authorization Servers", "description": "This module deploys an API Management Service Authorization Server.", @@ -1596,6 +1599,13 @@ "description": "Required. Identifier of the authorization server." } }, + "displayName": { + "type": "string", + "maxLength": 50, + "metadata": { + "description": "Required. API Management Service Authorization Servers name. Must be 1 to 50 characters long." + } + }, "apiManagementServiceName": { "type": "string", "metadata": { @@ -1732,7 +1742,7 @@ "bearerTokenSendingMethods": "[parameters('bearerTokenSendingMethods')]", "resourceOwnerUsername": "[parameters('resourceOwnerUsername')]", "resourceOwnerPassword": "[parameters('resourceOwnerPassword')]", - "displayName": "[parameters('name')]", + "displayName": "[parameters('displayName')]", "clientRegistrationEndpoint": "[parameters('clientRegistrationEndpoint')]", "authorizationEndpoint": "[parameters('authorizationEndpoint')]", "grantTypes": "[parameters('grantTypes')]", @@ -1825,8 +1835,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "2365531440872951056" + "version": "0.30.23.60470", + "templateHash": "14706757128951530017" }, "name": "API Management Service Backends", "description": "This module deploys an API Management Service Backend.", @@ -2009,8 +2019,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3234729148013684780" + "version": "0.30.23.60470", + "templateHash": "2750555671183513052" }, "name": "API Management Service Caches", "description": "This module deploys an API Management Service Cache.", @@ -2167,8 +2177,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "15630166564208731013" + "version": "0.30.23.60470", + "templateHash": "2531959928497745895" }, "name": "API Management Service APIs Diagnostics.", "description": "This module deploys an API Management Service API Diagnostics.", @@ -2397,8 +2407,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "12757169124799431378" + "version": "0.30.23.60470", + "templateHash": "1342690797398622979" }, "name": "API Management Service Identity Providers", "description": "This module deploys an API Management Service Identity Provider.", @@ -2610,8 +2620,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "12986610229102962453" + "version": "0.30.23.60470", + "templateHash": "12834599511984803283" }, "name": "API Management Service Loggers", "description": "This module deploys an API Management Service Logger.", @@ -2754,8 +2764,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3479776319506170502" + "version": "0.30.23.60470", + "templateHash": "10162843567606353040" }, "name": "API Management Service Named Values", "description": "This module deploys an API Management Service Named Value.", @@ -2895,8 +2905,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "10271256088614129674" + "version": "0.30.23.60470", + "templateHash": "14869704072680236257" }, "name": "API Management Service Portal Settings", "description": "This module deploys an API Management Service Portal Setting.", @@ -2994,8 +3004,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "11443463088593763324" + "version": "0.30.23.60470", + "templateHash": "9395795206748286282" }, "name": "API Management Service Policies", "description": "This module deploys an API Management Service Policy.", @@ -3089,6 +3099,9 @@ }, "mode": "Incremental", "parameters": { + "displayName": { + "value": "[parameters('products')[copyIndex()].displayName]" + }, "apiManagementServiceName": { "value": "[parameters('name')]" }, @@ -3126,8 +3139,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "6230115773857876317" + "version": "0.30.23.60470", + "templateHash": "8029364311033748838" }, "name": "API Management Service Products", "description": "This module deploys an API Management Service Product.", @@ -3140,6 +3153,13 @@ "description": "Conditional. The name of the parent API Management service. Required if the template is used in a standalone deployment." } }, + "displayName": { + "type": "string", + "maxLength": 300, + "metadata": { + "description": "Required. API Management Service Products name. Must be 1 to 300 characters long." + } + }, "approvalRequired": { "type": "bool", "defaultValue": false, @@ -3210,7 +3230,7 @@ "name": "[format('{0}/{1}', parameters('apiManagementServiceName'), parameters('name'))]", "properties": { "description": "[parameters('description')]", - "displayName": "[parameters('name')]", + "displayName": "[parameters('displayName')]", "terms": "[parameters('terms')]", "subscriptionRequired": "[parameters('subscriptionRequired')]", "approvalRequired": "[if(parameters('subscriptionRequired'), parameters('approvalRequired'), null())]", @@ -3248,8 +3268,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1052981479169082206" + "version": "0.30.23.60470", + "templateHash": "602104798329438871" }, "name": "API Management Service Products APIs", "description": "This module deploys an API Management Service Product API.", @@ -3338,8 +3358,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "5748451278124986706" + "version": "0.30.23.60470", + "templateHash": "5238408376918932137" }, "name": "API Management Service Products Groups", "description": "This module deploys an API Management Service Product Group.", @@ -3469,6 +3489,9 @@ "name": { "value": "[parameters('subscriptions')[copyIndex()].name]" }, + "displayName": { + "value": "[parameters('subscriptions')[copyIndex()].displayName]" + }, "allowTracing": { "value": "[tryGet(parameters('subscriptions')[copyIndex()], 'allowTracing')]" }, @@ -3495,8 +3518,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9499976066778278010" + "version": "0.30.23.60470", + "templateHash": "16082435269276611452" }, "name": "API Management Service Subscriptions", "description": "This module deploys an API Management Service Subscription.", @@ -3510,6 +3533,13 @@ "description": "Optional. Determines whether tracing can be enabled." } }, + "displayName": { + "type": "string", + "maxLength": 100, + "metadata": { + "description": "Required. API Management Service Subscriptions name. Must be 1 to 100 characters long." + } + }, "apiManagementServiceName": { "type": "string", "metadata": { @@ -3571,7 +3601,7 @@ "name": "[format('{0}/{1}', parameters('apiManagementServiceName'), parameters('name'))]", "properties": { "scope": "[parameters('scope')]", - "displayName": "[parameters('name')]", + "displayName": "[parameters('displayName')]", "ownerId": "[parameters('ownerId')]", "primaryKey": "[parameters('primaryKey')]", "secondaryKey": "[parameters('secondaryKey')]", diff --git a/avm/res/api-management/service/named-value/main.json b/avm/res/api-management/service/named-value/main.json index 4be9cba5187..b182535671d 100644 --- a/avm/res/api-management/service/named-value/main.json +++ b/avm/res/api-management/service/named-value/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "3479776319506170502" + "version": "0.30.23.60470", + "templateHash": "10162843567606353040" }, "name": "API Management Service Named Values", "description": "This module deploys an API Management Service Named Value.", diff --git a/avm/res/api-management/service/policy/main.json b/avm/res/api-management/service/policy/main.json index 83d94342400..dd3c7eab825 100644 --- a/avm/res/api-management/service/policy/main.json +++ b/avm/res/api-management/service/policy/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "11443463088593763324" + "version": "0.30.23.60470", + "templateHash": "9395795206748286282" }, "name": "API Management Service Policies", "description": "This module deploys an API Management Service Policy.", diff --git a/avm/res/api-management/service/portalsetting/main.json b/avm/res/api-management/service/portalsetting/main.json index 779c5741205..d68c8ed791c 100644 --- a/avm/res/api-management/service/portalsetting/main.json +++ b/avm/res/api-management/service/portalsetting/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "10271256088614129674" + "version": "0.30.23.60470", + "templateHash": "14869704072680236257" }, "name": "API Management Service Portal Settings", "description": "This module deploys an API Management Service Portal Setting.", diff --git a/avm/res/api-management/service/product/README.md b/avm/res/api-management/service/product/README.md index c5b8331a4af..9dbc604abbf 100644 --- a/avm/res/api-management/service/product/README.md +++ b/avm/res/api-management/service/product/README.md @@ -22,6 +22,7 @@ This module deploys an API Management Service Product. | Parameter | Type | Description | | :-- | :-- | :-- | +| [`displayName`](#parameter-displayname) | string | API Management Service Products name. Must be 1 to 300 characters long. | | [`name`](#parameter-name) | string | Product Name. | **Conditional parameters** @@ -43,6 +44,13 @@ This module deploys an API Management Service Product. | [`subscriptionsLimit`](#parameter-subscriptionslimit) | int | Whether the number of subscriptions a user can have to this product at the same time. Set to null or omit to allow unlimited per user subscriptions. Can be present only if subscriptionRequired property is present and has a value of false. | | [`terms`](#parameter-terms) | string | Product terms of use. Developers trying to subscribe to the product will be presented and required to accept these terms before they can complete the subscription process. | +### Parameter: `displayName` + +API Management Service Products name. Must be 1 to 300 characters long. + +- Required: Yes +- Type: string + ### Parameter: `name` Product Name. diff --git a/avm/res/api-management/service/product/api/main.json b/avm/res/api-management/service/product/api/main.json index 4042b9bf61a..5603f9f789f 100644 --- a/avm/res/api-management/service/product/api/main.json +++ b/avm/res/api-management/service/product/api/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1052981479169082206" + "version": "0.30.23.60470", + "templateHash": "602104798329438871" }, "name": "API Management Service Products APIs", "description": "This module deploys an API Management Service Product API.", diff --git a/avm/res/api-management/service/product/group/main.json b/avm/res/api-management/service/product/group/main.json index 4ac13f0dac8..28d5460152f 100644 --- a/avm/res/api-management/service/product/group/main.json +++ b/avm/res/api-management/service/product/group/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "5748451278124986706" + "version": "0.30.23.60470", + "templateHash": "5238408376918932137" }, "name": "API Management Service Products Groups", "description": "This module deploys an API Management Service Product Group.", diff --git a/avm/res/api-management/service/product/main.bicep b/avm/res/api-management/service/product/main.bicep index 97879742811..22ca0081c1a 100644 --- a/avm/res/api-management/service/product/main.bicep +++ b/avm/res/api-management/service/product/main.bicep @@ -5,6 +5,10 @@ metadata owner = 'Azure/module-maintainers' @sys.description('Conditional. The name of the parent API Management service. Required if the template is used in a standalone deployment.') param apiManagementServiceName string +@sys.description('Required. API Management Service Products name. Must be 1 to 300 characters long.') +@maxLength(300) +param displayName string + @sys.description('Optional. Whether subscription approval is required. If false, new subscriptions will be approved automatically enabling developers to call the products APIs immediately after subscribing. If true, administrators must manually approve the subscription before the developer can any of the products APIs. Can be present only if subscriptionRequired property is present and has a value of false.') param approvalRequired bool = false @@ -41,7 +45,7 @@ resource product 'Microsoft.ApiManagement/service/products@2022-08-01' = { parent: service properties: { description: description - displayName: name + displayName: displayName terms: terms subscriptionRequired: subscriptionRequired approvalRequired: subscriptionRequired ? approvalRequired : null diff --git a/avm/res/api-management/service/product/main.json b/avm/res/api-management/service/product/main.json index 73dd3977b6a..892a25de5cb 100644 --- a/avm/res/api-management/service/product/main.json +++ b/avm/res/api-management/service/product/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "6230115773857876317" + "version": "0.30.23.60470", + "templateHash": "8029364311033748838" }, "name": "API Management Service Products", "description": "This module deploys an API Management Service Product.", @@ -18,6 +18,13 @@ "description": "Conditional. The name of the parent API Management service. Required if the template is used in a standalone deployment." } }, + "displayName": { + "type": "string", + "maxLength": 300, + "metadata": { + "description": "Required. API Management Service Products name. Must be 1 to 300 characters long." + } + }, "approvalRequired": { "type": "bool", "defaultValue": false, @@ -88,7 +95,7 @@ "name": "[format('{0}/{1}', parameters('apiManagementServiceName'), parameters('name'))]", "properties": { "description": "[parameters('description')]", - "displayName": "[parameters('name')]", + "displayName": "[parameters('displayName')]", "terms": "[parameters('terms')]", "subscriptionRequired": "[parameters('subscriptionRequired')]", "approvalRequired": "[if(parameters('subscriptionRequired'), parameters('approvalRequired'), null())]", @@ -126,8 +133,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "1052981479169082206" + "version": "0.30.23.60470", + "templateHash": "602104798329438871" }, "name": "API Management Service Products APIs", "description": "This module deploys an API Management Service Product API.", @@ -216,8 +223,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "5748451278124986706" + "version": "0.30.23.60470", + "templateHash": "5238408376918932137" }, "name": "API Management Service Products Groups", "description": "This module deploys an API Management Service Product Group.", diff --git a/avm/res/api-management/service/subscription/README.md b/avm/res/api-management/service/subscription/README.md index c851bed0dd4..03e97821cd7 100644 --- a/avm/res/api-management/service/subscription/README.md +++ b/avm/res/api-management/service/subscription/README.md @@ -20,6 +20,7 @@ This module deploys an API Management Service Subscription. | Parameter | Type | Description | | :-- | :-- | :-- | +| [`displayName`](#parameter-displayname) | string | API Management Service Subscriptions name. Must be 1 to 100 characters long. | | [`name`](#parameter-name) | string | Subscription name. | **Conditional parameters** @@ -39,6 +40,13 @@ This module deploys an API Management Service Subscription. | [`secondaryKey`](#parameter-secondarykey) | string | Secondary subscription key. If not specified during request key will be generated automatically. | | [`state`](#parameter-state) | string | Initial subscription state. If no value is specified, subscription is created with Submitted state. Possible states are "*" active "?" the subscription is active, "*" suspended "?" the subscription is blocked, and the subscriber cannot call any APIs of the product, * submitted ? the subscription request has been made by the developer, but has not yet been approved or rejected, * rejected ? the subscription request has been denied by an administrator, * cancelled ? the subscription has been cancelled by the developer or administrator, * expired ? the subscription reached its expiration date and was deactivated. - suspended, active, expired, submitted, rejected, cancelled. | +### Parameter: `displayName` + +API Management Service Subscriptions name. Must be 1 to 100 characters long. + +- Required: Yes +- Type: string + ### Parameter: `name` Subscription name. diff --git a/avm/res/api-management/service/subscription/main.bicep b/avm/res/api-management/service/subscription/main.bicep index 1b1e9411be9..746242a7f9b 100644 --- a/avm/res/api-management/service/subscription/main.bicep +++ b/avm/res/api-management/service/subscription/main.bicep @@ -5,6 +5,10 @@ metadata owner = 'Azure/module-maintainers' @description('Optional. Determines whether tracing can be enabled.') param allowTracing bool = true +@description('Required. API Management Service Subscriptions name. Must be 1 to 100 characters long.') +@maxLength(100) +param displayName string + @description('Conditional. The name of the parent API Management service. Required if the template is used in a standalone deployment.') param apiManagementServiceName string @@ -35,7 +39,7 @@ resource subscription 'Microsoft.ApiManagement/service/subscriptions@2022-08-01' parent: service properties: { scope: scope - displayName: name + displayName: displayName ownerId: ownerId primaryKey: primaryKey secondaryKey: secondaryKey diff --git a/avm/res/api-management/service/subscription/main.json b/avm/res/api-management/service/subscription/main.json index 5510d608585..6abc772cc34 100644 --- a/avm/res/api-management/service/subscription/main.json +++ b/avm/res/api-management/service/subscription/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "9499976066778278010" + "version": "0.30.23.60470", + "templateHash": "16082435269276611452" }, "name": "API Management Service Subscriptions", "description": "This module deploys an API Management Service Subscription.", @@ -20,6 +20,13 @@ "description": "Optional. Determines whether tracing can be enabled." } }, + "displayName": { + "type": "string", + "maxLength": 100, + "metadata": { + "description": "Required. API Management Service Subscriptions name. Must be 1 to 100 characters long." + } + }, "apiManagementServiceName": { "type": "string", "metadata": { @@ -81,7 +88,7 @@ "name": "[format('{0}/{1}', parameters('apiManagementServiceName'), parameters('name'))]", "properties": { "scope": "[parameters('scope')]", - "displayName": "[parameters('name')]", + "displayName": "[parameters('displayName')]", "ownerId": "[parameters('ownerId')]", "primaryKey": "[parameters('primaryKey')]", "secondaryKey": "[parameters('secondaryKey')]", diff --git a/avm/res/api-management/service/tests/e2e/max/main.test.bicep b/avm/res/api-management/service/tests/e2e/max/main.test.bicep index 29894956458..e0419365c5f 100644 --- a/avm/res/api-management/service/tests/e2e/max/main.test.bicep +++ b/avm/res/api-management/service/tests/e2e/max/main.test.bicep @@ -124,6 +124,7 @@ module testDeployment '../../../main.bicep' = [ 'authorizationCode' ] name: 'AuthServer1' + displayName: 'AuthServer1' tokenEndpoint: '${environment().authentication.loginEndpoint}651b43ce-ccb8-4301-b551-b04dd872d401/oauth2/v2.0/token' } ] @@ -241,6 +242,7 @@ module testDeployment '../../../main.bicep' = [ } ] name: 'Starter' + displayName: 'Starter' subscriptionRequired: false } ] @@ -270,6 +272,7 @@ module testDeployment '../../../main.bicep' = [ { name: 'testArmSubscriptionAllApis' scope: '/apis' + displayName: 'testArmSubscriptionAllApis' } ] managedIdentities: { diff --git a/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep b/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep index ccf1f295b4b..b94ad37da75 100644 --- a/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep +++ b/avm/res/api-management/service/tests/e2e/waf-aligned/main.test.bicep @@ -128,6 +128,7 @@ module testDeployment '../../../main.bicep' = [ 'authorizationCode' ] name: 'AuthServer1' + displayName: 'AuthServer1' tokenEndpoint: '${environment().authentication.loginEndpoint}651b43ce-ccb8-4301-b551-b04dd872d401/oauth2/v2.0/token' } ] @@ -243,6 +244,7 @@ module testDeployment '../../../main.bicep' = [ { name: 'testArmSubscriptionAllApis' scope: '/apis' + displayName: 'testArmSubscriptionAllApis' } ] tags: { diff --git a/avm/res/api-management/service/version.json b/avm/res/api-management/service/version.json index a8eda31021f..9ed3662abaf 100644 --- a/avm/res/api-management/service/version.json +++ b/avm/res/api-management/service/version.json @@ -1,7 +1,7 @@ { "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", - "version": "0.5", + "version": "0.6", "pathFilters": [ "./main.json" ] -} \ No newline at end of file +}
Changelog

Sourced from github/codeql-action's changelog.

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

Note that the only difference between v2 and v3 of the CodeQL Action is the node version they support, with v3 running on node 20 while we continue to release v2 to support running on node 16. For example 3.22.11 was the first v3 release and is functionally identical to 2.22.11. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.

[UNRELEASED]

No user facing changes.

3.26.9 - 24 Sep 2024

No user facing changes.

3.26.8 - 19 Sep 2024

  • Update default CodeQL bundle version to 2.19.0. #2483

3.26.7 - 13 Sep 2024

  • Update default CodeQL bundle version to 2.18.4. #2471

3.26.6 - 29 Aug 2024

  • Update default CodeQL bundle version to 2.18.3. #2449

3.26.5 - 23 Aug 2024

  • Fix an issue where the csrutil system call used for telemetry would fail on MacOS ARM machines with System Integrity Protection disabled. #2441

3.26.4 - 21 Aug 2024

  • Deprecation: The add-snippets input on the analyze Action is deprecated and will be removed in the first release in August 2025. #2436
  • Fix an issue where the disk usage system call used for telemetry would fail on MacOS ARM machines with System Integrity Protection disabled, and then surface a warning. The system call is now disabled for these machines. #2434

3.26.3 - 19 Aug 2024

  • Fix an issue where the CodeQL Action could not write diagnostic messages on Windows. This issue did not impact analysis quality. #2430

3.26.2 - 14 Aug 2024

  • Update default CodeQL bundle version to 2.18.2. #2417

3.26.1 - 13 Aug 2024

No user facing changes.

3.26.0 - 06 Aug 2024

  • Deprecation: Swift analysis on Ubuntu runner images is no longer supported. Please migrate to a macOS runner if this affects you. #2403

... (truncated)