From 1a8347fec7ce6cfc0b856169c39b8ef1a7e9abd5 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Fri, 25 Jul 2025 13:41:49 +0530 Subject: [PATCH 01/84] model deployment when reusing existing foundry --- infra/deploy_ai_foundry.bicep | 41 +++++++++++--- infra/deploy_app_service.bicep | 26 ++++++++- infra/deploy_foundry_role_assignment.bicep | 63 ++++++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index a8797a154..b2c72202c 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -25,7 +25,7 @@ var aiSearchName = '${abbrs.ai.aiSearch}${solutionName}' var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}' var aiModelDeployments = [ { - name: gptModelName + name: '${gptModelName}-deployment' model: gptModelName sku: { name: deploymentType @@ -34,7 +34,7 @@ var aiModelDeployments = [ raiPolicyName: 'Microsoft.Default' } { - name: embeddingModel + name: '${embeddingModel}-deployment' model: embeddingModel sku: { name: 'GlobalStandard' @@ -72,6 +72,9 @@ var existingAIProjectName = !empty(azureExistingAIProjectResourceId) var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[2] : '' +var existingAIServicesName = !empty(azureExistingAIProjectResourceId) + ? split(azureExistingAIProjectResourceId, '/')[8] + : '' var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[4] : '' @@ -225,15 +228,37 @@ resource cognitiveServicesOpenAIUser 'Microsoft.Authorization/roleDefinitions@20 name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' } -module assignOpenAIRoleToAISearch 'deploy_foundry_role_assignment.bicep' = { - name: 'assignOpenAIRoleToAISearch' + +resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { + name: guid(resourceGroup().id, aiFoundry.id, cognitiveServicesOpenAIUser.id) + scope: aiFoundry + properties: { + principalId: aiSearch.identity.principalId + roleDefinitionId: cognitiveServicesOpenAIUser.id + principalType: 'ServicePrincipal' + } +} + +module assignOpenAIRoleToAISearchExisting 'deploy_foundry_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { + name: 'assignOpenAIRoleToAISearchExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { roleDefinitionId: cognitiveServicesOpenAIUser.id roleAssignmentName: guid(resourceGroup().id, aiSearch.id, cognitiveServicesOpenAIUser.id, 'openai-foundry') aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIFoundryName : aiFoundryName - aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName principalId: aiSearch.identity.principalId + aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName + aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName + aiModelDeployments: aiModelDeployments + enableSystemAssignedIdentity: !empty(azureExistingAIProjectResourceId) ? false : true + aiLocation: location + aiKind: 'OpenAI' + aiSkuName: 'S0' + customSubDomainName: !empty(azureExistingAIProjectResourceId) ? '' : aiFoundryName + publicNetworkAccess: 'Enabled' + defaultNetworkAction: 'Allow' + vnetRules: [] + ipRules: [] } } @@ -257,7 +282,7 @@ resource assignSearchIndexDataReaderToExistingAiProject 'Microsoft.Authorization scope: aiSearch properties: { roleDefinitionId: searchIndexDataReaderRoleDefinition.id - principalId: assignOpenAIRoleToAISearch.outputs.aiProjectPrincipalId + principalId: assignOpenAIRoleToAISearchExisting.outputs.aiProjectPrincipalId principalType: 'ServicePrincipal' } } @@ -283,7 +308,7 @@ resource searchServiceContributorRoleAssignmentExisting 'Microsoft.Authorization scope: aiSearch properties: { roleDefinitionId: searchServiceContributorRoleDefinition.id - principalId: assignOpenAIRoleToAISearch.outputs.aiProjectPrincipalId + principalId: assignOpenAIRoleToAISearchExisting.outputs.aiProjectPrincipalId principalType: 'ServicePrincipal' } } @@ -376,3 +401,5 @@ output logAnalyticsWorkspaceResourceGroup string = useExisting ? existingLawReso output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString output aiSearchFoundryConnectionName string = aiSearchConnectionName +output aiAppInsightsFoundryConnectionName string = aiAppInsightConnectionName +output aiModelDeployments array = aiModelDeployments diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index 648fbf16f..d18069641 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -157,6 +157,7 @@ var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) var existingAIServicesName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[8] : '' +var existingAIProjectName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : '' resource HostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { name: HostingPlanName @@ -413,14 +414,35 @@ resource aiUserRoleDefinitionFoundry 'Microsoft.Authorization/roleDefinitions@20 name: '53ca6127-db72-4b80-b1b0-d745d6d5456d' } -module assignAiUserRoleToAiProject 'deploy_foundry_role_assignment.bicep' = { - name: 'assignAiUserRoleToAiProject' +resource assignAiUserRoleToAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { + name: guid(resourceGroup().id, aiFoundry.id, aiUserRoleDefinitionFoundry.id) + // scope: aiProject + properties: { + principalId: Website.identity.principalId + roleDefinitionId: aiUserRoleDefinitionFoundry.id + principalType: 'ServicePrincipal' + } +} + +module assignAiUserRoleToAiProjectExisting 'deploy_foundry_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { + name: 'assignAiUserRoleToAiProjectExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { principalId: Website.identity.principalId roleDefinitionId: aiUserRoleDefinitionFoundry.id roleAssignmentName: guid(Website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName + aiServicesName: existingAIServicesName + aiProjectName: existingAIProjectName + enableSystemAssignedIdentity: !empty(azureExistingAIProjectResourceId) ? false : true + aiLocation: solutionLocation + aiKind: 'OpenAI' + aiSkuName: 'S0' + customSubDomainName: !empty(azureExistingAIProjectResourceId) ? '' : WebsiteName + publicNetworkAccess: 'Enabled' + defaultNetworkAction: 'Allow' + vnetRules: [] + ipRules: [] } } diff --git a/infra/deploy_foundry_role_assignment.bicep b/infra/deploy_foundry_role_assignment.bicep index 13b215850..05e46ccd6 100644 --- a/infra/deploy_foundry_role_assignment.bicep +++ b/infra/deploy_foundry_role_assignment.bicep @@ -3,16 +3,79 @@ param roleDefinitionId string param roleAssignmentName string = '' param aiFoundryName string param aiProjectName string = '' +param aiModelDeployments array = [] +param aiLocation string='' +param aiKind string='' +param aiSkuName string='' +param customSubDomainName string = '' +param publicNetworkAccess string = '' +param defaultNetworkAction string = '' +param aiServicesName string +param vnetRules array = [] +param ipRules array = [] +param enableSystemAssignedIdentity bool = false + resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { name: aiFoundryName } +resource aiServicesWithIdentity 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (enableSystemAssignedIdentity) { + name: aiServicesName + location: aiLocation + kind: aiKind + sku: { + name: aiSkuName + } + identity: { + type: 'SystemAssigned' + } + properties: { + allowProjectManagement: true + customSubDomainName: customSubDomainName + networkAcls: { + defaultAction: defaultNetworkAction + virtualNetworkRules: vnetRules + ipRules: ipRules + } + publicNetworkAccess: publicNetworkAccess + + } +} + +@batchSize(1) +resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { + parent: aiServicesWithIdentity + name: aiModeldeployment.name + properties: { + model: { + format: 'OpenAI' + name: aiModeldeployment.model + } + raiPolicyName: aiModeldeployment.raiPolicyName + } + sku:{ + name: aiModeldeployment.sku.name + capacity: aiModeldeployment.sku.capacity + } +}] + resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { name: aiProjectName parent: aiServices } + +resource aiProjectWithIdentity 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (!empty(aiProjectName) && enableSystemAssignedIdentity) { + name: aiProjectName + parent: aiServicesWithIdentity + location: aiLocation + identity: { + type: 'SystemAssigned' + } + properties: {} +} + resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: roleAssignmentName scope: aiServices From 383706b63e1543047fe03a4db58ff38cb66d6bd6 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Fri, 25 Jul 2025 13:44:44 +0530 Subject: [PATCH 02/84] change model deployment name --- infra/deploy_ai_foundry.bicep | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index b2c72202c..6dd4795c7 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -25,7 +25,7 @@ var aiSearchName = '${abbrs.ai.aiSearch}${solutionName}' var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}' var aiModelDeployments = [ { - name: '${gptModelName}-deployment' + name: gptModelName model: gptModelName sku: { name: deploymentType @@ -34,7 +34,7 @@ var aiModelDeployments = [ raiPolicyName: 'Microsoft.Default' } { - name: '${embeddingModel}-deployment' + name: embeddingModel model: embeddingModel sku: { name: 'GlobalStandard' From 5ccc7c10a1da0f749f5d64bea200e6bfa8acec05 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Mon, 28 Jul 2025 20:26:26 +0530 Subject: [PATCH 03/84] removed system managed identity code --- infra/deploy_ai_foundry.bicep | 12 +----- infra/deploy_app_service.bicep | 10 ----- infra/deploy_foundry_role_assignment.bicep | 45 +--------------------- 3 files changed, 2 insertions(+), 65 deletions(-) diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 6dd4795c7..9bc178912 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -247,18 +247,8 @@ module assignOpenAIRoleToAISearchExisting 'deploy_foundry_role_assignment.bicep' roleAssignmentName: guid(resourceGroup().id, aiSearch.id, cognitiveServicesOpenAIUser.id, 'openai-foundry') aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIFoundryName : aiFoundryName principalId: aiSearch.identity.principalId - aiServicesName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName - aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName +aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName aiModelDeployments: aiModelDeployments - enableSystemAssignedIdentity: !empty(azureExistingAIProjectResourceId) ? false : true - aiLocation: location - aiKind: 'OpenAI' - aiSkuName: 'S0' - customSubDomainName: !empty(azureExistingAIProjectResourceId) ? '' : aiFoundryName - publicNetworkAccess: 'Enabled' - defaultNetworkAction: 'Allow' - vnetRules: [] - ipRules: [] } } diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index d18069641..13abfad77 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -432,17 +432,7 @@ module assignAiUserRoleToAiProjectExisting 'deploy_foundry_role_assignment.bicep roleDefinitionId: aiUserRoleDefinitionFoundry.id roleAssignmentName: guid(Website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName - aiServicesName: existingAIServicesName aiProjectName: existingAIProjectName - enableSystemAssignedIdentity: !empty(azureExistingAIProjectResourceId) ? false : true - aiLocation: solutionLocation - aiKind: 'OpenAI' - aiSkuName: 'S0' - customSubDomainName: !empty(azureExistingAIProjectResourceId) ? '' : WebsiteName - publicNetworkAccess: 'Enabled' - defaultNetworkAction: 'Allow' - vnetRules: [] - ipRules: [] } } diff --git a/infra/deploy_foundry_role_assignment.bicep b/infra/deploy_foundry_role_assignment.bicep index 05e46ccd6..f57204b49 100644 --- a/infra/deploy_foundry_role_assignment.bicep +++ b/infra/deploy_foundry_role_assignment.bicep @@ -4,48 +4,15 @@ param roleAssignmentName string = '' param aiFoundryName string param aiProjectName string = '' param aiModelDeployments array = [] -param aiLocation string='' -param aiKind string='' -param aiSkuName string='' -param customSubDomainName string = '' -param publicNetworkAccess string = '' -param defaultNetworkAction string = '' -param aiServicesName string -param vnetRules array = [] -param ipRules array = [] -param enableSystemAssignedIdentity bool = false resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { name: aiFoundryName } -resource aiServicesWithIdentity 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (enableSystemAssignedIdentity) { - name: aiServicesName - location: aiLocation - kind: aiKind - sku: { - name: aiSkuName - } - identity: { - type: 'SystemAssigned' - } - properties: { - allowProjectManagement: true - customSubDomainName: customSubDomainName - networkAcls: { - defaultAction: defaultNetworkAction - virtualNetworkRules: vnetRules - ipRules: ipRules - } - publicNetworkAccess: publicNetworkAccess - - } -} - @batchSize(1) resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { - parent: aiServicesWithIdentity + parent: aiServices name: aiModeldeployment.name properties: { model: { @@ -66,16 +33,6 @@ resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-pre } -resource aiProjectWithIdentity 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (!empty(aiProjectName) && enableSystemAssignedIdentity) { - name: aiProjectName - parent: aiServicesWithIdentity - location: aiLocation - identity: { - type: 'SystemAssigned' - } - properties: {} -} - resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: roleAssignmentName scope: aiServices From 99ad1693eb045f84fda664e9d0e1c02becad12f9 Mon Sep 17 00:00:00 2001 From: Priyanka-Microsoft Date: Wed, 30 Jul 2025 14:26:30 +0530 Subject: [PATCH 04/84] seggregated the files --- infra/deploy_foundry_role_assignment.bicep | 28 ++++++------------- infra/model_deployments.bicep | 32 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 infra/model_deployments.bicep diff --git a/infra/deploy_foundry_role_assignment.bicep b/infra/deploy_foundry_role_assignment.bicep index f57204b49..585a3f018 100644 --- a/infra/deploy_foundry_role_assignment.bicep +++ b/infra/deploy_foundry_role_assignment.bicep @@ -10,26 +10,14 @@ resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' ex name: aiFoundryName } -@batchSize(1) -resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { - parent: aiServices - name: aiModeldeployment.name - properties: { - model: { - format: 'OpenAI' - name: aiModeldeployment.model - } - raiPolicyName: aiModeldeployment.raiPolicyName - } - sku:{ - name: aiModeldeployment.sku.name - capacity: aiModeldeployment.sku.capacity +// Call the model deployments module +module modelDeployments 'model_deployments.bicep' = { + name: 'modelDeployments' + params: { + aiFoundryName: aiFoundryName + aiProjectName: aiProjectName + aiModelDeployments: aiModelDeployments } -}] - -resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { - name: aiProjectName - parent: aiServices } @@ -44,4 +32,4 @@ resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-0 } output aiServicesPrincipalId string = aiServices.identity.principalId -output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' +output aiProjectPrincipalId string = !empty(aiProjectName) ? modelDeployments.outputs.aiProjectPrincipalId : '' diff --git a/infra/model_deployments.bicep b/infra/model_deployments.bicep new file mode 100644 index 000000000..54e3b0eb2 --- /dev/null +++ b/infra/model_deployments.bicep @@ -0,0 +1,32 @@ +param aiFoundryName string +param aiProjectName string = '' +param aiModelDeployments array = [] + +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { + name: aiFoundryName +} + +@batchSize(1) +resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { + parent: aiServices + name: aiModeldeployment.name + properties: { + model: { + format: 'OpenAI' + name: aiModeldeployment.model + } + raiPolicyName: aiModeldeployment.raiPolicyName + } + sku: { + name: aiModeldeployment.sku.name + capacity: aiModeldeployment.sku.capacity + } +}] + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { + name: aiProjectName + parent: aiServices +} + +output aiServices string = aiServices.identity.principalId +output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' From e0747b2781194c6d76490c44774e7456c5df065a Mon Sep 17 00:00:00 2001 From: Prajwal D C Date: Fri, 1 Aug 2025 16:40:27 +0530 Subject: [PATCH 05/84] fix: Updated file names --- infra/deploy_ai_foundry.bicep | 2 +- infra/deploy_app_service.bicep | 2 +- ...eploy_foundry_model_role_assignment.bicep} | 17 ++- infra/deploy_foundry_role_assignment.bicep | 35 ----- infra/main.json | 130 ++++++++++++++++-- 5 files changed, 134 insertions(+), 52 deletions(-) rename infra/{model_deployments.bicep => deploy_foundry_model_role_assignment.bicep} (67%) delete mode 100644 infra/deploy_foundry_role_assignment.bicep diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 9bc178912..057c13e1c 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -239,7 +239,7 @@ resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@202 } } -module assignOpenAIRoleToAISearchExisting 'deploy_foundry_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { +module assignOpenAIRoleToAISearchExisting 'deploy_foundry_model_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { name: 'assignOpenAIRoleToAISearchExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index 13abfad77..e622a11e0 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -424,7 +424,7 @@ resource assignAiUserRoleToAiProject 'Microsoft.Authorization/roleAssignments@20 } } -module assignAiUserRoleToAiProjectExisting 'deploy_foundry_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { +module assignAiUserRoleToAiProjectExisting 'deploy_foundry_model_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { name: 'assignAiUserRoleToAiProjectExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { diff --git a/infra/model_deployments.bicep b/infra/deploy_foundry_model_role_assignment.bicep similarity index 67% rename from infra/model_deployments.bicep rename to infra/deploy_foundry_model_role_assignment.bicep index 54e3b0eb2..26c30afdb 100644 --- a/infra/model_deployments.bicep +++ b/infra/deploy_foundry_model_role_assignment.bicep @@ -1,11 +1,16 @@ +param principalId string = '' +param roleDefinitionId string +param roleAssignmentName string = '' param aiFoundryName string param aiProjectName string = '' param aiModelDeployments array = [] + resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { name: aiFoundryName } +// Call the model deployments module @batchSize(1) resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { parent: aiServices @@ -28,5 +33,15 @@ resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-pre parent: aiServices } -output aiServices string = aiServices.identity.principalId +resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: roleAssignmentName + scope: aiServices + properties: { + roleDefinitionId: roleDefinitionId + principalId: principalId + principalType: 'ServicePrincipal' + } +} + +output aiServicesPrincipalId string = aiServices.identity.principalId output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' diff --git a/infra/deploy_foundry_role_assignment.bicep b/infra/deploy_foundry_role_assignment.bicep deleted file mode 100644 index 585a3f018..000000000 --- a/infra/deploy_foundry_role_assignment.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param principalId string = '' -param roleDefinitionId string -param roleAssignmentName string = '' -param aiFoundryName string -param aiProjectName string = '' -param aiModelDeployments array = [] - - -resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: aiFoundryName -} - -// Call the model deployments module -module modelDeployments 'model_deployments.bicep' = { - name: 'modelDeployments' - params: { - aiFoundryName: aiFoundryName - aiProjectName: aiProjectName - aiModelDeployments: aiModelDeployments - } -} - - -resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: roleAssignmentName - scope: aiServices - properties: { - roleDefinitionId: roleDefinitionId - principalId: principalId - principalType: 'ServicePrincipal' - } -} - -output aiServicesPrincipalId string = aiServices.identity.principalId -output aiProjectPrincipalId string = !empty(aiProjectName) ? modelDeployments.outputs.aiProjectPrincipalId : '' diff --git a/infra/main.json b/infra/main.json index 647023052..f2f7c7651 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "2238194529646818649" + "templateHash": "4417018826919204350" } }, "parameters": { @@ -744,7 +744,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "1124249040831466979" + "templateHash": "13609803072209612016" } }, "parameters": { @@ -1053,6 +1053,7 @@ "existingAIFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", "existingAIProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[10], '')]", "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], '')]", + "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], '')]", "aiSearchConnectionName": "[format('foundry-search-connection-{0}', parameters('solutionName'))]", "aiAppInsightConnectionName": "[format('foundry-app-insights-connection-{0}', parameters('solutionName'))]" @@ -1208,6 +1209,22 @@ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" ] }, + { + "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('aiFoundryName'))]", + "name": "[guid(resourceGroup().id, resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'))]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]", + "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" + ] + }, { "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", "type": "Microsoft.Authorization/roleAssignments", @@ -1232,12 +1249,12 @@ "name": "[guid(resourceGroup().id, variables('existingAIProjectName'), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f'), 'Existing')]", "properties": { "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", - "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch'), '2022-09-01').outputs.aiProjectPrincipalId.value]", + "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting'), '2022-09-01').outputs.aiProjectPrincipalId.value]", "principalType": "ServicePrincipal" }, "dependsOn": [ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch')]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting')]" ] }, { @@ -1264,12 +1281,12 @@ "name": "[guid(resourceGroup().id, variables('existingAIProjectName'), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), 'Existing')]", "properties": { "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", - "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch'), '2022-09-01').outputs.aiProjectPrincipalId.value]", + "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting'), '2022-09-01').outputs.aiProjectPrincipalId.value]", "principalType": "ServicePrincipal" }, "dependsOn": [ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearch')]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting')]" ] }, { @@ -1428,9 +1445,10 @@ ] }, { + "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "assignOpenAIRoleToAISearch", + "name": "assignOpenAIRoleToAISearchExisting", "subscriptionId": "[variables('existingAIServiceSubscription')]", "resourceGroup": "[variables('existingAIServiceResourceGroup')]", "properties": { @@ -1446,9 +1464,12 @@ "value": "[guid(resourceGroup().id, resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'), 'openai-foundry')]" }, "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIFoundryName')), createObject('value', variables('aiFoundryName')))]", - "aiProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIProjectName')), createObject('value', variables('aiProjectName')))]", "principalId": { "value": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').identity.principalId]" + }, + "aiProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIProjectName')), createObject('value', variables('aiProjectName')))]", + "aiModelDeployments": { + "value": "[variables('aiModelDeployments')]" } }, "template": { @@ -1458,7 +1479,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "11899270249637077405" + "templateHash": "9088273662782005929" } }, "parameters": { @@ -1479,9 +1500,36 @@ "aiProjectName": { "type": "string", "defaultValue": "" + }, + "aiModelDeployments": { + "type": "array", + "defaultValue": [] } }, "resources": [ + { + "copy": { + "name": "aiServicesDeployments", + "count": "[length(parameters('aiModelDeployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[not(empty(parameters('aiModelDeployments')))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('aiModelDeployments')[copyIndex()].name)]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('aiModelDeployments')[copyIndex()].model]" + }, + "raiPolicyName": "[parameters('aiModelDeployments')[copyIndex()].raiPolicyName]" + }, + "sku": { + "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", + "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" + } + }, { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", @@ -1575,6 +1623,14 @@ "aiSearchFoundryConnectionName": { "type": "string", "value": "[variables('aiSearchConnectionName')]" + }, + "aiAppInsightsFoundryConnectionName": { + "type": "string", + "value": "[variables('aiAppInsightConnectionName')]" + }, + "aiModelDeployments": { + "type": "array", + "value": "[variables('aiModelDeployments')]" } } } @@ -2244,7 +2300,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "10507186896960913919" + "templateHash": "3681696487679035168" } }, "parameters": { @@ -2583,7 +2639,8 @@ "WebAppImageName": "[format('DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:{0}', parameters('imageTag'))]", "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], subscription().subscriptionId)]", "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], resourceGroup().name)]", - "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]" + "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", + "existingAIProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[10], '')]" }, "resources": [ { @@ -2803,6 +2860,20 @@ "[resourceId('Microsoft.Web/serverfarms', parameters('HostingPlanName'))]" ] }, + { + "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceGroup().id, extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('WebsiteName')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('WebsiteName'))]" + ] + }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -2865,9 +2936,10 @@ ] }, { + "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "assignAiUserRoleToAiProject", + "name": "assignAiUserRoleToAiProjectExisting", "subscriptionId": "[variables('existingAIServiceSubscription')]", "resourceGroup": "[variables('existingAIServiceResourceGroup')]", "properties": { @@ -2885,7 +2957,10 @@ "roleAssignmentName": { "value": "[guid(parameters('WebsiteName'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]" }, - "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIServicesName')), createObject('value', parameters('aiFoundryName')))]" + "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIServicesName')), createObject('value', parameters('aiFoundryName')))]", + "aiProjectName": { + "value": "[variables('existingAIProjectName')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -2894,7 +2969,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "11899270249637077405" + "templateHash": "9088273662782005929" } }, "parameters": { @@ -2915,9 +2990,36 @@ "aiProjectName": { "type": "string", "defaultValue": "" + }, + "aiModelDeployments": { + "type": "array", + "defaultValue": [] } }, "resources": [ + { + "copy": { + "name": "aiServicesDeployments", + "count": "[length(parameters('aiModelDeployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[not(empty(parameters('aiModelDeployments')))]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('aiModelDeployments')[copyIndex()].name)]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('aiModelDeployments')[copyIndex()].model]" + }, + "raiPolicyName": "[parameters('aiModelDeployments')[copyIndex()].raiPolicyName]" + }, + "sku": { + "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", + "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" + } + }, { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", From dc627e4dc81c5916218a95c8d78531193d88764e Mon Sep 17 00:00:00 2001 From: Rafi-Microsoft Date: Wed, 6 Aug 2025 15:51:24 +0530 Subject: [PATCH 06/84] chore: down merging main into dev (#638) * fixed opent telemetry issue CustomDomainInUse, FlagMustBeSetForRestore (#618) (#619) Co-authored-by: VishalS-Microsoft * directory update in dependabot template (#634) --------- Co-authored-by: NirajC-Microsoft Co-authored-by: VishalS-Microsoft Co-authored-by: Prajwal-Microsoft --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7cca764a6..2f7bbe826 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -31,7 +31,7 @@ updates: # 3. Python dependencies – Fabric Scripts - package-ecosystem: "pip" - directory: "/src/infra/scripts/fabric_scripts" + directory: "/infra/scripts/fabric_scripts" schedule: interval: "monthly" commit-message: @@ -45,7 +45,7 @@ updates: # 4. Python dependencies – Index Scripts - package-ecosystem: "pip" - directory: "/src/infra/scripts/index_scripts" + directory: "/infra/scripts/index_scripts" schedule: interval: "monthly" commit-message: From c50954962b6ffaa4ebe0e5cd0da726e238fe3966 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Thu, 7 Aug 2025 15:17:56 +0530 Subject: [PATCH 07/84] added for cross-subscription existing AI project resource ID --- infra/deploy_ai_foundry.bicep | 11 ++++++++--- infra/main.bicep | 20 +++++++++++++++----- infra/main.json | 16 ++++++++-------- infra/scripts/process_sample_data.sh | 18 +++++++++--------- infra/scripts/run_create_index_scripts.sh | 7 ++----- 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 057c13e1c..c0e5b9f7e 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -146,6 +146,11 @@ resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04 } } +resource existingAiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = if (!empty(azureExistingAIProjectResourceId)) { + name: existingAIFoundryName + scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) +} + @batchSize(1) resource aiFModelDeployments 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [ for aiModeldeployment in aiModelDeployments: if (empty(azureExistingAIProjectResourceId)) { @@ -228,8 +233,7 @@ resource cognitiveServicesOpenAIUser 'Microsoft.Authorization/roleDefinitions@20 name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' } - -resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { +resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { name: guid(resourceGroup().id, aiFoundry.id, cognitiveServicesOpenAIUser.id) scope: aiFoundry properties: { @@ -247,7 +251,7 @@ module assignOpenAIRoleToAISearchExisting 'deploy_foundry_model_role_assignment. roleAssignmentName: guid(resourceGroup().id, aiSearch.id, cognitiveServicesOpenAIUser.id, 'openai-foundry') aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIFoundryName : aiFoundryName principalId: aiSearch.identity.principalId -aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName + aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName aiModelDeployments: aiModelDeployments } } @@ -377,6 +381,7 @@ output aoaiEndpoint string = !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint output aiFoundryName string = !empty(existingAIFoundryName) ? existingAIFoundryName : aiFoundryName //aiServicesName_m +output aiFoundryId string = !empty(azureExistingAIProjectResourceId) ? existingAiFoundry.id : aiFoundry.id output aiSearchName string = aiSearchName output aiSearchId string = aiSearch.id diff --git a/infra/main.bicep b/infra/main.bicep index 705259f56..14b447c98 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -54,12 +54,21 @@ param embeddingDeploymentCapacity int = 80 param imageTag string = 'latest' //restricting to these regions because assistants api for gpt-4o-mini is available only in these regions -@allowed(['australiaeast','eastus', 'eastus2','francecentral','japaneast','swedencentral','uksouth', 'westus', 'westus3']) +@allowed([ + 'australiaeast' + 'eastus' + 'eastus2' + 'francecentral' + 'japaneast' + 'swedencentral' + 'uksouth' + 'westus' + 'westus3' +]) // @description('Azure OpenAI Location') // param AzureOpenAILocation string = 'eastus2' - @metadata({ - azd:{ + azd: { type: 'location' usageName: [ 'OpenAI.GlobalStandard.gpt-4o-mini,200' @@ -278,11 +287,12 @@ output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageConta output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName output RESOURCE_GROUP_NAME string = resourceGroup().name -output RESOURCE_GROUP_NAME_FOUNDRY string = aifoundry.outputs.resourceGroupNameFoundry +//output RESOURCE_GROUP_NAME_FOUNDRY string = aifoundry.outputs.resourceGroupNameFoundry +output AI_FOUNDRY_RESOURCE_ID string = aifoundry.outputs.aiFoundryId output SQLDB_SERVER string = sqlDBModule.outputs.sqlServerName output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId -output AI_FOUNDRY_NAME string = aifoundry.outputs.aiFoundryName +//output AI_FOUNDRY_NAME string = aifoundry.outputs.aiFoundryName output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService output WEB_APP_NAME string = appserviceModule.outputs.webAppName diff --git a/infra/main.json b/infra/main.json index 058de995b..086ef9c03 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "7434704573623874481" + "templateHash": "7745904353912380124" } }, "parameters": { @@ -744,7 +744,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "13609803072209612016" + "templateHash": "419718136593168234" } }, "parameters": { @@ -1584,6 +1584,10 @@ "type": "string", "value": "[if(not(empty(variables('existingAIFoundryName'))), variables('existingAIFoundryName'), variables('aiFoundryName'))]" }, + "aiFoundryId": { + "type": "string", + "value": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', variables('existingAIFoundryName')), resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')))]" + }, "aiSearchName": { "type": "string", "value": "[variables('aiSearchName')]" @@ -3098,9 +3102,9 @@ "type": "string", "value": "[resourceGroup().name]" }, - "RESOURCE_GROUP_NAME_FOUNDRY": { + "AI_FOUNDRY_RESOURCE_ID": { "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.resourceGroupNameFoundry.value]" + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryId.value]" }, "SQLDB_SERVER": { "type": "string", @@ -3118,10 +3122,6 @@ "type": "string", "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" }, - "AI_FOUNDRY_NAME": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" - }, "AI_SEARCH_SERVICE_NAME": { "type": "string", "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index 7b6213eee..820642caf 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -10,18 +10,14 @@ sqlServerName="$6" SqlDatabaseName="$7" webAppManagedIdentityClientId="$8" webAppManagedIdentityDisplayName="$9" -aiFoundryName="${10}" -aiSearchName="${11}" -resourceGroupNameFoundry="${12}" +aiSearchName="${10}" +aif_resource_id="${11}" # get parameters from azd env, if not provided if [ -z "$resourceGroupName" ]; then resourceGroupName=$(azd env get-value RESOURCE_GROUP_NAME) fi -if [ -z "$resourceGroupNameFoundry" ]; then - resourceGroupNameFoundry=$(azd env get-value RESOURCE_GROUP_NAME_FOUNDRY) -fi if [ -z "$cosmosDbAccountName" ]; then cosmosDbAccountName=$(azd env get-value COSMOSDB_ACCOUNT_NAME) @@ -63,11 +59,15 @@ if [ -z "$aiSearchName" ]; then aiSearchName=$(azd env get-value AI_SEARCH_SERVICE_NAME) fi +if [ -z "$aif_resource_id" ]; then + aif_resource_id=$(azd env get-value AI_FOUNDRY_RESOURCE_ID) +fi + azSubscriptionId=$(azd env get-value AZURE_SUBSCRIPTION_ID) # Check if all required arguments are provided -if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiFoundryName" ] || [ -z "$aiSearchName" ] || [ -z "$resourceGroupNameFoundry" ]; then - echo "Usage: $0 " +if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiSearchName" ] || [ -z "$aif_resource_id" ]; then + echo "Usage: $0 " exit 1 fi @@ -149,7 +149,7 @@ echo "copy_kb_files.sh completed successfully." # Call run_create_index_scripts.sh echo "Running run_create_index_scripts.sh" -bash infra/scripts/run_create_index_scripts.sh "$keyvaultName" "" "" "$resourceGroupName" "$sqlServerName" "$aiFoundryName" "$aiSearchName" "$resourceGroupNameFoundry" +bash infra/scripts/run_create_index_scripts.sh "$keyvaultName" "" "" "$resourceGroupName" "$sqlServerName" "$aiSearchName" "$aif_resource_id" if [ $? -ne 0 ]; then echo "Error: run_create_index_scripts.sh failed." exit 1 diff --git a/infra/scripts/run_create_index_scripts.sh b/infra/scripts/run_create_index_scripts.sh index fcf775723..2f47638d5 100644 --- a/infra/scripts/run_create_index_scripts.sh +++ b/infra/scripts/run_create_index_scripts.sh @@ -6,9 +6,8 @@ baseUrl="$2" managedIdentityClientId="$3" resourceGroupName="$4" sqlServerName="$5" -aiFoundryName="$6" -aiSearchName="$7" -resourceGroupNameFoundry="$8" +aiSearchName="$6" +aif_resource_id="$7" echo "Script Started" @@ -69,8 +68,6 @@ fi ### Assign Azure AI User role to the signed in user ### -echo "Getting Azure AI Foundry resource id" -aif_resource_id=$(az cognitiveservices account show --name $aiFoundryName --resource-group $resourceGroupNameFoundry --query id --output tsv) # Check if the user has the Azure AI User role echo "Checking if user has the Azure AI User role" From ad9ba632a6eb4e840e8f7a9b52efc4dfa294aa95 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Thu, 7 Aug 2025 15:26:12 +0530 Subject: [PATCH 08/84] update --- infra/main.bicep | 2 -- 1 file changed, 2 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 14b447c98..2352e1b2a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -287,12 +287,10 @@ output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageConta output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName output RESOURCE_GROUP_NAME string = resourceGroup().name -//output RESOURCE_GROUP_NAME_FOUNDRY string = aifoundry.outputs.resourceGroupNameFoundry output AI_FOUNDRY_RESOURCE_ID string = aifoundry.outputs.aiFoundryId output SQLDB_SERVER string = sqlDBModule.outputs.sqlServerName output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId -//output AI_FOUNDRY_NAME string = aifoundry.outputs.aiFoundryName output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService output WEB_APP_NAME string = appserviceModule.outputs.webAppName From 18f3e785427f378465da688a7263a20bbd39973f Mon Sep 17 00:00:00 2001 From: Shreyas-Microsoft Date: Thu, 7 Aug 2025 17:38:59 +0530 Subject: [PATCH 09/84] fix: agent cleanup (#639) * Agent deletion handled successfully * use get azure credentials * remove unused import --------- Co-authored-by: Shreyas-Microsoft --- src/App/app.py | 24 ++++-- src/App/backend/agents/agent_factory.py | 73 ++++++++++++++++--- .../backend/plugins/chat_with_data_plugin.py | 26 ++++++- 3 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/App/app.py b/src/App/app.py index 3eaa8b7c1..515eb1342 100644 --- a/src/App/app.py +++ b/src/App/app.py @@ -82,17 +82,25 @@ async def startup(): app.wealth_advisor_agent = await AgentFactory.get_wealth_advisor_agent() logging.info("Wealth Advisor Agent initialized during application startup") app.search_agent = await AgentFactory.get_search_agent() - logging.info( - "Call Transcript Search Agent initialized during application startup" - ) + logging.info("Call Transcript Search Agent initialized during application startup") + app.sql_agent = await AgentFactory.get_sql_agent() + logging.info("SQL Agent initialized during application startup") @app.after_serving async def shutdown(): - await AgentFactory.delete_all_agent_instance() - app.wealth_advisor_agent = None - app.search_agent = None - app.sql_agent = None - logging.info("Agents cleaned up during application shutdown") + try: + logging.info("Application shutdown initiated...") + await AgentFactory.delete_all_agent_instance() + if hasattr(app, 'wealth_advisor_agent'): + app.wealth_advisor_agent = None + if hasattr(app, 'search_agent'): + app.search_agent = None + if hasattr(app, 'sql_agent'): + app.sql_agent = None + logging.info("Agents cleaned up successfully") + except Exception as e: + logging.error(f"Error during shutdown: {e}") + logging.exception("Detailed error during shutdown") # app.secret_key = secrets.token_hex(16) # app.session_interface = SecureCookieSessionInterface() diff --git a/src/App/backend/agents/agent_factory.py b/src/App/backend/agents/agent_factory.py index 6bae33808..d1f3956b8 100644 --- a/src/App/backend/agents/agent_factory.py +++ b/src/App/backend/agents/agent_factory.py @@ -7,6 +7,7 @@ """ import asyncio +import logging from typing import Optional from azure.ai.projects import AIProjectClient @@ -26,6 +27,7 @@ class AgentFactory: _lock = asyncio.Lock() _wealth_advisor_agent: Optional[AzureAIAgent] = None _search_agent: Optional[dict] = None + _sql_agent: Optional[dict] = None @classmethod async def get_wealth_advisor_agent(cls): @@ -94,18 +96,67 @@ async def delete_all_agent_instance(cls): Delete the singleton AzureAIAgent instances if it exists. """ async with cls._lock: - if cls._wealth_advisor_agent is not None: - await cls._wealth_advisor_agent.client.agents.delete_agent( - cls._wealth_advisor_agent.id - ) - cls._wealth_advisor_agent = None + logging.info("Starting agent deletion process...") + # Delete Wealth Advisor Agent + if cls._wealth_advisor_agent is not None: + try: + agent_id = cls._wealth_advisor_agent.id + logging.info(f"Deleting wealth advisor agent: {agent_id}") + if hasattr(cls._wealth_advisor_agent, 'client') and cls._wealth_advisor_agent.client: + await cls._wealth_advisor_agent.client.agents.delete_agent(agent_id) + logging.info("Wealth advisor agent deleted successfully") + else: + logging.warning("Wealth advisor agent client is None") + except Exception as e: + logging.error(f"Error deleting wealth advisor agent: {e}") + logging.exception("Detailed wealth advisor agent deletion error") + finally: + cls._wealth_advisor_agent = None + + # Delete Search Agent if cls._search_agent is not None: - cls._search_agent["client"].agents.delete_agent( - cls._search_agent["agent"].id - ) - cls._search_agent["client"].close() - cls._search_agent = None + try: + agent_id = cls._search_agent['agent'].id + logging.info(f"Deleting search agent: {agent_id}") + if cls._search_agent.get("client") and hasattr(cls._search_agent["client"], "agents"): + # AIProjectClient.agents.delete_agent is synchronous, don't await it + cls._search_agent["client"].agents.delete_agent(agent_id) + logging.info("Search agent deleted successfully") + + # Close the client if it has a close method + if hasattr(cls._search_agent["client"], "close"): + cls._search_agent["client"].close() + else: + logging.warning("Search agent client is None or invalid") + except Exception as e: + logging.error(f"Error deleting search agent: {e}") + logging.exception("Detailed search agent deletion error") + finally: + cls._search_agent = None + + # Delete SQL Agent + if cls._sql_agent is not None: + try: + agent_id = cls._sql_agent['agent'].id + logging.info(f"Deleting SQL agent: {agent_id}") + if cls._sql_agent.get("client") and hasattr(cls._sql_agent["client"], "agents"): + # AIProjectClient.agents.delete_agent is synchronous, don't await it + cls._sql_agent["client"].agents.delete_agent(agent_id) + logging.info("SQL agent deleted successfully") + + # Close the client if it has a close method + if hasattr(cls._sql_agent["client"], "close"): + cls._sql_agent["client"].close() + else: + logging.warning("SQL agent client is None or invalid") + except Exception as e: + logging.error(f"Error deleting SQL agent: {e}") + logging.exception("Detailed SQL agent deletion error") + finally: + cls._sql_agent = None + + logging.info("Agent deletion process completed") @classmethod async def get_sql_agent(cls) -> dict: @@ -114,7 +165,7 @@ async def get_sql_agent(cls) -> dict: This agent is used to generate T-SQL queries from natural language input. """ async with cls._lock: - if not hasattr(cls, "_sql_agent") or cls._sql_agent is None: + if cls._sql_agent is None: agent_instructions = config.SQL_SYSTEM_PROMPT or """ You are an expert assistant in generating T-SQL queries based on user questions. diff --git a/src/App/backend/plugins/chat_with_data_plugin.py b/src/App/backend/plugins/chat_with_data_plugin.py index 86b064aac..605ca5d42 100644 --- a/src/App/backend/plugins/chat_with_data_plugin.py +++ b/src/App/backend/plugins/chat_with_data_plugin.py @@ -42,9 +42,14 @@ async def get_SQL_Response( if not input or not input.strip(): return "Error: Query input is required" + thread = None try: + # TEMPORARY: Use AgentFactory directly to debug the issue + logging.info(f"Using AgentFactory directly for SQL agent for ClientId: {ClientId}") from backend.agents.agent_factory import AgentFactory agent_info = await AgentFactory.get_sql_agent() + + logging.info(f"SQL agent retrieved: {agent_info is not None}") agent = agent_info["agent"] project_client = agent_info["client"] @@ -73,23 +78,27 @@ async def get_SQL_Response( role=MessageRole.AGENT ) sql_query = message.text.value.strip() if message else None + logging.info(f"Generated SQL query: {sql_query}") if not sql_query: return "No SQL query was generated." # Clean up triple backticks (if any) sql_query = sql_query.replace("```sql", "").replace("```", "") + logging.info(f"Cleaned SQL query: {sql_query}") # Execute the query conn = get_connection() cursor = conn.cursor() cursor.execute(sql_query) rows = cursor.fetchall() + logging.info(f"Query returned {len(rows)} rows") if not rows: result = "No data found for that client." else: result = "\n".join(str(row) for row in rows) + logging.info(f"Result preview: {result[:200]}...") conn.close() @@ -97,6 +106,14 @@ async def get_SQL_Response( except Exception as e: logging.exception("Error in get_SQL_Response") return f"Error retrieving SQL data: {str(e)}" + finally: + if thread: + try: + logging.info(f"Attempting to delete thread {thread.id}") + await project_client.agents.threads.delete(thread.id) + logging.info(f"Thread {thread.id} deleted successfully") + except Exception as e: + logging.error(f"Error deleting thread {thread.id}: {str(e)}") @kernel_function( name="ChatWithCallTranscripts", @@ -115,12 +132,13 @@ async def get_answers_from_calltranscripts( if not question or not question.strip(): return "Error: Question input is required" + thread = None try: response_text = "" from backend.agents.agent_factory import AgentFactory - agent_info: dict = await AgentFactory.get_search_agent() + agent_info = await AgentFactory.get_search_agent() agent: Agent = agent_info["agent"] project_client: AIProjectClient = agent_info["client"] @@ -191,7 +209,11 @@ async def get_answers_from_calltranscripts( finally: if thread: - project_client.agents.threads.delete(thread.id) + try: + await project_client.agents.threads.delete(thread.id) + logging.info(f"Thread {thread.id} deleted successfully") + except Exception as e: + logging.error(f"Error deleting thread {thread.id}: {str(e)}") if not response_text.strip(): return "No data found for that client." From 0f8ccc568a03c92c5d781562adb884193e38c4b0 Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Fri, 8 Aug 2025 11:39:33 +0530 Subject: [PATCH 10/84] TS( 21657) Bicep Standard code changes --- .../database/cosmos/cosmos-role-assign.bicep | 5 + .../database/cosmos/deploy_cosmos_db.bicep | 16 +- infra/deploy_ai_foundry.bicep | 85 +++++++- infra/deploy_aifp_aisearch_connection.bicep | 11 ++ infra/deploy_app_service.bicep | 184 ++++++++++-------- infra/deploy_cosmos_db.bicep | 21 +- ...deploy_foundry_model_role_assignment.bicep | 18 +- infra/deploy_keyvault.bicep | 13 +- infra/deploy_managed_identity.bicep | 13 +- infra/deploy_sql_db.bicep | 17 +- infra/deploy_storage_account.bicep | 16 +- infra/main.bicep | 152 ++++++++++----- infra/main.parameters.json | 8 +- 13 files changed, 407 insertions(+), 152 deletions(-) diff --git a/infra/core/database/cosmos/cosmos-role-assign.bicep b/infra/core/database/cosmos/cosmos-role-assign.bicep index 3949efef0..d159d0070 100644 --- a/infra/core/database/cosmos/cosmos-role-assign.bicep +++ b/infra/core/database/cosmos/cosmos-role-assign.bicep @@ -1,7 +1,12 @@ metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' + +@description('Name of the Azure Cosmos DB account.') param accountName string +@description('ID of the Cosmos DB SQL role definition.') param roleDefinitionId string + +@description('Principal ID to assign the role to.') param principalId string = '' resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { diff --git a/infra/core/database/cosmos/deploy_cosmos_db.bicep b/infra/core/database/cosmos/deploy_cosmos_db.bicep index 3925eeaeb..15d451836 100644 --- a/infra/core/database/cosmos/deploy_cosmos_db.bicep +++ b/infra/core/database/cosmos/deploy_cosmos_db.bicep @@ -1,14 +1,21 @@ @minLength(3) @maxLength(15) -@description('Solution Name') +@description('Name of the solution.') param solutionName string + +@description('Deployment location for the solution.') param solutionLocation string -@description('Name') -param accountName string = '${ solutionName }-cosmos' +@description('Name of the Cosmos DB account.') +param accountName string = '${solutionName}-cosmos' + +@description('Name of the Cosmos DB database.') param databaseName string = 'db_conversation_history' + +@description('Name of the Cosmos DB container.') param collectionName string = 'conversations' +@description('List of Cosmos DB containers to be created.') param containers array = [ { name: collectionName @@ -17,9 +24,11 @@ param containers array = [ } ] +@description('API kind of the Cosmos DB account.') @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) param kind string = 'GlobalDocumentDB' +@description('Resource tags to apply.') param tags object = {} resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { @@ -68,6 +77,7 @@ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15 ] } +@description('Details of the Cosmos DB account, database, and container.') output cosmosOutput object = { cosmosAccountName: cosmos.name cosmosDatabaseName: databaseName diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 057c13e1c..838340b10 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -1,28 +1,54 @@ // Creates Azure dependent resources for Azure AI studio +@minLength(3) +@maxLength(15) +@description('Solution Name') param solutionName string + +@description('Solution Location') param solutionLocation string + +@description('Contains Name of KeyVault.') param keyVaultName string + +@description('Indicates the type of Deployment.') param deploymentType string + +@description('GPT Model Name') param gptModelName string + +@description('Azure OepnAI API Version.') param azureOpenaiAPIVersion string + +@description('Param to get Deployment Capacity.') param gptDeploymentCapacity int + +@description('Embedding Model.') param embeddingModel string + +@description('Info about Embedding Deployment Capacity.') param embeddingDeploymentCapacity int + +@description('Existing Log Analytics WorkspaceID.') param existingLogAnalyticsWorkspaceId string = '' + +@description('Azure Existing AI Project ResourceID.') param azureExistingAIProjectResourceId string = '' +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + // Load the abbrevations file required to name the azure resources. -var abbrs = loadJsonContent('./abbreviations.json') +//var abbrs = loadJsonContent('./abbreviations.json') -var aiFoundryName = '${abbrs.ai.aiFoundry}${solutionName}' -var applicationInsightsName = '${abbrs.managementGovernance.applicationInsights}${solutionName}' +var aiFoundryName = 'aif-${solutionName}' +var applicationInsightsName = 'appi-${solutionName}' var keyvaultName = keyVaultName var location = solutionLocation //'eastus2' -var aiProjectName = '${abbrs.ai.aiFoundryProject}${solutionName}' +var aiProjectName = 'aifp-${solutionName}' var aiProjectFriendlyName = aiProjectName var aiProjectDescription = 'AI Foundry Project' -var aiSearchName = '${abbrs.ai.aiSearch}${solutionName}' -var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}' +var aiSearchName = 'srch-${solutionName}' +var workspaceName = 'log-${solutionName}' var aiModelDeployments = [ { name: gptModelName @@ -89,7 +115,7 @@ resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = if (!useExisting) { name: workspaceName location: location - tags: {} + tags: tags properties: { retentionInDays: 30 sku: { @@ -108,6 +134,7 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { publicNetworkAccessForQuery: 'Enabled' WorkspaceResourceId: useExisting ? existingLogAnalyticsWorkspace.id : logAnalytics.id } + tags: tags } resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { @@ -131,6 +158,7 @@ resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = i publicNetworkAccess: 'Enabled' disableLocalAuth: false } + tags: tags } resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { @@ -144,6 +172,7 @@ resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04 description: aiProjectDescription displayName: aiProjectFriendlyName } + tags: tags } @batchSize(1) @@ -193,6 +222,7 @@ resource aiSearch 'Microsoft.Search/searchServices@2025-02-01-preview' = { } semanticSearch: 'free' } + tags: tags } resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/connections@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { @@ -247,8 +277,9 @@ module assignOpenAIRoleToAISearchExisting 'deploy_foundry_model_role_assignment. roleAssignmentName: guid(resourceGroup().id, aiSearch.id, cognitiveServicesOpenAIUser.id, 'openai-foundry') aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIFoundryName : aiFoundryName principalId: aiSearch.identity.principalId -aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName + aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName aiModelDeployments: aiModelDeployments + tags:tags } } @@ -327,6 +358,7 @@ resource azureOpenAIApiVersionEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-0 properties: { value: azureOpenaiAPIVersion //'2024-07-18' } + tags:tags } resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -338,6 +370,7 @@ resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01- ? existingOpenAIEndpoint : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] } + tags:tags } resource azureOpenAIEmbeddingModelEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -346,6 +379,7 @@ resource azureOpenAIEmbeddingModelEntry 'Microsoft.KeyVault/vaults/secrets@2021- properties: { value: embeddingModel } + tags:tags } resource azureSearchServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -354,6 +388,7 @@ resource azureSearchServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021 properties: { value: 'https://${aiSearch.name}.search.windows.net' } + tags:tags } resource azureSearchIndexEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -362,34 +397,64 @@ resource azureSearchIndexEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-pre properties: { value: 'transcripts_index' } + tags:tags } - +@description('Contains Name of KeyVault.') output keyvaultName string = keyvaultName + +@description('Contains KeyVault ID.') output keyvaultId string = keyVault.id +@description('Contains AI Foundry ResourceGroup Name') output resourceGroupNameFoundry string = !empty(existingAIServiceResourceGroup) ? existingAIServiceResourceGroup : resourceGroup().name -output aiFoundryProjectEndpoint string = !empty(existingProjEndpoint) + + @description('Contains Name of AI Foundry Project Endpoint.') + output aiFoundryProjectEndpoint string = !empty(existingProjEndpoint) ? existingProjEndpoint : aiFoundryProject.properties.endpoints['AI Foundry API'] + +@description('Contains AI Endpoint.') output aoaiEndpoint string = !empty(existingOpenAIEndpoint) ? existingOpenAIEndpoint : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint + +@description('Contains Name of AI Foundry.') output aiFoundryName string = !empty(existingAIFoundryName) ? existingAIFoundryName : aiFoundryName //aiServicesName_m +@description('Contains AI Search Name.') output aiSearchName string = aiSearchName + +@description('Contains AI SearchID.') output aiSearchId string = aiSearch.id + +@description('Contains AI Search Target.') output aiSearchTarget string = 'https://${aiSearch.name}.search.windows.net' + +@description('Contains AI Search Service.') output aiSearchService string = aiSearch.name + +@description('Contains Name of AI Foundry Project.') output aiFoundryProjectName string = !empty(existingAIProjectName) ? existingAIProjectName : aiFoundryProject.name +@description('Contains Application Insights ID.') output applicationInsightsId string = applicationInsights.id + +@description('Contains Log Analytics Workspace Resource Name.') output logAnalyticsWorkspaceResourceName string = useExisting ? existingLogAnalyticsWorkspace.name : logAnalytics.name + +@description('Contains Log Analytics Workspace ResourceGroup Name.') output logAnalyticsWorkspaceResourceGroup string = useExisting ? existingLawResourceGroup : resourceGroup().name +@description('Contains Application Insights Connection String.') output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString +@description('Contains AI Search Foundry Connection Name.') output aiSearchFoundryConnectionName string = aiSearchConnectionName + +@description('Contains AI Foundry App Insights Connection Name.') output aiAppInsightsFoundryConnectionName string = aiAppInsightConnectionName + +@description('Contains AI Model Deployments') output aiModelDeployments array = aiModelDeployments diff --git a/infra/deploy_aifp_aisearch_connection.bicep b/infra/deploy_aifp_aisearch_connection.bicep index 0dec1b9bb..f3719d11e 100644 --- a/infra/deploy_aifp_aisearch_connection.bicep +++ b/infra/deploy_aifp_aisearch_connection.bicep @@ -1,8 +1,19 @@ +@description('Existing AI Project Name') param existingAIProjectName string + +@description('Existing AI Foundry Name') param existingAIFoundryName string + +@description('AI Search Name') param aiSearchName string + +@description('AI Search Resource ID') param aiSearchResourceId string + +@description('AI Search Location') param aiSearchLocation string + +@description('AI Search Connection Name') param aiSearchConnectionName string resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index e0b1f97d5..930a7bdc6 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -6,93 +6,96 @@ param solutionLocation string @description('The pricing tier for the App Service plan') @allowed(['F1', 'D1', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1', 'P2', 'P3', 'P4', 'P0v3']) -param HostingPlanSku string = 'B2' +param hostingPlanSku string = 'B2' -param HostingPlanName string -param WebsiteName string +@description('Name of App Service plan') +param hostingPlanName string + +@description('Name of Web App') +param websiteName string // @description('Name of Application Insights') // param ApplicationInsightsName string = '${ solutionName }-app-insights' @description('Name of Azure Search Service') -param AzureSearchService string = '' +param azureSearchService string = '' @description('Name of Azure Search Index') -param AzureSearchIndex string = '' +param azureSearchIndex string = '' @description('Use semantic search') -param AzureSearchUseSemanticSearch string = 'False' +param azureSearchUseSemanticSearch string = 'False' @description('Semantic search config') -param AzureSearchSemanticSearchConfig string = 'default' +param azureSearchSemanticSearchConfig string = 'default' @description('Top K results') -param AzureSearchTopK string = '5' +param azureSearchTopK string = '5' @description('Enable in domain') -param AzureSearchEnableInDomain string = 'False' +param azureSearchEnableInDomain string = 'False' @description('Content columns') -param AzureSearchContentColumns string = 'content' +param azureSearchContentColumns string = 'content' @description('Filename column') -param AzureSearchFilenameColumn string = 'filename' +param azureSearchFilenameColumn string = 'filename' @description('Title column') -param AzureSearchTitleColumn string = 'client_id' +param azureSearchTitleColumn string = 'client_id' @description('Url column') -param AzureSearchUrlColumn string = 'sourceurl' +param azureSearchUrlColumn string = 'sourceurl' @description('Name of Azure OpenAI Resource') -param AzureOpenAIResource string +param azureOpenAIResource string @description('Azure OpenAI Model Deployment Name') -param AzureOpenAIModel string +param azureOpenAIModel string @description('Azure Open AI Endpoint') -param AzureOpenAIEndpoint string = '' +param azureOpenAIEndpoint string = '' @description('Azure OpenAI Temperature') -param AzureOpenAITemperature string = '0' +param azureOpenAITemperature string = '0' @description('Azure OpenAI Top P') -param AzureOpenAITopP string = '1' +param azureOpenAITopP string = '1' @description('Azure OpenAI Max Tokens') -param AzureOpenAIMaxTokens string = '1000' +param azureOpenAIMaxTokens string = '1000' @description('Azure OpenAI Stop Sequence') -param AzureOpenAIStopSequence string = '\n' +param azureOpenAIStopSequence string = '\n' @description('Azure OpenAI System Message') -param AzureOpenAISystemMessage string = 'You are an AI assistant that helps people find information.' +param azureOpenAISystemMessage string = 'You are an AI assistant that helps people find information.' @description('Azure OpenAI Api Version') -param AzureOpenAIApiVersion string = '2024-02-15-preview' +param azureOpenAIApiVersion string = '2024-02-15-preview' @description('Whether or not to stream responses from Azure OpenAI') -param AzureOpenAIStream string = 'True' +param azureOpenAIStream string = 'True' @description('Azure Search Query Type') @allowed(['simple', 'semantic', 'vector', 'vectorSimpleHybrid', 'vectorSemanticHybrid']) -param AzureSearchQueryType string = 'simple' +param azureSearchQueryType string = 'simple' @description('Azure Search Vector Fields') -param AzureSearchVectorFields string = 'contentVector' +param azureSearchVectorFields string = 'contentVector' @description('Azure Search Permitted Groups Field') -param AzureSearchPermittedGroupsField string = '' +param azureSearchPermittedGroupsField string = '' @description('Azure Search Strictness') @allowed(['1', '2', '3', '4', '5']) -param AzureSearchStrictness string = '3' +param azureSearchStrictness string = '3' @description('Azure OpenAI Embedding Deployment Name') -param AzureOpenAIEmbeddingName string = '' +param azureOpenAIEmbeddingName string = '' @description('Azure Open AI Embedding Endpoint') -param AzureOpenAIEmbeddingEndpoint string = '' +param azureOpenAIEmbeddingEndpoint string = '' @description('Use Azure Function') param USE_INTERNAL_STREAM string = 'True' @@ -118,34 +121,55 @@ param AZURE_COSMOSDB_ENABLE_FEEDBACK string = 'True' //@description('Power BI Embed URL') //param VITE_POWERBI_EMBED_URL string = '' +@description('The container image tag to be deployed') param imageTag string +@description('The resource ID of the user-assigned managed identity to be used by the deployed resources.') param userassignedIdentityId string + +@description('The client ID of the user-assigned managed identity.') param userassignedIdentityClientId string + +@description('The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring.') param applicationInsightsId string +@description('The endpoint URL of the Azure Cognitive Search service.') param azureSearchServiceEndpoint string @description('Azure Function App SQL System Prompt') param sqlSystemPrompt string + @description('Azure Function App CallTranscript System Prompt') param callTranscriptSystemPrompt string + @description('Azure Function App Stream Text System Prompt') param streamTextSystemPrompt string +@description('AI Foundry project endpoint URL.') param aiFoundryProjectEndpoint string + +@description('Flag to enable AI project client.') param useAIProjectClientFlag string = 'false' +@description('Name of the AI Foundry project.') param aiFoundryName string + +@description('Application Insights connection string.') param applicationInsightsConnectionString string + +@description('Connection name for Azure Cognitive Search.') param aiSearchProjectConnectionName string -// var WebAppImageName = 'DOCKER|byoaiacontainer.azurecr.io/byoaia-app:latest' +@description('Optional. Tags to be applied to the resources.') +param tags object = {} -// var WebAppImageName = 'DOCKER|ncwaappcontainerreg1.azurecr.io/ncqaappimage:v1.0.0' +// var webAppImageName = 'DOCKER|byoaiacontainer.azurecr.io/byoaia-app:latest' -var WebAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:${imageTag}' +// var webAppImageName = 'DOCKER|ncwaappcontainerreg1.azurecr.io/ncqaappimage:v1.0.0' +var webAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:${imageTag}' + +@description('Resource ID of the existing AI Foundry project.') param azureExistingAIProjectResourceId string = '' var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) @@ -159,21 +183,22 @@ var existingAIServicesName = !empty(azureExistingAIProjectResourceId) : '' var existingAIProjectName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : '' -resource HostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { - name: HostingPlanName +resource hostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { + name: hostingPlanName location: solutionLocation sku: { - name: HostingPlanSku + name: hostingPlanSku } properties: { - name: HostingPlanName + name: hostingPlanName reserved: true } kind: 'linux' + tags: tags } -resource Website 'Microsoft.Web/sites@2020-06-01' = { - name: WebsiteName +resource website 'Microsoft.Web/sites@2020-06-01' = { + name: websiteName location: solutionLocation identity: { type: 'SystemAssigned, UserAssigned' @@ -182,7 +207,7 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { } } properties: { - serverFarmId: HostingPlanName + serverFarmId: hostingPlanName siteConfig: { appSettings: [ { @@ -199,107 +224,107 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { } { name: 'AZURE_SEARCH_SERVICE' - value: AzureSearchService + value: azureSearchService } { name: 'AZURE_SEARCH_INDEX' - value: AzureSearchIndex + value: azureSearchIndex } { name: 'AZURE_SEARCH_USE_SEMANTIC_SEARCH' - value: AzureSearchUseSemanticSearch + value: azureSearchUseSemanticSearch } { name: 'AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG' - value: AzureSearchSemanticSearchConfig + value: azureSearchSemanticSearchConfig } { name: 'AZURE_SEARCH_TOP_K' - value: AzureSearchTopK + value: azureSearchTopK } { name: 'AZURE_SEARCH_ENABLE_IN_DOMAIN' - value: AzureSearchEnableInDomain + value: azureSearchEnableInDomain } { name: 'AZURE_SEARCH_CONTENT_COLUMNS' - value: AzureSearchContentColumns + value: azureSearchContentColumns } { name: 'AZURE_SEARCH_FILENAME_COLUMN' - value: AzureSearchFilenameColumn + value: azureSearchFilenameColumn } { name: 'AZURE_SEARCH_TITLE_COLUMN' - value: AzureSearchTitleColumn + value: azureSearchTitleColumn } { name: 'AZURE_SEARCH_URL_COLUMN' - value: AzureSearchUrlColumn + value: azureSearchUrlColumn } { name: 'AZURE_OPENAI_RESOURCE' - value: AzureOpenAIResource + value: azureOpenAIResource } { name: 'AZURE_OPENAI_MODEL' - value: AzureOpenAIModel + value: azureOpenAIModel } { name: 'AZURE_OPENAI_ENDPOINT' - value: AzureOpenAIEndpoint + value: azureOpenAIEndpoint } { name: 'AZURE_OPENAI_TEMPERATURE' - value: AzureOpenAITemperature + value: azureOpenAITemperature } { name: 'AZURE_OPENAI_TOP_P' - value: AzureOpenAITopP + value: azureOpenAITopP } { name: 'AZURE_OPENAI_MAX_TOKENS' - value: AzureOpenAIMaxTokens + value: azureOpenAIMaxTokens } { name: 'AZURE_OPENAI_STOP_SEQUENCE' - value: AzureOpenAIStopSequence + value: azureOpenAIStopSequence } { name: 'AZURE_OPENAI_SYSTEM_MESSAGE' - value: AzureOpenAISystemMessage + value: azureOpenAISystemMessage } { name: 'AZURE_OPENAI_PREVIEW_API_VERSION' - value: AzureOpenAIApiVersion + value: azureOpenAIApiVersion } { name: 'AZURE_OPENAI_STREAM' - value: AzureOpenAIStream + value: azureOpenAIStream } { name: 'AZURE_SEARCH_QUERY_TYPE' - value: AzureSearchQueryType + value: azureSearchQueryType } { name: 'AZURE_SEARCH_VECTOR_COLUMNS' - value: AzureSearchVectorFields + value: azureSearchVectorFields } { name: 'AZURE_SEARCH_PERMITTED_GROUPS_COLUMN' - value: AzureSearchPermittedGroupsField + value: azureSearchPermittedGroupsField } { name: 'AZURE_SEARCH_STRICTNESS' - value: AzureSearchStrictness + value: azureSearchStrictness } { name: 'AZURE_OPENAI_EMBEDDING_NAME' - value: AzureOpenAIEmbeddingName + value: azureOpenAIEmbeddingName } { name: 'AZURE_OPENAI_EMBEDDING_ENDPOINT' - value: AzureOpenAIEmbeddingEndpoint + value: azureOpenAIEmbeddingEndpoint } { name: 'SQLDB_SERVER' @@ -362,21 +387,22 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { } { name: 'AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME' - value: AzureOpenAIModel + value: azureOpenAIModel } { name: 'AZURE_AI_AGENT_API_VERSION' - value: AzureOpenAIApiVersion + value: azureOpenAIApiVersion } { name: 'AZURE_SEARCH_CONNECTION_NAME' value: aiSearchProjectConnectionName } ] - linuxFxVersion: WebAppImageName + linuxFxVersion: webAppImageName } } - dependsOn: [HostingPlan] + tags: tags + dependsOn: [hostingPlan] } // resource ApplicationInsights 'Microsoft.Insights/components@2020-02-02' = { @@ -396,14 +422,14 @@ resource contributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRol } module cosmosUserRole 'core/database/cosmos/cosmos-role-assign.bicep' = { - name: 'cosmos-sql-user-role-${WebsiteName}' + name: 'cosmos-sql-user-role-${websiteName}' params: { accountName: AZURE_COSMOSDB_ACCOUNT roleDefinitionId: contributorRoleDefinition.id - principalId: Website.identity.principalId + principalId: website.identity.principalId } dependsOn: [ - Website + website ] } @@ -422,7 +448,7 @@ resource assignAiUserRoleToAiProject 'Microsoft.Authorization/roleAssignments@20 name: guid(resourceGroup().id, aiFoundry.id, aiUserRoleDefinitionFoundry.id) // scope: aiProject properties: { - principalId: Website.identity.principalId + principalId: website.identity.principalId roleDefinitionId: aiUserRoleDefinitionFoundry.id principalType: 'ServicePrincipal' } @@ -432,13 +458,17 @@ module assignAiUserRoleToAiProjectExisting 'deploy_foundry_model_role_assignment name: 'assignAiUserRoleToAiProjectExisting' scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) params: { - principalId: Website.identity.principalId + principalId: website.identity.principalId roleDefinitionId: aiUserRoleDefinitionFoundry.id - roleAssignmentName: guid(Website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) + roleAssignmentName: guid(website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName aiProjectName: existingAIProjectName + tags: tags } } -output webAppUrl string = 'https://${WebsiteName}.azurewebsites.net' -output webAppName string = WebsiteName +@description('URL of the deployed web application.') +output webAppUrl string = 'https://${websiteName}.azurewebsites.net' + +@description('Name of the deployed web application.') +output webAppName string = websiteName diff --git a/infra/deploy_cosmos_db.bicep b/infra/deploy_cosmos_db.bicep index 4a3f29198..2a8aca40e 100644 --- a/infra/deploy_cosmos_db.bicep +++ b/infra/deploy_cosmos_db.bicep @@ -1,10 +1,19 @@ + +@minLength(3) +@maxLength(20) +@description('Solution location.') param solutionLocation string -@description('Name') +@description('Name of the Azure Cosmos DB account.') param cosmosDBName string + +@description('Name of the Cosmos DB database.') param databaseName string = 'db_conversation_history' + +@description('Name of the Cosmos DB container (collection).') param collectionName string = 'conversations' +@description('List of Cosmos DB containers to be created.') param containers array = [ { name: collectionName @@ -13,9 +22,11 @@ param containers array = [ } ] +@description('The API kind of the Cosmos DB account.') @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) param kind string = 'GlobalDocumentDB' +@description('Optional. Tags to be applied to the resources.') param tags object = {} resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { @@ -47,7 +58,7 @@ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15 properties: { resource: { id: databaseName } } - + tags: tags resource list 'containers' = [for container in containers: { name: container.name properties: { @@ -63,7 +74,11 @@ resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15 cosmos ] } - +@description('Name of the Cosmos DB account.') output cosmosAccountName string = cosmos.name + +@description('Name of the Cosmos DB database.') output cosmosDatabaseName string = databaseName + +@description('Name of the Cosmos DB container.') output cosmosContainerName string = collectionName diff --git a/infra/deploy_foundry_model_role_assignment.bicep b/infra/deploy_foundry_model_role_assignment.bicep index 26c30afdb..078903784 100644 --- a/infra/deploy_foundry_model_role_assignment.bicep +++ b/infra/deploy_foundry_model_role_assignment.bicep @@ -1,10 +1,23 @@ +@description('Principal ID to assign the role to.') param principalId string = '' + +@description('ID of the role definition to assign.') param roleDefinitionId string + +@description('Name of the role assignment.') param roleAssignmentName string = '' + +@description('Name of the AI Foundry resource.') param aiFoundryName string + +@description('Name of the AI project.') param aiProjectName string = '' + +@description('List of AI model deployments.') param aiModelDeployments array = [] +@description('Optional. Tags to be applied to the resources.') +param tags object = {} resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { name: aiFoundryName @@ -26,6 +39,7 @@ resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments name: aiModeldeployment.sku.name capacity: aiModeldeployment.sku.capacity } + tags : tags }] resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { @@ -42,6 +56,8 @@ resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-0 principalType: 'ServicePrincipal' } } - +@description('Principal ID of the AI Services resource.') output aiServicesPrincipalId string = aiServices.identity.principalId + +@description('Principal ID of the AI Project resource if defined.') output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' diff --git a/infra/deploy_keyvault.bicep b/infra/deploy_keyvault.bicep index 0878f84bc..005b5c123 100644 --- a/infra/deploy_keyvault.bicep +++ b/infra/deploy_keyvault.bicep @@ -9,12 +9,13 @@ param solutionName string @description('Solution Location') param solutionLocation string +@description('Current UTC timestamp.') param utc string = utcNow() -@description('Name') +@description('Name of the Azure Key Vault.') param kvName string -@description('Create Mode') +@description('Specifies the create mode for the resource.') param createMode string = 'default' @description('Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') @@ -49,12 +50,17 @@ param sku string = 'standard' @description('Vault URI. The URI of the vault for performing operations on keys and secrets.') var vaultUri = 'https://${ kvName }.vault.azure.net/' +@description('Object ID of the managed identity.') param managedIdentityObjectId string +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { name: kvName location: solutionLocation tags: { + ...tags app: solutionName location: solutionLocation } @@ -111,6 +117,9 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { } } +@description('Name of the Key Vault.') output keyvaultName string = keyVault.name + +@description('Resource ID of the Key Vault.') output keyvaultId string = keyVault.id diff --git a/infra/deploy_managed_identity.bicep b/infra/deploy_managed_identity.bicep index 7fecc336c..cb59ee570 100644 --- a/infra/deploy_managed_identity.bicep +++ b/infra/deploy_managed_identity.bicep @@ -3,19 +3,23 @@ targetScope = 'resourceGroup' @minLength(3) @maxLength(15) -@description('Solution Name') +@description('Name of the solution.') param solutionName string -@description('Solution Location') +@description('Deployment location for the solution.') param solutionLocation string -@description('Name') +@description('Name of the managed identity.') param miName string +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: miName location: solutionLocation tags: { + ...tags app: solutionName location: solutionLocation } @@ -40,6 +44,7 @@ resource managedIdentityWebApp 'Microsoft.ManagedIdentity/userAssignedIdentities name: '${miName}-webapp' location: solutionLocation tags: { + ...tags app: solutionName location: solutionLocation } @@ -90,6 +95,7 @@ resource managedIdentityWebApp 'Microsoft.ManagedIdentity/userAssignedIdentities // } // } +@description('Details of the managed identity resource.') output managedIdentityOutput object = { id: managedIdentity.id objectId: managedIdentity.properties.principalId @@ -97,6 +103,7 @@ output managedIdentityOutput object = { name: miName } +@description('Details of the managed identity for the web app.') output managedIdentityWebAppOutput object = { id: managedIdentityWebApp.id objectId: managedIdentityWebApp.properties.principalId diff --git a/infra/deploy_sql_db.bicep b/infra/deploy_sql_db.bicep index 669ddb31c..dd4076a66 100644 --- a/infra/deploy_sql_db.bicep +++ b/infra/deploy_sql_db.bicep @@ -1,6 +1,13 @@ +@description('Deployment location for the solution.') param solutionLocation string + +@description('Name of the Azure Key Vault.') param keyVaultName string + +@description('Object ID of the managed identity.') param managedIdentityObjectId string + +@description('Name of the managed identity.') param managedIdentityName string @description('The name of the SQL logical server.') @@ -12,6 +19,8 @@ param sqlDBName string @description('Location for all resources.') param location string = solutionLocation +@description('Optional. Tags to be applied to the resources.') +param tags object = {} resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { name: serverName @@ -30,6 +39,7 @@ resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { azureADOnlyAuthentication: true } } + tags: tags } resource firewallRule 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { @@ -68,6 +78,7 @@ resource sqlDB 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { readScale: 'Disabled' zoneRedundant: false } + tags: tags } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { @@ -80,6 +91,7 @@ resource sqldbServerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' properties: { value: '${serverName}.database.windows.net' } + tags: tags } resource sqldbDatabaseEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -88,8 +100,11 @@ resource sqldbDatabaseEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-previe properties: { value: sqlDBName } + tags: tags } - +@description('Name of the SQL logical server.') output sqlServerName string = serverName + +@description('Name of the SQL database.') output sqlDbName string = sqlDBName // output sqlDbUser string = administratorLogin diff --git a/infra/deploy_storage_account.bicep b/infra/deploy_storage_account.bicep index f9f8f9f1a..994f7979a 100644 --- a/infra/deploy_storage_account.bicep +++ b/infra/deploy_storage_account.bicep @@ -1,15 +1,21 @@ // ========== Storage Account ========== // targetScope = 'resourceGroup' -@description('Solution Location') +@description('Deployment location for the solution.') param solutionLocation string -@description('Name') +@description('Name of the storage account.') param saName string +@description('Object ID of the managed identity.') param managedIdentityObjectId string + +@description('Name of the Azure Key Vault.') param keyVaultName string +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + resource storageAccounts_resource 'Microsoft.Storage/storageAccounts@2022-09-01' = { name: saName location: solutionLocation @@ -45,6 +51,7 @@ resource storageAccounts_resource 'Microsoft.Storage/storageAccounts@2022-09-01' accessTier: 'Hot' allowSharedKeyAccess: false } + tags : tags } resource storageAccounts_default 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01' = { @@ -103,6 +110,7 @@ resource adlsAccountNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-prev properties: { value: saName } + tags : tags } resource adlsAccountContainerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { @@ -111,7 +119,11 @@ resource adlsAccountContainerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01 properties: { value: 'data' } + tags : tags } +@description('Name of the storage account.') output storageName string = saName + +@description('Name of the default storage container.') output storageContainer string = 'data' diff --git a/infra/main.bicep b/infra/main.bicep index 705259f56..bd2fa9639 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -3,20 +3,20 @@ targetScope = 'resourceGroup' @minLength(3) @maxLength(20) -@description('A unique prefix for all resources in this deployment. This should be 3-20 characters long:') -param environmentName string +@description('Required. A unique prefix for all resources in this deployment. This should be 3-20 characters long:') +param solutionName string = 'clientadvisor' -@description('Optional: Existing Log Analytics Workspace Resource ID') +@description('Optional. Existing Log Analytics Workspace Resource ID') param existingLogAnalyticsWorkspaceId string = '' -@description('Use this parameter to use an existing AI project resource ID') +@description('Optional. Use this parameter to use an existing AI project resource ID') param azureExistingAIProjectResourceId string = '' -@description('CosmosDB Location') +@description('Optional. CosmosDB Location') param cosmosLocation string = 'eastus2' @minLength(1) -@description('GPT model deployment type:') +@description('Optional. GPT model deployment type:') @allowed([ 'Standard' 'GlobalStandard' @@ -24,16 +24,17 @@ param cosmosLocation string = 'eastus2' param deploymentType string = 'GlobalStandard' @minLength(1) -@description('Name of the GPT model to deploy:') +@description('Optional. Name of the GPT model to deploy:') @allowed([ 'gpt-4o-mini' ]) param gptModelName string = 'gpt-4o-mini' +@description('Optional. API version for the Azure OpenAI service.') param azureOpenaiAPIVersion string = '2025-04-01-preview' @minValue(10) -@description('Capacity of the GPT deployment:') +@description('Optional. Capacity of the GPT deployment:') // You can increase this, but capacity is limited per model/region, so you will get errors if you go over // https://learn.microsoft.com/en-us/azure/ai-services/openai/quotas-limits param gptDeploymentCapacity int = 200 @@ -46,7 +47,7 @@ param gptDeploymentCapacity int = 200 param embeddingModel string = 'text-embedding-ada-002' @minValue(10) -@description('Capacity of the Embedding Model deployment') +@description('Optional. Capacity of the Embedding Model deployment') param embeddingDeploymentCapacity int = 80 // @description('Fabric Workspace Id if you have one, else leave it empty. ') @@ -70,15 +71,29 @@ param imageTag string = 'latest' @description('Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') param aiDeploymentsLocation string -@description('Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') +@description('Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') param AZURE_LOCATION string = '' var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION -var uniqueId = toLower(uniqueString(environmentName, subscription().id, solutionLocation, resourceGroup().name)) -var solutionPrefix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' +//var uniqueId = toLower(uniqueString(solutionName , subscription().id, solutionLocation, resourceGroup().name)) +//var solutionSuffix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' + +@maxLength(5) +@description('Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.') +param solutionUniqueToken string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) + +var solutionSuffix= toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) // Load the abbrevations file required to name the azure resources. -var abbrs = loadJsonContent('./abbreviations.json') +//var abbrs = loadJsonContent('./abbreviations.json') //var resourceGroupLocation = resourceGroup().location //var solutionLocation = resourceGroupLocation @@ -120,11 +135,15 @@ var functionAppStreamTextSystemPrompt = '''The currently selected client's name If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response. Always send clientId as '{client_id}'.''' +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' properties: { tags: { + ...tags TemplateName: 'Client Advisor' } } @@ -134,9 +153,10 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { module managedIdentityModule 'deploy_managed_identity.bicep' = { name: 'deploy_managed_identity' params: { - solutionName: solutionPrefix + solutionName: solutionSuffix solutionLocation: solutionLocation - miName: '${abbrs.security.managedIdentity}${solutionPrefix}' + miName: 'id-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -145,10 +165,11 @@ module managedIdentityModule 'deploy_managed_identity.bicep' = { module keyvaultModule 'deploy_keyvault.bicep' = { name: 'deploy_keyvault' params: { - solutionName: solutionPrefix + solutionName: solutionSuffix solutionLocation: solutionLocation managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - kvName: '${abbrs.security.keyVault}${solutionPrefix}' + kvName: 'kv-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -157,7 +178,7 @@ module keyvaultModule 'deploy_keyvault.bicep' = { module aifoundry 'deploy_ai_foundry.bicep' = { name: 'deploy_ai_foundry' params: { - solutionName: solutionPrefix + solutionName: solutionSuffix solutionLocation: aiDeploymentsLocation keyVaultName: keyvaultModule.outputs.keyvaultName deploymentType: deploymentType @@ -168,6 +189,7 @@ module aifoundry 'deploy_ai_foundry.bicep' = { embeddingDeploymentCapacity: embeddingDeploymentCapacity existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId azureExistingAIProjectResourceId: azureExistingAIProjectResourceId + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -177,7 +199,8 @@ module cosmosDBModule 'deploy_cosmos_db.bicep' = { name: 'deploy_cosmos_db' params: { solutionLocation: cosmosLocation - cosmosDBName: '${abbrs.databases.cosmosDBDatabase}${solutionPrefix}' + cosmosDBName: 'cosmos-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -188,8 +211,9 @@ module storageAccountModule 'deploy_storage_account.bicep' = { params: { solutionLocation: solutionLocation managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - saName: '${abbrs.storage.storageAccount}${solutionPrefix}' + saName: 'st${solutionSuffix}' keyVaultName: keyvaultModule.outputs.keyvaultName + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -202,8 +226,9 @@ module sqlDBModule 'deploy_sql_db.bicep' = { keyVaultName: keyvaultModule.outputs.keyvaultName managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId managedIdentityName: managedIdentityModule.outputs.managedIdentityOutput.name - serverName: '${abbrs.databases.sqlDatabaseServer}${solutionPrefix}' - sqlDBName: '${abbrs.databases.sqlDatabase}${solutionPrefix}' + serverName: 'sql-${solutionSuffix}' + sqlDBName: 'sqldb-${solutionSuffix}' + tags: tags } scope: resourceGroup(resourceGroup().name) } @@ -219,33 +244,33 @@ module appserviceModule 'deploy_app_service.bicep' = { name: 'deploy_app_service' params: { solutionLocation: solutionLocation - HostingPlanName: '${abbrs.compute.appServicePlan}${solutionPrefix}' - WebsiteName: '${abbrs.compute.webApp}${solutionPrefix}' - AzureSearchService: aifoundry.outputs.aiSearchService - AzureSearchIndex: 'transcripts_index' - AzureSearchUseSemanticSearch: 'True' - AzureSearchSemanticSearchConfig: 'my-semantic-config' - AzureSearchTopK: '5' - AzureSearchContentColumns: 'content' - AzureSearchFilenameColumn: 'chunk_id' - AzureSearchTitleColumn: 'client_id' - AzureSearchUrlColumn: 'sourceurl' - AzureOpenAIResource: aifoundry.outputs.aiFoundryName - AzureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint - AzureOpenAIModel: gptModelName - AzureOpenAITemperature: '0' - AzureOpenAITopP: '1' - AzureOpenAIMaxTokens: '1000' - AzureOpenAIStopSequence: '' - AzureOpenAISystemMessage: '''You are a helpful Wealth Advisor assistant''' - AzureOpenAIApiVersion: azureOpenaiAPIVersion - AzureOpenAIStream: 'True' - AzureSearchQueryType: 'simple' - AzureSearchVectorFields: 'contentVector' - AzureSearchPermittedGroupsField: '' - AzureSearchStrictness: '3' - AzureOpenAIEmbeddingName: embeddingModel - AzureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint + hostingPlanName: 'asp-${solutionSuffix}' + websiteName: 'app-${solutionSuffix}' + azureSearchService: aifoundry.outputs.aiSearchService + azureSearchIndex: 'transcripts_index' + azureSearchUseSemanticSearch: 'True' + azureSearchSemanticSearchConfig: 'my-semantic-config' + azureSearchTopK: '5' + azureSearchContentColumns: 'content' + azureSearchFilenameColumn: 'chunk_id' + azureSearchTitleColumn: 'client_id' + azureSearchUrlColumn: 'sourceurl' + azureOpenAIResource: aifoundry.outputs.aiFoundryName + azureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint + azureOpenAIModel: gptModelName + azureOpenAITemperature: '0' + azureOpenAITopP: '1' + azureOpenAIMaxTokens: '1000' + azureOpenAIStopSequence: '' + azureOpenAISystemMessage: '''You are a helpful Wealth Advisor assistant''' + azureOpenAIApiVersion: azureOpenaiAPIVersion + azureOpenAIStream: 'True' + azureSearchQueryType: 'simple' + azureSearchVectorFields: 'contentVector' + azureSearchPermittedGroupsField: '' + azureSearchStrictness: '3' + azureOpenAIEmbeddingName: embeddingModel + azureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint USE_INTERNAL_STREAM: 'True' SQLDB_SERVER: '${sqlDBModule.outputs.sqlServerName}.database.windows.net' SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName @@ -268,21 +293,50 @@ module appserviceModule 'deploy_app_service.bicep' = { applicationInsightsConnectionString: aifoundry.outputs.applicationInsightsConnectionString azureExistingAIProjectResourceId: azureExistingAIProjectResourceId aiSearchProjectConnectionName: aifoundry.outputs.aiSearchFoundryConnectionName + tags: tags } scope: resourceGroup(resourceGroup().name) } +@description('URL of the deployed web application.') output WEB_APP_URL string = appserviceModule.outputs.webAppUrl + +@description('Name of the storage account.') output STORAGE_ACCOUNT_NAME string = storageAccountModule.outputs.storageName + +@description('Name of the storage container.') output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageContainer + +@description('Name of the Key Vault.') output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName + +@description('Name of the Cosmos DB account.') output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName + +@description('Name of the resource group.') output RESOURCE_GROUP_NAME string = resourceGroup().name + +@description('Name of the resource group used by AI Foundry.') output RESOURCE_GROUP_NAME_FOUNDRY string = aifoundry.outputs.resourceGroupNameFoundry + +@description('Name of the SQL Database server.') output SQLDB_SERVER string = sqlDBModule.outputs.sqlServerName + +@description('Name of the SQL Database.') output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName + +@description('Name of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name + +@description('Client ID of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId + +@description('Name of the AI Foundry resource.') output AI_FOUNDRY_NAME string = aifoundry.outputs.aiFoundryName + +@description('Name of the AI Search service.') output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService + +@description('Name of the deployed web application.') output WEB_APP_NAME string = appserviceModule.outputs.webAppName + diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 10822f1f7..9accd9fa3 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -2,7 +2,7 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { - "environmentName": { + "solutionName": { "value": "${AZURE_ENV_NAME}" }, "cosmosLocation": { @@ -37,6 +37,12 @@ }, "azureExistingAIProjectResourceId": { "value": "${AZURE_EXISTING_AI_PROJECT_RESOURCE_ID}" + }, + "tags": { + "value": { + "solutionName": "${AZURE_ENV_NAME}", + "location": "${AZURE_LOCATION}" + } } } } \ No newline at end of file From e8a113f4fc821dcaf315172425f8eee8b042c714 Mon Sep 17 00:00:00 2001 From: Prasanjeet-Microsoft Date: Fri, 8 Aug 2025 15:55:08 +0530 Subject: [PATCH 11/84] Added required env variables for local debugging in .env generated by azd up --- .github/workflows/CAdeploy.yml | 8 +- docs/LocalSetupAndDeploy.md | 5 +- infra/deploy_ai_foundry.bicep | 1 + infra/deploy_app_service.bicep | 3 +- infra/main.bicep | 123 +++++++++--- infra/main.json | 274 ++++++++++++++++++++++++--- infra/scripts/process_sample_data.sh | 2 +- 7 files changed, 357 insertions(+), 59 deletions(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 73806dbbc..786865dff 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -172,8 +172,8 @@ jobs: echo "STORAGE_CONTAINER=$STORAGE_CONTAINER" >> $GITHUB_ENV export KEYVAULT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.keY_VAULT_NAME.value') echo "KEYVAULT_NAME=$KEYVAULT_NAME" >> $GITHUB_ENV - export SQL_SERVER=$(echo "$DEPLOY_OUTPUT" | jq -r '.sqldB_SERVER.value') - echo "SQL_SERVER=$SQL_SERVER" >> $GITHUB_ENV + export SQL_SERVER_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.sqldB_SERVER_NAME.value') + echo "SQL_SERVER_NAME=$SQL_SERVER_NAME" >> $GITHUB_ENV export SQL_DATABASE=$(echo "$DEPLOY_OUTPUT" | jq -r '.sqldB_DATABASE.value') echo "SQL_DATABASE=$SQL_DATABASE" >> $GITHUB_ENV export CLIENT_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.managedidentitY_WEBAPP_CLIENTID.value') @@ -220,7 +220,7 @@ jobs: "" \ "${{ secrets.AZURE_CLIENT_ID }}" \ "${{ env.RG_NAME }}" \ - "${{ env.SQL_SERVER }}" \ + "${{ env.SQL_SERVER_NAME }}" \ "${{ env.AI_FOUNDARY_NAME }}" \ "${{ env.SEARCH_SERVICE_NAME }}" \ "${{ env.RESOURCE_GROUP_NAME_FOUNDRY }}" @@ -232,7 +232,7 @@ jobs: ]' bash ./infra/scripts/add_user_scripts/create_sql_user_and_role.sh \ - "${{ env.SQL_SERVER }}.database.windows.net" \ + "${{ env.SQL_SERVER_NAME }}.database.windows.net" \ "${{ env.SQL_DATABASE }}" \ "$user_roles_json" \ "${{ secrets.AZURE_CLIENT_ID }}" diff --git a/docs/LocalSetupAndDeploy.md b/docs/LocalSetupAndDeploy.md index 6b7547e3e..7c2e92b87 100644 --- a/docs/LocalSetupAndDeploy.md +++ b/docs/LocalSetupAndDeploy.md @@ -13,7 +13,10 @@ Navigate to the `App` folder located in the `src` directory of the repository us ### 2. Configure Environment Variables - Copy the `.env.sample` file to a new file named `.env`. -- Update the `.env` file with the required values from your Azure resource group. +- Update the `.env` file with the required values from your Azure resource group in Azure Portal App Service environment variables. +- Alternatively, if resources were +provisioned using `azd provision` or `azd up`, a `.env` file is automatically generated in the `.azure//.env` +file. To get your `` run `azd env list` to see which env is default. ### 3. Start the Application - Run `start.cmd` (Windows) or `start.sh` (Linux/Mac) to: diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 057c13e1c..a8baef153 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -385,6 +385,7 @@ output aiSearchService string = aiSearch.name output aiFoundryProjectName string = !empty(existingAIProjectName) ? existingAIProjectName : aiFoundryProject.name output applicationInsightsId string = applicationInsights.id +output instrumentationKey string = applicationInsights.properties.InstrumentationKey output logAnalyticsWorkspaceResourceName string = useExisting ? existingLogAnalyticsWorkspace.name : logAnalytics.name output logAnalyticsWorkspaceResourceGroup string = useExisting ? existingLawResourceGroup : resourceGroup().name diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index e0b1f97d5..17116fde7 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -10,6 +10,7 @@ param HostingPlanSku string = 'B2' param HostingPlanName string param WebsiteName string +param AppEnvironment string // @description('Name of Application Insights') // param ApplicationInsightsName string = '${ solutionName }-app-insights' @@ -187,7 +188,7 @@ resource Website 'Microsoft.Web/sites@2020-06-01' = { appSettings: [ { name: 'APP_ENV' - value: 'Prod' + value: AppEnvironment } { name: 'APPINSIGHTS_INSTRUMENTATIONKEY' diff --git a/infra/main.bicep b/infra/main.bicep index 705259f56..e6ba33798 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -51,6 +51,7 @@ param embeddingDeploymentCapacity int = 80 // @description('Fabric Workspace Id if you have one, else leave it empty. ') // param fabricWorkspaceId string +@description('The Docker image tag to use for the application deployment.') param imageTag string = 'latest' //restricting to these regions because assistants api for gpt-4o-mini is available only in these regions @@ -84,6 +85,34 @@ var abbrs = loadJsonContent('./abbreviations.json') //var solutionLocation = resourceGroupLocation // var baseUrl = 'https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/' +var hostingPlanName = '${abbrs.compute.appServicePlan}${solutionPrefix}' +var websiteName = '${abbrs.compute.webApp}${solutionPrefix}' +var appEnvironment = 'Prod' +var azureSearchIndex = 'transcripts_index' +var azureSearchUseSemanticSearch = 'True' +var azureSearchSemanticSearchConfig = 'my-semantic-config' +var azureSearchTopK = '5' +var azureSearchContentColumns = 'content' +var azureSearchFilenameColumn = 'chunk_id' +var azureSearchTitleColumn = 'client_id' +var azureSearchUrlColumn = 'sourceurl' +var azureOpenAITemperature = '0' +var azureOpenAITopP = '1' +var azureOpenAIMaxTokens = '1000' +var azureOpenAIStopSequence = '\n' +var azureOpenAISystemMessage = '''You are a helpful Wealth Advisor assistant''' +var azureOpenAIStream = 'True' +var azureSearchQueryType = 'simple' +var azureSearchVectorFields = 'contentVector' +var azureSearchPermittedGroupsField = '' +var azureSearchStrictness = '3' +var azureSearchEnableInDomain = 'False' // Set to 'True' if you want to enable in-domain search +var azureCosmosDbEnableFeedback = 'True' +var useInternalStream = 'True' +var useAIProjectClientFlag = 'False' +var sqlServerFqdn = '${sqlDBModule.outputs.sqlServerName}.database.windows.net' + + var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for tables and columns provided below: 1. Table: Clients Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents @@ -219,40 +248,41 @@ module appserviceModule 'deploy_app_service.bicep' = { name: 'deploy_app_service' params: { solutionLocation: solutionLocation - HostingPlanName: '${abbrs.compute.appServicePlan}${solutionPrefix}' - WebsiteName: '${abbrs.compute.webApp}${solutionPrefix}' + HostingPlanName: hostingPlanName + WebsiteName: websiteName + AppEnvironment: appEnvironment AzureSearchService: aifoundry.outputs.aiSearchService - AzureSearchIndex: 'transcripts_index' - AzureSearchUseSemanticSearch: 'True' - AzureSearchSemanticSearchConfig: 'my-semantic-config' - AzureSearchTopK: '5' - AzureSearchContentColumns: 'content' - AzureSearchFilenameColumn: 'chunk_id' - AzureSearchTitleColumn: 'client_id' - AzureSearchUrlColumn: 'sourceurl' + AzureSearchIndex: azureSearchIndex + AzureSearchUseSemanticSearch: azureSearchUseSemanticSearch + AzureSearchSemanticSearchConfig: azureSearchSemanticSearchConfig + AzureSearchTopK: azureSearchTopK + AzureSearchContentColumns: azureSearchContentColumns + AzureSearchFilenameColumn: azureSearchFilenameColumn + AzureSearchTitleColumn: azureSearchTitleColumn + AzureSearchUrlColumn: azureSearchUrlColumn AzureOpenAIResource: aifoundry.outputs.aiFoundryName AzureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint AzureOpenAIModel: gptModelName - AzureOpenAITemperature: '0' - AzureOpenAITopP: '1' - AzureOpenAIMaxTokens: '1000' - AzureOpenAIStopSequence: '' - AzureOpenAISystemMessage: '''You are a helpful Wealth Advisor assistant''' + AzureOpenAITemperature: azureOpenAITemperature + AzureOpenAITopP: azureOpenAITopP + AzureOpenAIMaxTokens: azureOpenAIMaxTokens + AzureOpenAIStopSequence: azureOpenAIStopSequence + AzureOpenAISystemMessage: azureOpenAISystemMessage AzureOpenAIApiVersion: azureOpenaiAPIVersion - AzureOpenAIStream: 'True' - AzureSearchQueryType: 'simple' - AzureSearchVectorFields: 'contentVector' - AzureSearchPermittedGroupsField: '' - AzureSearchStrictness: '3' + AzureOpenAIStream: azureOpenAIStream + AzureSearchQueryType: azureSearchQueryType + AzureSearchVectorFields: azureSearchVectorFields + AzureSearchPermittedGroupsField: azureSearchPermittedGroupsField + AzureSearchStrictness: azureSearchStrictness AzureOpenAIEmbeddingName: embeddingModel AzureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint - USE_INTERNAL_STREAM: 'True' - SQLDB_SERVER: '${sqlDBModule.outputs.sqlServerName}.database.windows.net' + USE_INTERNAL_STREAM: useInternalStream + SQLDB_SERVER: sqlServerFqdn SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName AZURE_COSMOSDB_ACCOUNT: cosmosDBModule.outputs.cosmosAccountName AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: cosmosDBModule.outputs.cosmosContainerName AZURE_COSMOSDB_DATABASE: cosmosDBModule.outputs.cosmosDatabaseName - AZURE_COSMOSDB_ENABLE_FEEDBACK: 'True' + AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback //VITE_POWERBI_EMBED_URL: 'TBD' imageTag: imageTag userassignedIdentityClientId: managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId @@ -279,10 +309,55 @@ output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName output RESOURCE_GROUP_NAME string = resourceGroup().name output RESOURCE_GROUP_NAME_FOUNDRY string = aifoundry.outputs.resourceGroupNameFoundry -output SQLDB_SERVER string = sqlDBModule.outputs.sqlServerName +output SQLDB_SERVER_NAME string = sqlDBModule.outputs.sqlServerName output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId output AI_FOUNDRY_NAME string = aifoundry.outputs.aiFoundryName output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService output WEB_APP_NAME string = appserviceModule.outputs.webAppName +output APP_ENV string = appEnvironment +output APPINSIGHTS_INSTRUMENTATIONKEY string = aifoundry.outputs.instrumentationKey +output APPLICATIONINSIGHTS_CONNECTION_STRING string = aifoundry.outputs.applicationInsightsConnectionString +output AZURE_AI_AGENT_API_VERSION string = azureOpenaiAPIVersion +output AZURE_AI_AGENT_ENDPOINT string = aifoundry.outputs.aiFoundryProjectEndpoint +output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName +output AZURE_AI_SEARCH_ENDPOINT string = aifoundry.outputs.aiSearchTarget +output AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT string = functionAppCallTranscriptSystemPrompt +output AZURE_COSMOSDB_ACCOUNT string = cosmosDBModule.outputs.cosmosAccountName +output AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = cosmosDBModule.outputs.cosmosContainerName +output AZURE_COSMOSDB_DATABASE string = cosmosDBModule.outputs.cosmosDatabaseName +output AZURE_COSMOSDB_ENABLE_FEEDBACK string = azureCosmosDbEnableFeedback +output AZURE_OPENAI_EMBEDDING_ENDPOINT string = aifoundry.outputs.aoaiEndpoint +output AZURE_OPENAI_EMBEDDING_NAME string = embeddingModel +output AZURE_OPENAI_ENDPOINT string = aifoundry.outputs.aoaiEndpoint +output AZURE_OPENAI_MAX_TOKENS string = azureOpenAIMaxTokens +output AZURE_OPENAI_MODEL string = gptModelName +output AZURE_OPENAI_PREVIEW_API_VERSION string = azureOpenaiAPIVersion +output AZURE_OPENAI_RESOURCE string = aifoundry.outputs.aiFoundryName +output AZURE_OPENAI_STOP_SEQUENCE string = azureOpenAIStopSequence +output AZURE_OPENAI_STREAM string = azureOpenAIStream +output AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT string = functionAppStreamTextSystemPrompt +output AZURE_OPENAI_SYSTEM_MESSAGE string = azureOpenAISystemMessage +output AZURE_OPENAI_TEMPERATURE string = azureOpenAITemperature +output AZURE_OPENAI_TOP_P string = azureOpenAITopP +output AZURE_SEARCH_CONNECTION_NAME string = aifoundry.outputs.aiSearchFoundryConnectionName +output AZURE_SEARCH_CONTENT_COLUMNS string = azureSearchContentColumns +output AZURE_SEARCH_ENABLE_IN_DOMAIN string = azureSearchEnableInDomain +output AZURE_SEARCH_FILENAME_COLUMN string = azureSearchFilenameColumn +output AZURE_SEARCH_INDEX string = azureSearchIndex +output AZURE_SEARCH_PERMITTED_GROUPS_COLUMN string = azureSearchPermittedGroupsField +output AZURE_SEARCH_QUERY_TYPE string = azureSearchQueryType +output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = azureSearchSemanticSearchConfig +output AZURE_SEARCH_SERVICE string = aifoundry.outputs.aiSearchService +output AZURE_SEARCH_STRICTNESS string = azureSearchStrictness +output AZURE_SEARCH_TITLE_COLUMN string = azureSearchTitleColumn +output AZURE_SEARCH_TOP_K string = azureSearchTopK +output AZURE_SEARCH_URL_COLUMN string = azureSearchUrlColumn +output AZURE_SEARCH_USE_SEMANTIC_SEARCH string = azureSearchUseSemanticSearch +output AZURE_SEARCH_VECTOR_COLUMNS string = azureSearchVectorFields +output AZURE_SQL_SYSTEM_PROMPT string = functionAppSqlPrompt +output SQLDB_SERVER string = sqlServerFqdn +output SQLDB_USER_MID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId +output USE_AI_PROJECT_CLIENT string = useAIProjectClientFlag +output USE_INTERNAL_STREAM string = useInternalStream diff --git a/infra/main.json b/infra/main.json index 058de995b..43c2bf875 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "7434704573623874481" + "templateHash": "8388189115253050142" } }, "parameters": { @@ -94,7 +94,10 @@ }, "imageTag": { "type": "string", - "defaultValue": "latest" + "defaultValue": "latest", + "metadata": { + "description": "The Docker image tag to use for the application deployment." + } }, "aiDeploymentsLocation": { "type": "string", @@ -362,6 +365,31 @@ "uniqueId": "[toLower(uniqueString(parameters('environmentName'), subscription().id, variables('solutionLocation'), resourceGroup().name))]", "solutionPrefix": "[format('ca{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]", "abbrs": "[variables('$fxv#0')]", + "hostingPlanName": "[format('{0}{1}', variables('abbrs').compute.appServicePlan, variables('solutionPrefix'))]", + "websiteName": "[format('{0}{1}', variables('abbrs').compute.webApp, variables('solutionPrefix'))]", + "appEnvironment": "Prod", + "azureSearchIndex": "transcripts_index", + "azureSearchUseSemanticSearch": "True", + "azureSearchSemanticSearchConfig": "my-semantic-config", + "azureSearchTopK": "5", + "azureSearchContentColumns": "content", + "azureSearchFilenameColumn": "chunk_id", + "azureSearchTitleColumn": "client_id", + "azureSearchUrlColumn": "sourceurl", + "azureOpenAITemperature": "0", + "azureOpenAITopP": "1", + "azureOpenAIMaxTokens": "1000", + "azureOpenAIStopSequence": "\n", + "azureOpenAISystemMessage": "You are a helpful Wealth Advisor assistant", + "azureOpenAIStream": "True", + "azureSearchQueryType": "simple", + "azureSearchVectorFields": "contentVector", + "azureSearchPermittedGroupsField": "", + "azureSearchStrictness": "3", + "azureSearchEnableInDomain": "False", + "azureCosmosDbEnableFeedback": "True", + "useInternalStream": "True", + "useAIProjectClientFlag": "False", "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n Only return the generated SQL query. Do not return anything else.", "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'." @@ -744,7 +772,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "13609803072209612016" + "templateHash": "5605479917210969670" } }, "parameters": { @@ -1608,6 +1636,10 @@ "type": "string", "value": "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]" }, + "instrumentationKey": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').InstrumentationKey]" + }, "logAnalyticsWorkspaceResourceName": { "type": "string", "value": "[if(variables('useExisting'), variables('existingLawName'), variables('workspaceName'))]" @@ -2152,37 +2184,40 @@ "value": "[variables('solutionLocation')]" }, "HostingPlanName": { - "value": "[format('{0}{1}', variables('abbrs').compute.appServicePlan, variables('solutionPrefix'))]" + "value": "[variables('hostingPlanName')]" }, "WebsiteName": { - "value": "[format('{0}{1}', variables('abbrs').compute.webApp, variables('solutionPrefix'))]" + "value": "[variables('websiteName')]" + }, + "AppEnvironment": { + "value": "[variables('appEnvironment')]" }, "AzureSearchService": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" }, "AzureSearchIndex": { - "value": "transcripts_index" + "value": "[variables('azureSearchIndex')]" }, "AzureSearchUseSemanticSearch": { - "value": "True" + "value": "[variables('azureSearchUseSemanticSearch')]" }, "AzureSearchSemanticSearchConfig": { - "value": "my-semantic-config" + "value": "[variables('azureSearchSemanticSearchConfig')]" }, "AzureSearchTopK": { - "value": "5" + "value": "[variables('azureSearchTopK')]" }, "AzureSearchContentColumns": { - "value": "content" + "value": "[variables('azureSearchContentColumns')]" }, "AzureSearchFilenameColumn": { - "value": "chunk_id" + "value": "[variables('azureSearchFilenameColumn')]" }, "AzureSearchTitleColumn": { - "value": "client_id" + "value": "[variables('azureSearchTitleColumn')]" }, "AzureSearchUrlColumn": { - "value": "sourceurl" + "value": "[variables('azureSearchUrlColumn')]" }, "AzureOpenAIResource": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" @@ -2194,37 +2229,37 @@ "value": "[parameters('gptModelName')]" }, "AzureOpenAITemperature": { - "value": "0" + "value": "[variables('azureOpenAITemperature')]" }, "AzureOpenAITopP": { - "value": "1" + "value": "[variables('azureOpenAITopP')]" }, "AzureOpenAIMaxTokens": { - "value": "1000" + "value": "[variables('azureOpenAIMaxTokens')]" }, "AzureOpenAIStopSequence": { - "value": "" + "value": "[variables('azureOpenAIStopSequence')]" }, "AzureOpenAISystemMessage": { - "value": "You are a helpful Wealth Advisor assistant" + "value": "[variables('azureOpenAISystemMessage')]" }, "AzureOpenAIApiVersion": { "value": "[parameters('azureOpenaiAPIVersion')]" }, "AzureOpenAIStream": { - "value": "True" + "value": "[variables('azureOpenAIStream')]" }, "AzureSearchQueryType": { - "value": "simple" + "value": "[variables('azureSearchQueryType')]" }, "AzureSearchVectorFields": { - "value": "contentVector" + "value": "[variables('azureSearchVectorFields')]" }, "AzureSearchPermittedGroupsField": { - "value": "" + "value": "[variables('azureSearchPermittedGroupsField')]" }, "AzureSearchStrictness": { - "value": "3" + "value": "[variables('azureSearchStrictness')]" }, "AzureOpenAIEmbeddingName": { "value": "[parameters('embeddingModel')]" @@ -2233,7 +2268,7 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, "USE_INTERNAL_STREAM": { - "value": "True" + "value": "[variables('useInternalStream')]" }, "SQLDB_SERVER": { "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" @@ -2251,7 +2286,7 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" }, "AZURE_COSMOSDB_ENABLE_FEEDBACK": { - "value": "True" + "value": "[variables('azureCosmosDbEnableFeedback')]" }, "imageTag": { "value": "[parameters('imageTag')]" @@ -2300,7 +2335,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "14489288067602272519" + "templateHash": "17855260504239152628" } }, "parameters": { @@ -2338,6 +2373,9 @@ "WebsiteName": { "type": "string" }, + "AppEnvironment": { + "type": "string" + }, "AzureSearchService": { "type": "string", "defaultValue": "", @@ -2674,7 +2712,7 @@ "appSettings": [ { "name": "APP_ENV", - "value": "Prod" + "value": "[parameters('AppEnvironment')]" }, { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", @@ -3102,7 +3140,7 @@ "type": "string", "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.resourceGroupNameFoundry.value]" }, - "SQLDB_SERVER": { + "SQLDB_SERVER_NAME": { "type": "string", "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value]" }, @@ -3129,6 +3167,186 @@ "WEB_APP_NAME": { "type": "string", "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppName.value]" + }, + "APP_ENV": { + "type": "string", + "value": "[variables('appEnvironment')]" + }, + "APPINSIGHTS_INSTRUMENTATIONKEY": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.instrumentationKey.value]" + }, + "APPLICATIONINSIGHTS_CONNECTION_STRING": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + }, + "AZURE_AI_AGENT_API_VERSION": { + "type": "string", + "value": "[parameters('azureOpenaiAPIVersion')]" + }, + "AZURE_AI_AGENT_ENDPOINT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryProjectEndpoint.value]" + }, + "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": { + "type": "string", + "value": "[parameters('gptModelName')]" + }, + "AZURE_AI_SEARCH_ENDPOINT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchTarget.value]" + }, + "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT": { + "type": "string", + "value": "[variables('functionAppCallTranscriptSystemPrompt')]" + }, + "AZURE_COSMOSDB_ACCOUNT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" + }, + "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosContainerName.value]" + }, + "AZURE_COSMOSDB_DATABASE": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" + }, + "AZURE_COSMOSDB_ENABLE_FEEDBACK": { + "type": "string", + "value": "[variables('azureCosmosDbEnableFeedback')]" + }, + "AZURE_OPENAI_EMBEDDING_ENDPOINT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" + }, + "AZURE_OPENAI_EMBEDDING_NAME": { + "type": "string", + "value": "[parameters('embeddingModel')]" + }, + "AZURE_OPENAI_ENDPOINT": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" + }, + "AZURE_OPENAI_MAX_TOKENS": { + "type": "string", + "value": "[variables('azureOpenAIMaxTokens')]" + }, + "AZURE_OPENAI_MODEL": { + "type": "string", + "value": "[parameters('gptModelName')]" + }, + "AZURE_OPENAI_PREVIEW_API_VERSION": { + "type": "string", + "value": "[parameters('azureOpenaiAPIVersion')]" + }, + "AZURE_OPENAI_RESOURCE": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" + }, + "AZURE_OPENAI_STOP_SEQUENCE": { + "type": "string", + "value": "[variables('azureOpenAIStopSequence')]" + }, + "AZURE_OPENAI_STREAM": { + "type": "string", + "value": "[variables('azureOpenAIStream')]" + }, + "AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT": { + "type": "string", + "value": "[variables('functionAppStreamTextSystemPrompt')]" + }, + "AZURE_OPENAI_SYSTEM_MESSAGE": { + "type": "string", + "value": "[variables('azureOpenAISystemMessage')]" + }, + "AZURE_OPENAI_TEMPERATURE": { + "type": "string", + "value": "[variables('azureOpenAITemperature')]" + }, + "AZURE_OPENAI_TOP_P": { + "type": "string", + "value": "[variables('azureOpenAITopP')]" + }, + "AZURE_SEARCH_CONNECTION_NAME": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" + }, + "AZURE_SEARCH_CONTENT_COLUMNS": { + "type": "string", + "value": "[variables('azureSearchContentColumns')]" + }, + "AZURE_SEARCH_ENABLE_IN_DOMAIN": { + "type": "string", + "value": "[variables('azureSearchEnableInDomain')]" + }, + "AZURE_SEARCH_FILENAME_COLUMN": { + "type": "string", + "value": "[variables('azureSearchFilenameColumn')]" + }, + "AZURE_SEARCH_INDEX": { + "type": "string", + "value": "[variables('azureSearchIndex')]" + }, + "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN": { + "type": "string", + "value": "[variables('azureSearchPermittedGroupsField')]" + }, + "AZURE_SEARCH_QUERY_TYPE": { + "type": "string", + "value": "[variables('azureSearchQueryType')]" + }, + "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG": { + "type": "string", + "value": "[variables('azureSearchSemanticSearchConfig')]" + }, + "AZURE_SEARCH_SERVICE": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" + }, + "AZURE_SEARCH_STRICTNESS": { + "type": "string", + "value": "[variables('azureSearchStrictness')]" + }, + "AZURE_SEARCH_TITLE_COLUMN": { + "type": "string", + "value": "[variables('azureSearchTitleColumn')]" + }, + "AZURE_SEARCH_TOP_K": { + "type": "string", + "value": "[variables('azureSearchTopK')]" + }, + "AZURE_SEARCH_URL_COLUMN": { + "type": "string", + "value": "[variables('azureSearchUrlColumn')]" + }, + "AZURE_SEARCH_USE_SEMANTIC_SEARCH": { + "type": "string", + "value": "[variables('azureSearchUseSemanticSearch')]" + }, + "AZURE_SEARCH_VECTOR_COLUMNS": { + "type": "string", + "value": "[variables('azureSearchVectorFields')]" + }, + "AZURE_SQL_SYSTEM_PROMPT": { + "type": "string", + "value": "[variables('functionAppSqlPrompt')]" + }, + "SQLDB_SERVER": { + "type": "string", + "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" + }, + "SQLDB_USER_MID": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" + }, + "USE_AI_PROJECT_CLIENT": { + "type": "string", + "value": "[variables('useAIProjectClientFlag')]" + }, + "USE_INTERNAL_STREAM": { + "type": "string", + "value": "[variables('useInternalStream')]" } } } \ No newline at end of file diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index 7b6213eee..4419703f0 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -40,7 +40,7 @@ if [ -z "$keyvaultName" ]; then fi if [ -z "$sqlServerName" ]; then - sqlServerName=$(azd env get-value SQLDB_SERVER) + sqlServerName=$(azd env get-value SQLDB_SERVER_NAME) fi if [ -z "$SqlDatabaseName" ]; then From 2c170257d10703e269cc14543117c1ec615a3d29 Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Fri, 8 Aug 2025 17:46:09 +0530 Subject: [PATCH 12/84] Added "Required/Optional" and changed aiProjectName suffix. --- .../database/cosmos/cosmos-role-assign.bicep | 6 +- .../database/cosmos/deploy_cosmos_db.bicep | 19 ++-- infra/deploy_ai_foundry.bicep | 36 +++--- infra/deploy_aifp_aisearch_connection.bicep | 12 +- infra/deploy_app_service.bicep | 104 +++++++++--------- infra/deploy_cosmos_db.bicep | 12 +- ...deploy_foundry_model_role_assignment.bicep | 12 +- infra/deploy_keyvault.bicep | 30 +++-- infra/deploy_managed_identity.bicep | 6 +- infra/deploy_sql_db.bicep | 14 +-- infra/deploy_storage_account.bicep | 8 +- infra/main.bicep | 7 +- 12 files changed, 134 insertions(+), 132 deletions(-) diff --git a/infra/core/database/cosmos/cosmos-role-assign.bicep b/infra/core/database/cosmos/cosmos-role-assign.bicep index d159d0070..52cbed0d9 100644 --- a/infra/core/database/cosmos/cosmos-role-assign.bicep +++ b/infra/core/database/cosmos/cosmos-role-assign.bicep @@ -1,12 +1,12 @@ metadata description = 'Creates a SQL role assignment under an Azure Cosmos DB account.' -@description('Name of the Azure Cosmos DB account.') +@description('Required. Name of the Azure Cosmos DB account.') param accountName string -@description('ID of the Cosmos DB SQL role definition.') +@description('Required. ID of the Cosmos DB SQL role definition.') param roleDefinitionId string -@description('Principal ID to assign the role to.') +@description('Otional. Principal ID to assign the role to.') param principalId string = '' resource role 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2022-05-15' = { diff --git a/infra/core/database/cosmos/deploy_cosmos_db.bicep b/infra/core/database/cosmos/deploy_cosmos_db.bicep index 15d451836..dae6d7bbe 100644 --- a/infra/core/database/cosmos/deploy_cosmos_db.bicep +++ b/infra/core/database/cosmos/deploy_cosmos_db.bicep @@ -1,21 +1,20 @@ -@minLength(3) -@maxLength(15) -@description('Name of the solution.') + +@description('Required. Name of the solution.') param solutionName string -@description('Deployment location for the solution.') +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name of the Cosmos DB account.') +@description('Otional. Name of the Cosmos DB account.') param accountName string = '${solutionName}-cosmos' -@description('Name of the Cosmos DB database.') +@description('Otional. Name of the Cosmos DB database.') param databaseName string = 'db_conversation_history' -@description('Name of the Cosmos DB container.') +@description('Otional. Name of the Cosmos DB container.') param collectionName string = 'conversations' -@description('List of Cosmos DB containers to be created.') +@description('Otional. List of Cosmos DB containers to be created.') param containers array = [ { name: collectionName @@ -24,11 +23,11 @@ param containers array = [ } ] -@description('API kind of the Cosmos DB account.') +@description('Otional. API kind of the Cosmos DB account.') @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) param kind string = 'GlobalDocumentDB' -@description('Resource tags to apply.') +@description('Otional. Resource tags to apply.') param tags object = {} resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep index 838340b10..9e823bd02 100644 --- a/infra/deploy_ai_foundry.bicep +++ b/infra/deploy_ai_foundry.bicep @@ -1,39 +1,41 @@ // Creates Azure dependent resources for Azure AI studio -@minLength(3) -@maxLength(15) -@description('Solution Name') + +@description('Required. Solution Name') param solutionName string -@description('Solution Location') +@description('Required. Solution Location') param solutionLocation string -@description('Contains Name of KeyVault.') +@description('Required. Contains Name of KeyVault.') param keyVaultName string -@description('Indicates the type of Deployment.') +@description('Required. Indicates the type of Deployment.') param deploymentType string -@description('GPT Model Name') -param gptModelName string +@description('Optional. GPT Model Name') +param gptModelName string = 'gpt-4o-mini' -@description('Azure OepnAI API Version.') +@description('Required. Azure OepnAI API Version.') param azureOpenaiAPIVersion string -@description('Param to get Deployment Capacity.') +@description('Required. Param to get Deployment Capacity.') param gptDeploymentCapacity int -@description('Embedding Model.') -param embeddingModel string +@description('Optional. Embedding Model.') +param embeddingModel string = 'text-embedding-ada-002' -@description('Info about Embedding Deployment Capacity.') -param embeddingDeploymentCapacity int +@description('Optional. Info about Embedding Deployment Capacity.') +param embeddingDeploymentCapacity int = 80 -@description('Existing Log Analytics WorkspaceID.') +@description('Optional. Existing Log Analytics WorkspaceID.') param existingLogAnalyticsWorkspaceId string = '' -@description('Azure Existing AI Project ResourceID.') +@description('Optional. Azure Existing AI Project ResourceID.') param azureExistingAIProjectResourceId string = '' +@description('Required. The name of the AI Foundry AI Project resource in Azure.') +param aiFoundryAiServicesAiProjectResourceName string + @description('Optional. Tags to be applied to the resources.') param tags object = {} @@ -44,7 +46,7 @@ var aiFoundryName = 'aif-${solutionName}' var applicationInsightsName = 'appi-${solutionName}' var keyvaultName = keyVaultName var location = solutionLocation //'eastus2' -var aiProjectName = 'aifp-${solutionName}' +var aiProjectName = '${aiFoundryAiServicesAiProjectResourceName}-${solutionName}' var aiProjectFriendlyName = aiProjectName var aiProjectDescription = 'AI Foundry Project' var aiSearchName = 'srch-${solutionName}' diff --git a/infra/deploy_aifp_aisearch_connection.bicep b/infra/deploy_aifp_aisearch_connection.bicep index f3719d11e..ee0424d33 100644 --- a/infra/deploy_aifp_aisearch_connection.bicep +++ b/infra/deploy_aifp_aisearch_connection.bicep @@ -1,19 +1,19 @@ -@description('Existing AI Project Name') +@description('Required. Existing AI Project Name') param existingAIProjectName string -@description('Existing AI Foundry Name') +@description('Required. Existing AI Foundry Name') param existingAIFoundryName string -@description('AI Search Name') +@description('Required. AI Search Name') param aiSearchName string -@description('AI Search Resource ID') +@description('Required. AI Search Resource ID') param aiSearchResourceId string -@description('AI Search Location') +@description('Required. AI Search Location') param aiSearchLocation string -@description('AI Search Connection Name') +@description('Required. AI Search Connection Name') param aiSearchConnectionName string resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { diff --git a/infra/deploy_app_service.bicep b/infra/deploy_app_service.bicep index 930a7bdc6..4f5547251 100644 --- a/infra/deploy_app_service.bicep +++ b/infra/deploy_app_service.bicep @@ -1,163 +1,163 @@ // ========== Key Vault ========== // targetScope = 'resourceGroup' -@description('Solution Location') +@description('Required. Solution Location') param solutionLocation string -@description('The pricing tier for the App Service plan') +@description('Optional. The pricing tier for the App Service plan') @allowed(['F1', 'D1', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1', 'P2', 'P3', 'P4', 'P0v3']) param hostingPlanSku string = 'B2' -@description('Name of App Service plan') +@description('Required. Name of App Service plan') param hostingPlanName string -@description('Name of Web App') +@description('Required. Name of Web App') param websiteName string // @description('Name of Application Insights') // param ApplicationInsightsName string = '${ solutionName }-app-insights' -@description('Name of Azure Search Service') +@description('Optional. Name of Azure Search Service') param azureSearchService string = '' -@description('Name of Azure Search Index') +@description('Optional. Name of Azure Search Index') param azureSearchIndex string = '' -@description('Use semantic search') +@description('Optional. Use semantic search') param azureSearchUseSemanticSearch string = 'False' -@description('Semantic search config') +@description('Optional. Semantic search config') param azureSearchSemanticSearchConfig string = 'default' -@description('Top K results') +@description('Optional. Top K results') param azureSearchTopK string = '5' -@description('Enable in domain') +@description('Optional. Enable in domain') param azureSearchEnableInDomain string = 'False' -@description('Content columns') +@description('Optional. Content columns') param azureSearchContentColumns string = 'content' -@description('Filename column') +@description('Optional. Filename column') param azureSearchFilenameColumn string = 'filename' -@description('Title column') +@description('Optional. Title column') param azureSearchTitleColumn string = 'client_id' -@description('Url column') +@description('Optional. Url column') param azureSearchUrlColumn string = 'sourceurl' -@description('Name of Azure OpenAI Resource') +@description('Required. Name of Azure OpenAI Resource') param azureOpenAIResource string -@description('Azure OpenAI Model Deployment Name') -param azureOpenAIModel string +@description('Optional. Azure OpenAI Model Deployment Name') +param azureOpenAIModel string = 'gpt-4o-mini' -@description('Azure Open AI Endpoint') +@description('Optional. Azure Open AI Endpoint') param azureOpenAIEndpoint string = '' -@description('Azure OpenAI Temperature') +@description('Optional. Azure OpenAI Temperature') param azureOpenAITemperature string = '0' -@description('Azure OpenAI Top P') +@description('Optional. Azure OpenAI Top P') param azureOpenAITopP string = '1' -@description('Azure OpenAI Max Tokens') +@description('Optional. Azure OpenAI Max Tokens') param azureOpenAIMaxTokens string = '1000' -@description('Azure OpenAI Stop Sequence') +@description('Optional. Azure OpenAI Stop Sequence') param azureOpenAIStopSequence string = '\n' -@description('Azure OpenAI System Message') +@description('Optional. Azure OpenAI System Message') param azureOpenAISystemMessage string = 'You are an AI assistant that helps people find information.' -@description('Azure OpenAI Api Version') +@description('Optional. Azure OpenAI Api Version') param azureOpenAIApiVersion string = '2024-02-15-preview' -@description('Whether or not to stream responses from Azure OpenAI') +@description('Optional. Whether or not to stream responses from Azure OpenAI') param azureOpenAIStream string = 'True' -@description('Azure Search Query Type') +@description('Optional. Azure Search Query Type') @allowed(['simple', 'semantic', 'vector', 'vectorSimpleHybrid', 'vectorSemanticHybrid']) param azureSearchQueryType string = 'simple' -@description('Azure Search Vector Fields') +@description('Optional. Azure Search Vector Fields') param azureSearchVectorFields string = 'contentVector' -@description('Azure Search Permitted Groups Field') +@description('Optional. Azure Search Permitted Groups Field') param azureSearchPermittedGroupsField string = '' -@description('Azure Search Strictness') +@description('Optional. Azure Search Strictness') @allowed(['1', '2', '3', '4', '5']) param azureSearchStrictness string = '3' -@description('Azure OpenAI Embedding Deployment Name') +@description('Optional. Azure OpenAI Embedding Deployment Name') param azureOpenAIEmbeddingName string = '' -@description('Azure Open AI Embedding Endpoint') +@description('Optional. Azure Open AI Embedding Endpoint') param azureOpenAIEmbeddingEndpoint string = '' -@description('Use Azure Function') +@description('Optional. Use Azure Function') param USE_INTERNAL_STREAM string = 'True' -@description('SQL Database Server Name') +@description('Optional. SQL Database Server Name') param SQLDB_SERVER string = '' -@description('SQL Database Name') +@description('Optional. SQL Database Name') param SQLDB_DATABASE string = '' -@description('Azure Cosmos DB Account') +@description('Optional. Azure Cosmos DB Account') param AZURE_COSMOSDB_ACCOUNT string = '' -@description('Azure Cosmos DB Conversations Container') +@description('Optional. Azure Cosmos DB Conversations Container') param AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = '' -@description('Azure Cosmos DB Database') +@description('Optional. Azure Cosmos DB Database') param AZURE_COSMOSDB_DATABASE string = '' -@description('Enable feedback in Cosmos DB') +@description('Optional. Enable feedback in Cosmos DB') param AZURE_COSMOSDB_ENABLE_FEEDBACK string = 'True' //@description('Power BI Embed URL') //param VITE_POWERBI_EMBED_URL string = '' -@description('The container image tag to be deployed') +@description('Required. The container image tag to be deployed') param imageTag string -@description('The resource ID of the user-assigned managed identity to be used by the deployed resources.') +@description('Required. The resource ID of the user-assigned managed identity to be used by the deployed resources.') param userassignedIdentityId string -@description('The client ID of the user-assigned managed identity.') +@description('Required. The client ID of the user-assigned managed identity.') param userassignedIdentityClientId string -@description('The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring.') +@description('Required. The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring.') param applicationInsightsId string -@description('The endpoint URL of the Azure Cognitive Search service.') +@description('Required. The endpoint URL of the Azure Cognitive Search service.') param azureSearchServiceEndpoint string -@description('Azure Function App SQL System Prompt') +@description('Required. Azure Function App SQL System Prompt') param sqlSystemPrompt string -@description('Azure Function App CallTranscript System Prompt') +@description('Required. Azure Function App CallTranscript System Prompt') param callTranscriptSystemPrompt string -@description('Azure Function App Stream Text System Prompt') +@description('Required. Azure Function App Stream Text System Prompt') param streamTextSystemPrompt string -@description('AI Foundry project endpoint URL.') +@description('Required. AI Foundry project endpoint URL.') param aiFoundryProjectEndpoint string -@description('Flag to enable AI project client.') +@description('Optional. Flag to enable AI project client.') param useAIProjectClientFlag string = 'false' -@description('Name of the AI Foundry project.') +@description('Required. Name of the AI Foundry project.') param aiFoundryName string -@description('Application Insights connection string.') +@description('Required. Application Insights connection string.') param applicationInsightsConnectionString string -@description('Connection name for Azure Cognitive Search.') +@description('Required. Connection name for Azure Cognitive Search.') param aiSearchProjectConnectionName string @description('Optional. Tags to be applied to the resources.') @@ -169,7 +169,7 @@ param tags object = {} var webAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:${imageTag}' -@description('Resource ID of the existing AI Foundry project.') +@description('Optional. Resource ID of the existing AI Foundry project.') param azureExistingAIProjectResourceId string = '' var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) diff --git a/infra/deploy_cosmos_db.bicep b/infra/deploy_cosmos_db.bicep index 2a8aca40e..58eacf26c 100644 --- a/infra/deploy_cosmos_db.bicep +++ b/infra/deploy_cosmos_db.bicep @@ -1,19 +1,19 @@ @minLength(3) @maxLength(20) -@description('Solution location.') +@description('Required. Solution location.') param solutionLocation string -@description('Name of the Azure Cosmos DB account.') +@description('Required. Name of the Azure Cosmos DB account.') param cosmosDBName string -@description('Name of the Cosmos DB database.') +@description('Optional. Name of the Cosmos DB database.') param databaseName string = 'db_conversation_history' -@description('Name of the Cosmos DB container (collection).') +@description('Optional.Name of the Cosmos DB container (collection).') param collectionName string = 'conversations' -@description('List of Cosmos DB containers to be created.') +@description('Optional. List of Cosmos DB containers to be created.') param containers array = [ { name: collectionName @@ -22,7 +22,7 @@ param containers array = [ } ] -@description('The API kind of the Cosmos DB account.') +@description('Optional. The API kind of the Cosmos DB account.') @allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) param kind string = 'GlobalDocumentDB' diff --git a/infra/deploy_foundry_model_role_assignment.bicep b/infra/deploy_foundry_model_role_assignment.bicep index 078903784..b4da8843c 100644 --- a/infra/deploy_foundry_model_role_assignment.bicep +++ b/infra/deploy_foundry_model_role_assignment.bicep @@ -1,19 +1,19 @@ -@description('Principal ID to assign the role to.') +@description('Optional. Principal ID to assign the role to.') param principalId string = '' -@description('ID of the role definition to assign.') +@description('Required. ID of the role definition to assign.') param roleDefinitionId string -@description('Name of the role assignment.') +@description('Optional. Name of the role assignment.') param roleAssignmentName string = '' -@description('Name of the AI Foundry resource.') +@description('Required. Name of the AI Foundry resource.') param aiFoundryName string -@description('Name of the AI project.') +@description('Optional. Name of the AI project.') param aiProjectName string = '' -@description('List of AI model deployments.') +@description('Optional. List of AI model deployments.') param aiModelDeployments array = [] @description('Optional. Tags to be applied to the resources.') diff --git a/infra/deploy_keyvault.bicep b/infra/deploy_keyvault.bicep index 005b5c123..d8141baa8 100644 --- a/infra/deploy_keyvault.bicep +++ b/infra/deploy_keyvault.bicep @@ -1,56 +1,54 @@ // ========== Key Vault ========== // targetScope = 'resourceGroup' -@minLength(3) -@maxLength(15) -@description('Solution Name') +@description('Required. Solution Name') param solutionName string -@description('Solution Location') +@description('Required. Solution Location') param solutionLocation string -@description('Current UTC timestamp.') +@description('Optional. Current UTC timestamp.') param utc string = utcNow() -@description('Name of the Azure Key Vault.') +@description('Required. Name of the Azure Key Vault.') param kvName string -@description('Specifies the create mode for the resource.') +@description('Optional. Specifies the create mode for the resource.') param createMode string = 'default' -@description('Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') +@description('Optional. Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') param enableForDeployment bool = true -@description('Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.') +@description('Optional. Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.') param enableForDiskEncryption bool = true -@description('Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault.') +@description('Optional. Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault.') param enableForTemplateDeployment bool = true -@description('Enable RBAC Authorization. Property that controls how data actions are authorized.') +@description('Optional. Enable RBAC Authorization. Property that controls how data actions are authorized.') param enableRBACAuthorization bool = true -@description('Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90.') +@description('Optional. Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90.') param softDeleteRetentionInDays int = 7 -@description('Public Network Access, Property to specify whether the vault will accept traffic from public internet.') +@description('Optional. Public Network Access, Property to specify whether the vault will accept traffic from public internet.') @allowed([ 'enabled' 'disabled' ]) param publicNetworkAccess string = 'enabled' -@description('SKU') +@description('Optional. SKU') @allowed([ 'standard' 'premium' ]) param sku string = 'standard' -@description('Vault URI. The URI of the vault for performing operations on keys and secrets.') +@description('Optional. Vault URI. The URI of the vault for performing operations on keys and secrets.') var vaultUri = 'https://${ kvName }.vault.azure.net/' -@description('Object ID of the managed identity.') +@description('Required. Object ID of the managed identity.') param managedIdentityObjectId string @description('Optional. Tags to be applied to the resources.') diff --git a/infra/deploy_managed_identity.bicep b/infra/deploy_managed_identity.bicep index cb59ee570..16746b2e7 100644 --- a/infra/deploy_managed_identity.bicep +++ b/infra/deploy_managed_identity.bicep @@ -3,13 +3,13 @@ targetScope = 'resourceGroup' @minLength(3) @maxLength(15) -@description('Name of the solution.') +@description('Required. Name of the solution.') param solutionName string -@description('Deployment location for the solution.') +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name of the managed identity.') +@description('Required. Name of the managed identity.') param miName string @description('Optional. Tags to be applied to the resources.') diff --git a/infra/deploy_sql_db.bicep b/infra/deploy_sql_db.bicep index dd4076a66..e2a964b2c 100644 --- a/infra/deploy_sql_db.bicep +++ b/infra/deploy_sql_db.bicep @@ -1,22 +1,22 @@ -@description('Deployment location for the solution.') +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name of the Azure Key Vault.') +@description('Required. Name of the Azure Key Vault.') param keyVaultName string -@description('Object ID of the managed identity.') +@description('Required. Object ID of the managed identity.') param managedIdentityObjectId string -@description('Name of the managed identity.') +@description('Required. Name of the managed identity.') param managedIdentityName string -@description('The name of the SQL logical server.') +@description('Required. The name of the SQL logical server.') param serverName string -@description('The name of the SQL Database.') +@description('Required. The name of the SQL Database.') param sqlDBName string -@description('Location for all resources.') +@description('Required. Location for all resources.') param location string = solutionLocation @description('Optional. Tags to be applied to the resources.') diff --git a/infra/deploy_storage_account.bicep b/infra/deploy_storage_account.bicep index 994f7979a..9f269d39b 100644 --- a/infra/deploy_storage_account.bicep +++ b/infra/deploy_storage_account.bicep @@ -1,16 +1,16 @@ // ========== Storage Account ========== // targetScope = 'resourceGroup' -@description('Deployment location for the solution.') +@description('Required. Deployment location for the solution.') param solutionLocation string -@description('Name of the storage account.') +@description('Required. Name of the storage account.') param saName string -@description('Object ID of the managed identity.') +@description('Required. Object ID of the managed identity.') param managedIdentityObjectId string -@description('Name of the Azure Key Vault.') +@description('Required. Name of the Azure Key Vault.') param keyVaultName string @description('Optional. Tags to be applied to the resources.') diff --git a/infra/main.bicep b/infra/main.bicep index bd2fa9639..a8b41762f 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -40,7 +40,7 @@ param azureOpenaiAPIVersion string = '2025-04-01-preview' param gptDeploymentCapacity int = 200 @minLength(1) -@description('Name of the Text Embedding model to deploy:') +@description('Optional. Name of the Text Embedding model to deploy:') @allowed([ 'text-embedding-ada-002' ]) @@ -68,7 +68,7 @@ param imageTag string = 'latest' ] } }) -@description('Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') +@description('Rquired. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') param aiDeploymentsLocation string @description('Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') @@ -138,6 +138,8 @@ var functionAppStreamTextSystemPrompt = '''The currently selected client's name @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} +var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -189,6 +191,7 @@ module aifoundry 'deploy_ai_foundry.bicep' = { embeddingDeploymentCapacity: embeddingDeploymentCapacity existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId azureExistingAIProjectResourceId: azureExistingAIProjectResourceId + aiFoundryAiServicesAiProjectResourceName : aiFoundryAiServicesAiProjectResourceName tags: tags } scope: resourceGroup(resourceGroup().name) From 51df2692f5166c520d95d68aaa90fbd0f8348444 Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Fri, 8 Aug 2025 19:03:43 +0530 Subject: [PATCH 13/84] removed location tag --- infra/main.parameters.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 9accd9fa3..8fe1de47a 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -40,8 +40,7 @@ }, "tags": { "value": { - "solutionName": "${AZURE_ENV_NAME}", - "location": "${AZURE_LOCATION}" + "solutionName": "${AZURE_ENV_NAME}" } } } From f202b609b71dbd47ffd097354fe95878336f185e Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Mon, 11 Aug 2025 11:27:34 +0530 Subject: [PATCH 14/84] foundry project documentation --- docs/DeploymentGuide.md | 7 +++ .../azure_ai_foundry_list.png | Bin 0 -> 339436 bytes .../navigate_to_projects.png | Bin 0 -> 97180 bytes .../project_resource_id.png | Bin 0 -> 200850 bytes docs/re-use-foundry-project.md | 44 ++++++++++++++++++ 5 files changed, 51 insertions(+) create mode 100644 docs/images/re_use_foundry_project/azure_ai_foundry_list.png create mode 100644 docs/images/re_use_foundry_project/navigate_to_projects.png create mode 100644 docs/images/re_use_foundry_project/project_resource_id.png create mode 100644 docs/re-use-foundry-project.md diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index 7d54bbc54..043bcccb4 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -143,6 +143,13 @@ To adjust quota settings, follow these [steps](./AzureGPTQuotaSettings.md). Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) + +
+ + Reusing an Existing Azure AI Foundry Project + + Guide to get your [Existing Project ID](/docs/re-use-foundry-project.md) +
### Deploying with AZD diff --git a/docs/images/re_use_foundry_project/azure_ai_foundry_list.png b/docs/images/re_use_foundry_project/azure_ai_foundry_list.png new file mode 100644 index 0000000000000000000000000000000000000000..784bc85c74c188f7e647fba807fd3157bf191273 GIT binary patch literal 339436 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 z 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

ja1gw1j{sEZJuZtRXDwl%- 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 literal 0 HcmV?d00001 diff --git a/docs/images/re_use_foundry_project/navigate_to_projects.png b/docs/images/re_use_foundry_project/navigate_to_projects.png new file mode 100644 index 0000000000000000000000000000000000000000..11082c15c95c2c0b85ed5a9974670b6ee6d2d0cd GIT binary patch literal 97180 zcmagGXH-*b)HP~DIYEV^1Qi7IC<;NuMiB%QL=;32*a;+5k(Lmufb^mP1;q*?RY2NK zLQO&^f*?q536KZ~N)4eW)WFU8-usRF=X%~h2_qwez4JV4tvT0R^V!dh@97@FsNN0nLQICJ@qQF4MP%!KvZ7GG^+Oczw$o2nxIVu^RyG43R$=uy$@%Mj!_4I3 zf4zJ$5j{b&ie9x8|6i|vf;4vQf4l6?J@FYk(%B~U6W#xH)&IGtbcK!`yM9@IN=&ps zi|IPLJP>OLxS=)aUUK^7x#Hdo?S~6Ti*nIY>+HDcZId*F<-En(kGEIIB#KVgwiE+S zOq6+Jaq`j<#sZD&KiB0m+SGl8EZgmKNWR|So?#!2*PZOscSYH%#)ODWob*}RI66yK zKh0pCqXj*R%B+XrB7mOyZZsN@%#r)-SfnMJ2enVJI@>ai?CfxPh|}gGEt9r8%?2d? zV`bJ4^H*iyx^1%1(@yvZ>iFAs)x|xRfVW5V;>`}ri~T}Qmb*T2mKsBUw%f?IEjDhd z)5W?d31w#@6TqK7vZx1+v||73A7w@1v3Z5v)sJSI4jB-SHdKd?8@E$cH{ehP>?XHR ztyus4>WO!!B}^@L-}@&9`bGXbT~l+PQL2VB{EGC(P=FkzxKbvhUA0-;O1OeEce=EWFc}?DcqQrC-l^|c;L(34f%Jbg;SnZ3bkgh zlg@yLfc_7_AXwpb&K0;U{{hfIlKUa?^$>-_roRa=b0AhM`i;yT8pP$HOu;p5h})R{ z1KMeF+0hMujUQ%Nc@g*h>7Zo;JUUZmI=jf_X5!0#EF6klEaw9UOz)KJpvyn-DfMxJ zG8}IdL@-s)2dJW+fnrA;G!OyKLg)CYlsYiW>v}@w)>* z`U*Gk_ToQ(4D&)&{_l&)Y%CId@ir{#HCew)gfYIGxw&z&^#GK-RVtSdLqI>58InR= zOnR&C{?zpjlCuI}j%7j4wpfTGSecf0M1Mf95lnwh5u@;6P3zk_)$@-(+gCuqobHTAF!dTJ_zk^m*6;VPbS||iUufReS(4}UyG*9KovY!h6(0)+H z!$`NwLG0#PS}*FDe8w`oMqeh=btciv;JfK9gJy^nU_tox01vk}$OOO3(=QGQTd<=_ zJn_Ur#_7NXm$oxzY)1ql_apMgl5B1OK&yTueOA_UDh(1F_dX!T4wyLO82353?Xrmg zKJs&>sdS9WnfM|YYVb76ooEcytK|$|wZLrMp&7y$IT>phO#yi^v%iy&aZ~9xIL|H!$wR*cv{>Oh$nf$w5P4Ak*%f)1070O(qJ|28ahDkI6`c>c3#3e! z7s|ka1oJMAXf7;)n9`xnbsTZeGT}tG_wF_n_+(+8=h6A=m-&btG4YDNg1)?FyO+3y zpNz!YZ-i-6ysS4-X;a~*)Opwz?WzGXkbZ>vvV>#JdG|!dE&e8s(BP#&n7Q)+Z-ybh z*4V*~vrf5uUUsup>@CskANltt7;jMvn%DplN4AK|*ESX?9EhkiK?vCc1jiMakXG=t zBmEs*BNra{fP5hqqH6#G!c9IAv!}^k7UAgMjd9g+C9o&J{iMfXL>S6HKDs&DwBR!! zkgugk_J@h~Nr%GU);E)V!hV^>eRWh&)4PbOgm3?a`7xcS&=3KwrY(v{yZY+=89Bm3 z1a4g+Y0RHB8c2e>lNGC-$eKqs848MwVe3gsi?Dg1O+U$I4kg{S9=&(!7)d$s{MyW;gRJ_k_EQK#q#bnEt- zCbsThW`W>3rY^Sd55E%6o9XM2bA5;M6E**>=e;#mFulW>wPM_jd6v`xlFt zJ(P-omM2n5-RYm=hG6@Nu%}m8`HmANIG6H7^H&j#`VSB57d14Td)8alLqcP9fx&<` zMNmvF7-3qSeNpy3EkWqtU?_K`vlVb9#>j6LEx$oSXE}e=`03{*bwAAe%sxq=bk*@P zC5Sc0GtUvw9C-J~Ym>la67n;>1;8;a#)=W`w*;t10<8MX;s_Cr!;cg*dSNcEgW%Eg zx9BDqfx#}*g{7u@v|`$6ZebPqC@SyM)%tk&Dc0gcRJx~Y79$u~#fQOZWkLZkF472Y zLFju_5ZL^jEG9%5ff3>7KEq-4aM{mvaHHh2rmf|^KVnz-*mc`-VQeT5kHPW|C>MUb zJbZ<~B<@%!W2G+HcMSQU)0(>-wVAh0t`zPqPS~d;K~nbq=-82EtL(}N-?B|-U$=XF z+5Y(V;9K&f6z>)V%Ipgt_5pWuEzH&9pbUmaN&g4oeMdAmfMp5LVO1?~4TZCBd_G_l zJn#Vi7R0H#sn@In2?L`|8g=17oMW;;CFD3jM4FJZqVgR9&N#4V5{S3X!AyTj(k1C% zua++=6@_#7yFqrMZ@UmJ0CIo>5g_83ghOZHckM9>4snE`$o?KZfjo#C&HJjvyQoso zlBU{IuLg^jg}TL`BbpI1k>pgVlD~)+FR}FU(w~lp^~n(Pn7az$O1?O<+J}9GXhXuh z1&bv1Ksa2z<$0+D`~vv80#7J`6UfH%oBb~jttF7ffC0SN*MZ~E_vlLi`{#CR`x~Gd zl=Kq2u4g?mZOU165coOtww%sDp3JMWlRS$M$kxyRSlz+V|6t^%0xGD0X~|f|7BRUL z3_gXST^AY;C(NcJ^;tF3*W`OaPn#v*W{B^pv{Htx{A*hBqt?qy&6I9P5wSPFzZ|XL z*@cf8%UBus558#oa8LY3C&eVM-|xKY@DR1-JZ0hAB5`>&Mu3O7z5&d+gtNmq1LZ1W!{6}dI1-XyTJf!yq z;R(-ZstAM)MIbihs@P`ziH@uhcn&C zX8M#N5R5O%u~709Z=nB*p`V$B?R#nUrXb>>?y%Rp7f3aHTajb_gF%zemhec$;XWUceMZQfIgP{m;;C+Zbb(px>~ zwkZSR+jEk|h37>!>sIG*Ygzf`#p+rmsa9Sq*VNZa6<06X1P@OKGcK?a=B@jw_YFpG z8QmZw$wKW~?oGACaZi2_Fy!W}28~cQymHlaw(TuWx?@d+&-gTPwsJVzbY_ORjeXT2 zulT?+bn9!W<@c@m$4>5#VI5kUU3N>Ahc{Zh2PtFAzJYx&{t9l@$#M;jbG;l#>AR*n zoWI@@UM+bJP*gjT;FmwDkf@aJJ@@!K#o8$%n60&$uO#T?7Y^0n{bJ#0x#sZ&b> z{Vmgs=;fVG01ZO9C20(0e%SWp7ne; zPSiyOKd6Poo9kWWXuV@Xl+%y;$Du#yUlDRV9HSdETHicII8cJzBi|%n4`39;Gg z;VWhe#BcQO42UH;5AKCWBaeYoB^DOmB!Wy)1X7?70+QkRE((MCw2Taxg^*Z-04wh$ zo|s#d@Cu)Kc0qs2x8MYw@#Xoxss>f^Jz(51r5KAdK{w>uLd$c;-M~@6Z7?yB%uilB zKN>#9=b%8x;+x@m`r9^EJ_91NG;x!A$P7RA0OOjq%;`+pcGUe3a?)_*=t3tYp`( z&-+OtC?~%MioU=m!}><@Q{Qg_7=_P6R#)YFI6nr?fV~K8+$mx@igfe2+k107{pm}C z8tVe&e)-3M56JjO@f>ExQJ!>*4v%K@Yb-($?*(9^0?!rW2BFg1^N9)jzV<9XE$MtKy#)ah2?be);0e2(Iaq zhd2t@9#=8&o*qFoHfivl?zkS)ZUHk-e=pEPaAD4bmJ~|=S#^6NbdF#0Y93tPe^EM8 z38JN=%lb?&D-~c9s{2UfY|-ksCJJFosNj~dTDjKUuVc=y$Fd8*24dfBufEzT{1-HN zw^ixKPRjkJ!53YlPbq#H15^C(hqG+ci5C`EWNhS6!b;}OsNovb<7w_X*Z%!88aXjJ zSK;5WG#m6CZ>eK2vBfy00g+{B1aAl&?GwMmyaRm$ zj!D>A>|33f--T}246-Yfk)7wW{Fzc%>H4bLvS>{MiigueREp&k1d@6k>A-VBNsj9~ zroPl|@ZCbyY&0VJUaIkO^F0Ba_0A+`D>LHlq=4MK!U9n|)N2&_8FCuvnyPi85tQI@ zXt*wGgaDfU%J7N9!=FJxv2%v}F0f6T&dq@7moyoV6d{)=@A(-0S^f+h*s#s|>M6!R zWBX#=h%P8D`p4-u?ao*Y0@bi*@DuorE(>_ec+(jYXrk)X=%qkP$@OtJ z6=+53z=;|5!c?ViBykH=5}fjKu=)WMbuyaJZ!$k^PAtHCR4bhCk4OazV(|VH);IHq zk{W2wWE>9}V_y9Nn!OTReRd)KySNcWxeyG8I({JFbI?nb??YUpv;t7Q=x9f zRdcYY$AQ0%pW}ZC@zqsQHb&IKXn^1fBn^LL@H%|^)lBuR8^9flA8{H3>-jPQQQ-Xk zbmFf1rrr9u0Pq$KSuCjmjfp8TbW=#ks8u!Dfg;mQzN3~Lc~Rt8^bGCe$m)ZOWSrHT zQd{mcoE`rSeyW_b)$NFW>5`W)Y=C__G>sE~-{O1LJT6JW!-qp<_wQKOB5D{UeXw9yEQ zB>EM2p(`Z2#Cv*Jl6G^ylpAZr^d}>O($&!a#1}_-$LQ5YVi>S%2!n|xs>m{PiwfnWp zBqVdTRvSgutyu^0t}R3giQJbugc>|bujEYY$G!e$!L-HYD=HHDc^y6jk@95oBGCc7 zkUaOU(}Tm1H->J{s}fCtbdUsG#2VZ<;4cKvGb0k-pZ`wBDCAu#HD9JQqrVV*seH`9 z$mgmd4m>&~EDWuHiI~MXDJiq2FS1|=g5E`!LmS9^2%ICWIJqp+N5EH(LQ@+ecHK}E z;pn3TN^!DWcs=A$wT71~ee{MMS~BH%H3+fIN~cA0K06AvJeL`3(kBX-Uc-BT&pMhT z<$MMJB4O{(nJWeM6;usvNz+-b3C8kC{M^ z?nD#FPD1F?8H2iYMj7~x-|U)!TLDVZ*GMmW`YU>iE8d!;ihe>{!AG=yBCijGj#93yEf2>Y3v(BQZCww_;>R9QdXF`H&h*<===G9k7W@)CzS@3TmG<#j?L=+2JT{M`;qN)W$0twcFLBb|ZWcOJ*_ECtl4Jbi;p{ z0Pzd`+>hb^4Ga72 z+@XhFb6JZW%@f1bAaCLMK_S-mK{3Yk>9r-Q_qRRFfRw+uy?BQ-)6j6AV39N>?OBuy zJ`LI(sgKl1LTt_CP&mrW{t8z6yjWGbf>*+ex{_jsH2x4J5$8L0rC85qhBPsOIOldT zruj`l8v_|#E#s}1h1M9W&nCH4I}x8GKCCVHXy^vq1LG?p20lae%rT1SDSUQbDa1|T zTehN_*(ty)k(@^Y1TtM7+&3%yAO>SXpI4bDU^Qh#6E6ahT|B$>BVSY3RqJ-=U{5(p0$I z1~0B!dz2CpF>|hy3;W$P6E_PI>DHw0>>^$LM>H*z--)+r+;oLY+ph%vOsGyNT*ydg z-xhYelWc@_Qt7wVVLJbM`JsO7+osYwMkpn%-xkA&^?z!2_fySVb8IlVddOLI!y=s+ zagSH-(|#P~H|T>MJZ52w@6*O%b000we!Y`S+(z>y1G7AfLND?t`#Gm(mw8rz8U_4q zb|4Jzc;9lqx%u4DmsJjhj3Xc99S7qGP!9r#aGyvDNV4Sp$Uwpi!f2|8F^I~ay_*4` ztCjghdHLm+r_x?&54#bch~pQ5U$+Sa@p}R}NEAg^Atu;dEH9&k5|Y1nuB8Xv*i;4f z={Tkg_w~HwZ33o8ksNy<9*?#XC`1}jN-O&CZ%pMI2G&c|vSsv3?0@ISQ!ho6xY+6R z+&V*nUdZdp3bGh+93R_~m<02`yxX1Wniq#3sHjpv{zBBcydAv!(qcn)h5$coKvH(U zPpfBWEpS^C6Tv4uJp}{S4RGx$E#JZ~?{l;x**0HRfbP z(u~+^Q>z;GHPNazL?WvPIg1+8AzHQAm~}FAhigZHb!ORwqj<~~*JqE0M9`DX}QCZ_A%8Eq5)^cKhEN`{fW|qd1Z_ivCkoALi-n;^`BGW z#eb*5?rP^djv^tUoM8M)Wjr>4FAlO^T{mCyZJ_keZng%9A;W~1U!OBEL>ag(5#Mm! z1zZ@S4`5YGW#<9b1M&~RIRQt3T+t}xESqk34l_!B(o#WlHkivb8?hAIfuDwBGGQds z$sCSJ^)B=92PXp!Cx9)BW#D0nh?0JrTJ!ULjSvV_5O#JZ&zXoI!?_X2;qKFT6JRxW z>6Qx(Qq-PCbU>MLa=*<%Yu-Sw)yJ0b`TjefSrYn=CFyfUKo6lqG1OLDtpgIC27IDSesF&v!rmc<6jb%hz@>3uxMnFD*Io*2w48-6t zj;I!;?J}rkb-T3Xt1bCqd)?@1+U??Kn-oM`#cV#M_sw{sInkK@Nac#Va3)YKF$>5n zK%B`BDA8w(7Xq_RAo}P>H~$9POeJJ2><6MXHi?xmTqWlID zgoJ6lUp@f`DEGzU103=h!cI5x>W~HFy=%0C$lBbwcUA^V+(J6)qElbnRtmH>;2GFj z48PABL4(sMo|SqI)kf$OfI|wE`bYwU6$NLe3LbnmuN3xUzp`2cmN zwvulj8u~nEpv5O&06&lAiK{io0=S~LzJpI97$O@9CEVuJJUw0cZ-5O8an*f1nd}LN z7G7f9mBr`MFQy*oW84W$E%*u7+^X4p^!f}MZan68!@CLT6)6Akr5=kxr#AsIURYSj z^BEs)X2ZIN_k};#ARd2Wy~yvL_6z;SW1#u|e4(rGz*la3v$aN|gICQ>7os-Z25||2 zKge0_K*?cx*30z|b3(yR;^RpWeP1$1q)SJCEGWS0Z+oIuF{oKGhCJo|gitiaP`lT+ z;$xDjil+d!xu4(wC}wek{RD#zCASmXrzGEGG7?LrbR16-wSQb`X7HwGATb)pIbhd0 zB4Cc6?wRrNxtaeGwbR#(EW7ZuL^%X#29;Iucj0k(@V$=2pP5A$$b_f0;<0eOcmT zOLmCM2TdGOdheGBBp>>ZhUi-Y-=}o6(r*8ea0jdFEGoz@rr$ISk{m=n!oyq811#2* zBWncIxMbz`fur8M`d`B~l7Jvq50$WV>d&T#|2MIB{~KtJ|2I?Cj$H=#!#aKCM!jAB zquc)S+(X0I%-6j$Mj#RV8mBaBCOj@!dj(;|s=%X&3e54t6u+dp_J#%S%H`^nq?AB46t%>Ze zr2hN%f+x$rNfH*V_p}>cFBbZ9kubRP@1E3pTkQFkaa|CrbM)vg?Ka&4+zS6ve=pTy zRnI?7KY5e3b+VJ@dGbAN?a|$St*7rs0!GwoO*U9Yf+kFsZzp8oE%Nn12?$0 z_vEel_HyFd*P?%mn7^uwdi&QOlm5hogL?$AjkSgkA%f@R!h>WbkUQijdjlvZTg^^< zEO>SRAgryh`YPH=4cZY4k@k;b*W;-p37bU~Zn*M#8}n}d5n2#f_WrrZR|;h zahtgIxCmpKb78_GB`8dM)+oVGv(WlSAVRBPo!hi#`}nl>XoI9;<9|f?mkfR;97u)W z6m!x-Dy%}*#wav6YCzzKrR}>jhNEl8bi1a%!${kbD>^!Zs=7O|AFy*0N@`RnkR z7o*T9zv-e&?IUe&^O?&7g0)0$s)~DRivRdWZk>=~|1AmZ!!+KEQ#Hq3&|F6~FYHRF zWhTc?hiNNW5KJEHYiUEwL@q`7IFa3RAGJjlu1$AuQokPcv8+jiZ)|Q(XqDpgE1GK- z;|lusO1O6Jli_a-_v^_rq87qjMTPYI;E4NCy@Za@y?H#K@BFO)zEJW0-Tybo|9`xh zxAen0Dd7(_6Ot}DMDymOv`BmndHblGcl+gMSJT;}8vmSp>MF{>*wYBd%U$seXgn}HE8h;YjSdag>DlX z%ydc%o>6f3k14%((9W^!TlLBihPu|~K9e3LwPKJ*j3IH$FM6{`602!R(P@c zoYVktFm#(2BK1TzQn*b5FgED*D=;e2y*h1-)O3H)X_*md~HRu>R6L$lg70 zLgY$RGu`9GxKH<;vjZ9{KYlII#pvHtl(C7yKX*$PR&yEn6$3#Irh`7r4pOxm9x9`r zJw>I=vrC8FY}UI!%(S_Olysz5R|hXh$;SSN={EfSkrO2Bb;5=5s-^Z4ixS6y%3+?{ z2EDXKH;XN+6>Xmt;bS50Bq->ESp<^PvplxQK`qv7>0R9-B9ZhO4yGV7cnQc-3(Y+b z4o30xtcT##7+3>ZxMIXOE!=Y;7qiCy$t*q9}S&#c`XN6iSe31aEcbR?vqLybkaAy1bed%BGy zIUAFya`W*1{Cff|53)8r#v3mF88goP#Ycac^j4+4Q1ke@&hWjJ=VD80GOkFEqtCCU zvE*++7sv2-zy4r#yeU}SPa{AyZ33ob+@BLVxmfqWqlc*J84Z#inM)(X#b z=}B|SkNfu9ar2CVo41KY!+yiTQj3un$C;AyW?Qp8S*I`mpQhb-Y^q9ljTPs8M+JXcYI#%U}#}gYHyKI;Z z>q+-5D4CNUnCYQJH9G2)SyS>W)EL(^RY3OBs(Dy|E2h7q;g_Yv139yU=W?T0I6!>q}?iO{W_;N?tdHj#9|4y_qR zySlaTDsgzjmyz{eI$N#d>kMeg1@n-8|n}@vE<`^u$5${e5P( z_}AufT*!%~xu{T<_y|Mj$azvn@T|Agc~(MDC%&k9v01+Q*1wAsI9#CRo*AfgXs2ti zE+t>xghpQLXfhYZDyC{M(_)N{do>k?M0tw*34Oox|E;?4^zclfQAqcPN5`EvCNf_! zW(G=TSWZFqy=V2D)u-z%KOGBqxVRsCWtLx|MPQLvfA6l{6ffq`vKiExMYD~kuN6rr z=pkyav{66}j07Zr7nDhQ2Qj5|#G4^sCf52R=q>5@!;@R|Dwyy=O*%U=+QQ0amSKeX zg*N2-CP4GHIh#;PN$<(@s6^y>>V(GJ0=yRDPR9991Z&5P50p3sI^(G-0}tN~6>UPwX15+#L+*&efY)GpAKg5%W`rskL9$?48JcaZlpGKQ=LU9!L}*&9p?-v z6m@cGXmse*?HQZF=fs{vOvl3yP~~M!V|v&+h)2lgFX_OfZvMacf!_=LnJd65YLsFD z5q3RlAJev$bJ(v!H#3PP?P}b2X`A(M6qY=+A{{t$abswu#)-NyICqzgfVSREQKMaIU(vV8ud9nEXbA(`(9}t(@bwphE?m|3!;e3e@j+PxiWpEJ*~;mQQRF z0fZHepTYaoN1h&bUHtuBN2&t(19oj=c$;Ul(#iM?;rR~vJE{2dE9bTMEu{!rbp?;h zGz6kU*II0T04w)cPth~tMj@upvIDbUpZkFU9BP(Q=C0tJDn4*V@O4+LeyID1pFB1 z4+mqrya^r2+RPZmn#XTGAtrj4g7{0jK75iy^XXNyD5zd|>Au|YUbAH`ps(@=GMz-3 zzgjh0!3xldS`RK^n8c#!-yD5Ue6~(@VHcG=S#LvHNv|x~jV|&=Eo}NYqB*p6MKLgMN{FVZ!R`ELINv zTYv(=OiwKEE9VcLjP)YL}`+M*Pt<+ z{gn4a%0c=P?*0+0M{lgW+myC8e=~oh)Be4QH!ymVd(D8(q{r}LTv>(tx^~2D(s3U* zfI&w@O*;kGj!feVe4R9&1Z+Xg4lZx{`S}GJ6J8_IY{>HPFR8-a5;#g?X{sNtTKpHE zKzB++9OM$3FFvIN!1priChn#aXDzmlzBF7hPRl~MSJQN?a1$qw(AktoxG`be@%9B-?ir$gX!7c$BB@Oav7V9>NDSk(tIsWO;jJZIH~8}(wC5z zO7wZm%JTB#FoIGfi+c2O!rEP+sY*`nL^5eJYUQ!J;idM)3@nl z)Z?r{J5=mh(Eb(d;&_} zG%L&Y(BAn$`!+6SO{8cbiGayVyg!nlj6;CR2QWUcJ@?Bv{K%vjIpcTe-e_~HWRHu1 zqozqn+^t0Fkf~tq1&7)Q0c+m+gx2AWKHS~aQ-IzRHxBgOPs6Ujh(H$?0b*SRE1pZ9x z@{lokn<-*G+#!3HdCE%gTwvNDA3C_?g5b8@)*5Fkj#TVpJQ`lEnv4+!PxJ-9OHakw zY>pnOUNO{?ckT5)2D?eSw;GRfTX51%8b5;pcCeI!?_|_epeEidL}_i>6$~BNxxi=S zGaWIo0iS}sn0?1nC9Dwf;JB;ryUd6&k$GWXOHC5?Q7dm{crK0rdca?6xpaTlTw(DE zb|oTtdfh;a$%iG77k1(t~8=z9juvH+?g}2p}J)uM0 z2hC+!du7E3kqMrSRUnZ!jp*ZEZLs&AV5qdOMgs5ZYnB_{iMinTU=+xZ;=`rthcWX%5(o#g z6T^Egbj+B)FL!K?A3EUbGkE_1wjiL~)g}~VaSADENefd-NVX}4^nyV&k^LLFFs_*K zQ6oP2NGbPTlECr*NOn5A1tU&tFZV_T8sRsli!O!kTUwV!uU z*3s?j-oGc(S47{-{cL-sS4-aCJXXvu>Cm*+C4}9(;hYI?Xp1cg(ZbyXea&tor1!hn z4DKc)funK4SQcsvR)!``S;HPz%oMk5ibMG0OwN(nv9Ab$I0#kLIm!@g3pMuj^$lP- zlPNU+rN${IoD1aLS1z9w49N$N9g7WI8~c#NZ4JHnsf$MxW-CpIASZ~^_?o4Tm*2oI z@FgJ@3B;8~SYZRCL>ZVK4bvvE*(6x;*E6U=6@jS=cL{Rf%HCMDnxqmu^0V@Ym2;ZQlluy&tPdpP*&9d%h|gE5s8Xj{OJZc$trM(pd=Ub?mMV)BIQ zu|F~I>jhu5q=S1uB-~ZtCmZ{bB8ci{=He(q?|OZCOpG!W3}A+ra{FicE}!M(d#Z2D zB4+5JKIY|#3}qID**IMB*3czppL$!^94qt%|0Dz>8Z0*6`GoEQT+dUm;V^8R@xeMR z0I@O8)&QC?68gyf$_@b)(46K&(**b={r7&X`w}~NDIK+Cx#nAs)&b!~kgmP>6RjOO zv;>H?=!3CRk?~yg`8~mqMeRu_@~r&h7R1g-@7{f1*Sw=_QMEa+)3iu3Rm5;b8yW(M zUJSR_+(X-U(~dwdB)aD%xrg)kZ9UmY*FJOOt*{L=@%t^T)mJ#x7i(^>vRx6OD8NIn zsC9bkESPoXdKh%*h1ywZh5@#OFBW!2TWjB=%EgmvNS|KAiIM-upct2CClvR(0)ZaG zZ)AnaPfAbL$n~4B7CeThbx^=_m~3x$Fmw^jxE%Cc8>qH3WL^+!vm(BSih&iY!lA2A zN}=#)%)2KFI!Zw`AJ<%8sbEPwRHx{=GUTm+{rcDyX4mP^52V2hb-lPODTqw1BC6v7@ zPO?u;nAQXpr*Pv6xLZT~uw{SUCp8(W!f?8M#LnrD6(?I@GxYM-;s+u!FW_Klh*g(G z&spRPNj(J;Z&(1TK58RbP+%uPFK#2cB)NYGntkDm%2Y4%o_+k?kvA+KoK5OWIx`Sj z9SATT2mAVv$D5O($&mGRR>54!t=u|MEaY|KUs$p`uYv%1qdXksZL?%LVk)RCdI-Zq z382@MEXmuyzrW*!zV1`*?S#{E(Pw|$#f*j1#}9!j*NjM{fJT1;|2izZg;MNREQVh4v3YbW|6*s+6Ahu4n|m*d zJJ;+E)|7tybEoiYKq23^7yM1rkaX^GGT%wq^?R!(?9;C$XkS~b;4J4X+(4fVK{1Cay4BzxarB%{4pt4*UW{G#yT8dy4)BjZC7jID?r&c z+BW%D7oIBiL$%hX4q*naiF^M_{-bQu_E&GIp62*uCcD)=#QROwa-uqOenp(z$u$y! z`L1dmsnNW|-z(up#qhxtu^0GpAMzuwu^I?4ebNi>DNvms6h?}!a&>G z(8u6o6rX`s3pi(rJSq$`u#1m)FJDkqTpDeBa{R33!e8#{c*rFnFArLTB*T82Wr4_A z7@=vO>{=(~ zR{FYaU*`tjBMIdyRUULJvzgN6KP%vWo(m95kHKE~F#qv>pbu~=N-?4LmDZlD_5Dwy zS>f8K;G&x>kCoD?%URd0s5yHfaT1bR#?BJlg6;j2e#IdQ5u!3(9+zsR#HCyQlALDV05tPMK%$ zyPxWtePl`grYy|XYtEOhjb~9HP6}<>j3D!*N*ikb!(z!mS`T+=-;pa-G43{%gIkY& zcW&rSFlWd6&TuY{(MecJx3ZQh5Dv^%Q4`xoGZ6jDNdKV-={JHWf} zg#TxGY!atcOM*w%Ye(@nGo%{psaMhMdwLGV+tiaMwUzcrO*_fI@U#%bKB;Disq{!$ zbcC48Q~1?LtOq@@RVy4)xZ;0ohI*cZJCIUxe`EYsqw--!UgiYMui+@$;sv0JH?U@V#&r zWzZKTY_WlV+{U;6@6qJ<--dhbOr41^O#rWaSq!LbHo&}($hkHA!BwW zuwCSAhtOX~3EIW?`}1Mrg$E13l`AWBecn>cpv(`)-9C`8XuPj3gpl~qGBbeq;qp^4$`-o%D&A(6~ zSr4PE;@xV@m&&lgJ9TYt9-E%4t^GDMvyqb$0BZ1-O}Et9t(U1&Q7ImEM>hSYmwo6( z?~Jy*N^%}({Sk2Z8rc`_>aSNN1);nbAjuiC?sU?xo>B$??$U)DtWtxH5R!v=#Y8m-y_lc2eh%l&@^2MT_R zF6ZT>qWJ=HXrXnYgsW_CMQxe46}dh>yfI%DdBS8{{4!p>>%b?NG6Vr3?vaOtW`@LgaGTQQYAbPJjO5gh4l1{x1$@$Hhm$j^{s9|a5kX*FF} zC#Rg!_cU`p)lO5F{+-7#0!ZQ|buFjw1hw5Ee+T-dU|2HOX-k|xb>Pps)cGn1man91 zo*h&tW?tn_c|!V94+!_)hBKx$udwn8?+ID9eBRn>zEQBfoTKHF<5$hHvi2mLxX1jT z@c5kr|2_A(QQdW$x1utb#vdB;1$@t%&Bc}jyEhNtQ?0drJR+dJ`tu1-)1{J+N*bRJ z0z(@!C9g^+=o5<6ya%SlU@~Qfudhu&2il&#<511`Aq2PY_CBv8@N>Nm$G8!70kvWS zEnDAJ&lmN%njJSX`G*QEr(z`52Xi`f`OLck0)J zT%2NZXQfzCY7f6liX2pPiXGgnM8)ef#~VH#Y5e0eC-$HB=ze~-pC1&CEce3NFmutCC{rNt9$BZ<+@-0f4RMApP0$B$fyhwG$4+G%GSm446| z819g{Bn%&6P97C7f&}RNOc>T%xqdPqTuXgC`dl*|tNDzeg>33Ue zvY=YJNVq>u7T7gxW@ErT>(_r`1D#+qx4?mWCbvY;`I7_cqk%lhX!Mr?-6Cdg3e|YN zk+u3n{fE6WKu=bFh`zi>Akf3aX8Wj6@vZ6!N8?_UkHvG)@V?&ogAWi74*jCS!2#`= zV>Nwc4I5n5{luS0dap!UYr&;)X4nQ$W7#;}3Gg1Y=v6l@5Oggl zg6wd3dTxKx-y2*63T2241biCBJ9A!Kzqw-rJbH`j8R%FqR$L zx>ZTIfj>Ub)lTN2eb!pskG>QwVgJGlwKPXtyN-Gv8O2ZSTShdFD_1er;~3%^s) zobpB^^kVbhzsZ;~I52qjLVJQ>xrygN^Fa1zO)Ey0LaLSNsf$_+Rgql!Q;|U|DyJdZ z^sFo28Bp_uabGeHjhUdP;rwur+uFvzAp9&;3DP@0+|Q)F>$KijCm;u zSz_jK!L2L$@8z+A_Ua`M-jYI&g%{zj>yFOHtGVcSiAn@OmFQ&@45HnaQ_U_od8C=c z7lGURu6XFn^#hz^n3D9Ba(x?L8Sjm6jSo>*%5 zRV{*mPS<<|)x5}4Uns%z*eHnTtN3Lp3(qlC5F0YwOhB}e3E8%M z6l}r}#gAtUC`!iaHhS4_Jf^GEy=8+TLFR~IEK=DBfTjjqFZ=wku1PuLmdh7JsJ$-{ zA!rb6-E=zp1Jkt06tNBuFMz;v#}a>`+gz6hs%M&iS!e}Sy2|!bH|Mqs>gBK zL*6TfqU=kA4_bCnu^to?v7#;;Ya3UThCg`4i)Jg(W(qH?Pl7!<5Vc)=i5zTm8J z$Y`H;t%nSrW?GZzeX?Hr{`K@Iu?ZAc1^o#xHd@|Ht5Pss*9Wv;xi@?>@InFnFC}&y znMc3#J(vUmkmZ$@RcopcK&P=g#5hQF`EVYVs@#-ht@O&)sY2L*+nDu4>dmu@$|v|r zewi{3>Ws>tt>dAFPBul+`vamp>zl6;W#FDuKkIVIg46vK{5afkjk+fp2#q@JP-jgd zGOG*(4^+qaqSzVRYXY}x$46vE%a<2HU_D>WBlVHJxKHQ*i%jJ&ZJn>6FA*Nd=V%W@ zXwG+8Z%_~Dz9|lXs40SvO|8bIVI|0@>NqW}f`$ekqMCj zX$0b_V-yv7K($j?l;nzW|EpjC7*b?qpbK$+Mm1KUs#4H1(G4x6dXnPR_?x#Nf1i_& z3+}QX?D9hhI!|!9YU;1i&TAQbe*vHJKKLdg*jym$uX;QAI?6?G;6p`Ke1&2)(tBOO zvWjB5dAp|5%;Aa|{+D3y;|GY;%zn+OBqA~y-(ns12pkNuL3iIOfLUl4wo|>hEW`Qt zV|P}y0QaDZ{a);KLJH2P(9tf0*kZ#A#q|gD_SKkM4-^zPfSNDlafERBAbnd6Ac%mY zj}3#U#+JuBsFZ>1m@nsN9rf}RX9!$a-@yGYS=o?_W*2xNQfdPzT2vJmv!naWM2!J{ zVMmF)+;2!|v!=bo>pPK@1{hp_4*oKP`ud5z{Gt-gJ89QWF9@6)K;P5f@s}_D_aac7 zvhN~8W#dwkl{HZ5+_4;EgNz#mtK{=~T&3H9GwLi30+Vf9*LTa8HWg7aKfam8NMd!_H80z3T<163zwTgrmg;h2mdCIx43zd}=m2*tdMU~w{pptX5#H&o}u|801xTrXd??d&G(Az~xKt{Qi03`KC z?poT~Uaa|@fSl~i>;p6@qBCDC33*ZZCv84b>l2(A)3yceF7@xm^$Tvss_T^jHR$o2 zH;G1X@9tCQx~BqF1r(4^I@g|yU;oQZFY&w%dXFChov|QnFVZw-Ace9V-%%}@tMVO~_$CNx zo^M+}+75)aHS9SyCDn*lj-oT#_-_6HbPBSzJPsPj>cn}cG|4p2?yEiMj`$Y=UwaxD zFRtpViVHM_nFl$R`I!+231fC$KT^d3PrEN`0+rVE>br5whm@9pM-zYm4g~#*`7=-e zkSJIt^qjqc`lNr-rY%~45r0+J=RbxL$iTTN0hcs1*YMvrra>oI>4FheP2nI>Czbt` zN{bN>ZE_zZc&4JAP;9a-31Ap`D~Zws98GQaEi<)0+lEn2(|pUeAI}vPCn&Mf12`G) z)$Di=ZTbRB-WgPlH|0_KuA>cbbOn@vwC?x-5akpV74=W;ln^3_-q~L1{=jLGwimWQ zLF?2@N+2-HrnB|QT1Rj?M$ufpvo@(`V9z+<=pOvFz~i$K!`51kEvL!3O~(P3g;QW0 z+;0dpP_gQNB!Sd`Cl{WJBJgB9&&MGr5p_v_hCy-7Krou>$A(g`S9XINNKV- z@g^C!Ubbq=F#&$;%dY(^=3;}~Zv{U3f(yUU8M4=Jv}r-wx90Es58LfvsK9oF|8LTg z|1Ue;`Tyn!(n~_vzu`pbT|nO-Pb(UKgy7mz! zSK^};tx6B zU&~;Nz`EKP=6I};L#Qzywo2J^;lVUb#jOHbUHsH;<7TT06*Voh7?u&E+?N9y^tFN zQ62WQi}C_7IgrF`a8UzZ-!-pmB7c?DTgvG<0XhrFg4dCvrqy1SfpSkeyiC0hlv7dt zGv{z`m)X7RukNO$xlX%}O-&)tAb>(}0YF-K9c>2U8%abk9;=PHkkuGrp$hXvmzeh! zaVioJfS3c4!&tdQt`5O_?3Du5)0XuJ2%MPe#{EmF_G&lfr zwru~|V2jd0BQm&;sq*|rkbrc=}M`J+q^B-~A-G3(;~zto)@Dps?Wu1H#`8fYqn~5T?+6jPk~}%m|3ZNDf=% zx+Bq*A=@_t2jGan%kxw&Q#1M%Bd%JFbKhK2c%U|-*0V#X3)K2Jod2ugYxG}08Ngx{ zV`FL;VnL7gsehqD(dNxia_$X)^xAh^*@r-Q!NBu?01&hX6%rBx4`!Yc3T?^7p zZc|^R>$N}Xokp*^C{(H4J)OzKIXQ&#+a0WygnK8tonJ#i%8dp{jGr^+T`}vcy zn5onYU`T`?su_Sgp$gTH&vz>z$7^9?{+WVg^5IuTgrS%gfL#a9go7_ebs+{6N~*gJ z(sUKeBJ__cr<}U$P6<>D*94YR=I1QEWEZxkfm)3Dg~D->#n}0U>H@XftS@!<8YQ>c zKqUZf;S%s#K;II~n6}btjeW77&rI(E3}u65Z{fTXLQ=I^4yR%y;U1ZdIXb@iq(cIJ zf|~UwgfS#rb6HYfC^R<`&C;HV^oodxxGeDvls=9)QP6geRh2{}rI1}21`W0`C3z}G zx0>uucX>1DA)ZZvf=r2;D{60_q7?AQyKv2!I6qJO(G6nuZ35>Oc?55-{z}qJa0m8 z^e5X+cL8xlN0|5x##Lh@v-f=qpxra0YOjr+Tu>K20}K(tR!KFc-+FiaY$iGtb>v!N zDb#HL`!hq}aEL#xMi&_SsTLaBC%W)qg{NRAFeOPoS(%(}T zuB;%?5iYXHKUSqnd#rQwO54@7d<%dO0!Nt8G;Bzt_q(8Fi3QaJUMf|Of7NMdf3xl{ zVW|iDE5?B=b#C2tOuZ?embX&erZNZABvL+*S7mjf>tQd?L1f44S$0u6hL(60gvr1S zXL_E3XMYDklJ)@n%POZNv=N=I^Ys?rv|A>qqMw)7Vwi4Sqh`pa5upZmhEBz;kkyb~ zxe5yRQq+h&?#np>qock`yC_WF0EDGj#mT{m%>%bA%&gjmL)=w0$aJ-ugzE`ed&isqtF81ma z+lZ%0wz1VN@KV7V_(7yWe9=VsBGKwf%-LJr@-Ya~%g0zjV+&kAj|k_w>dYc>g9!jJ z>!LPs9%}^x{bKPM$*qc4K`38)W}p#T-j;Rb0XYDBBJCGguiBulVf?GF@DI0N0MImU z`R!V#dmU8djpT%($OObNWM8$fejNWx3ou)2mR?m*a-2wYG?5wCvtRq<%BgJG`3Jz^ zAQU!eZU%)rJ2|n^DD^qik|b~%`Za> z)*q@5m}VPnn|P_N>#tjcUI(sxY8OpLd*ws?|M&d2J@+1OjfWA!ZFu|7AiFV(-&X|C z-N76ai;WeaikXzMCIk$<+slj2$K(aoS6M?xLwbXM{8p&gjZEH15=t+(MCRc_U=SLJ zF0ZxbNpwUz_ln~|pte5rfprLXwLVzjIFul#^^!^x=kx_ALsOZ0V?QkY*s^mUmZZrw zQ+)J;8x`5CUR03FUWsrYwK-~UMjQ(0j#d5aap}E2Vr|*y&z;aR0vv65+6t)vD405= z*7>eM7l$p9aM6)3#w(srYU$HoF7cnsdtMpXl{}}_O+Q@2xPi^;>Z`aDlInHF?J^d^ zGSNu$2~oqrUKz2MS6O68zl+$BO)r;NmMa(Yv8n5)t3mAEWr{hSYR3!Qo3Na5JD65< zeT@~~o=Ss8%pgj~EKv2X3e-+6y;EjRibO8|<{JNF{rhgS0}Fl{$+;_W&G*&KxDxSi zmOdvFGazVjETSwzu8LHyC<=`O(#>x#u6HW-$^!9#O`6ZUrW4;wtLj>z3-YiSu3vvn z^sa{a1#!^i`_4xRUnJ#>zHUo)To3k}4ZyIxODT@|>ecD<*M48VGLRATn5#I@Kk!dL z3+ZCMp*6ZgdL8q@N?!iJ(RmeWdNen`pzhPA<%&&{ho77}AY+|I3XG-(0Sdypoy{K) zKiekp&e-2fNo|?lr(V<*$Dq6XbuF6>i9!6(w=uj$k3L|`E3|3XOIS3IFnoYdU>(%9 zsND3jizvv*vLvTLM6W$eO3(NHwG^&h(4@Dg@gV(ff7^SrwC`ItD1_8e^SiNSN8v0B z%roFQL(ieIL{cQ80@JRn(A{wuL~x<71g+Yvm!0_oROjC?AX#zeX$gnc2dnzMwjB~Ug18c&T`GgkqGR z_;M-hWvzNeYPOWvc6FYUub+9}*84dP*(}(r0e!AKomFsi>R4XQwdT7vBeK3yiDrSG z?Z!bI(hh`{L~JuT!ef@xj>zwwOR1lnp{ItZgtm6lMLR6cnOn8h4+gXXg^&j;10;QhO^b27IVDcCF}?jaoJAtJhP=lk(Ig`+1~E<_m`$k@;%DI!W+yZ zeGQKwY{Sg`b%)Q($yYS@^lsOva#7uc4BQp^aPL6Ly7rxq)0-*TMYrGTR@;-+1Re7; zAAi-g?|4UqRO)(IUhnW(QbK*M^xA*Qy5ZR5uHdypHgNjWGSq9Gz!Q9brjt_saz`6- zz*dAoQmi^+qpsmB$j!3!fsOF=2A_KO1~Lmr(565e$0EeRFQ0a%!AYckxr;J7b{g$R z49o6}MMW~}ZhpYY^rj*F=oz`pfZeRH}5 zw&HOg=9Pf;)TvC`P{l;mN-o&&$!>Z(tsT9wNCxS^9Iw?8)RN7J-EGN%ZvS#Dc$*Q8 zY9YGsMWf$)Uq)o!0Y~HD#PN{Q>pbfFd*nt${J|0NjkLTrwQDz;Mo0?er*k(a);-b} z8eFf*xzC*YCZa2`MzUMjbs|rNif~c3Qvim)EgZJ>>xEZp^R|Ts^iVYH4S*JriJFFnR>BzFg z;jtK5J+DMfum{cptOT?7HN~7sHiSKo-GyiQn`R?vEt95clhpI$#iWW291E*}H8o0R zTAs{Gd&OTl(s04f3^n(K(ZdnHgP#N0?s&SQ0ufn7*puzkw`B5-+&n$SQj#*Epdulj z+{%7uWJ<&hCFFDEF=00HwCyCKB5cnmvmr@@cgKxO0-~Er<~pTY5L%mXpi38iw`pLv zEiW&f$OpXel+tPL=(RMQM8C6E%!4|2Y^{qG?M9u;WdiT=2Gr=eeo)hZ=k;TW~mhRg04r^z-V@F&zD0S892;K+lnq!I!ugMwn;v*B35gb-7y=dCyT9o(oB_ zF15gnQvcEPNAOG7i>$S-75ATS$B*mj$DelR7@Pd0)c^@rOTsXGii~w~D*NB;&^k9^ zWP^5BELe~~nplk7Y}5maHk#EGv>ng!y#y-s%fT~Y9k7-l+`YQo@3!fcgp>*3uW38o z&N1x8watsC!oaD;BgkitK1ojQ2*Ue{<4fqK1%Bd(*Z=#=9RFsL3ash4nY6}sE`H_C z&XDNB9H+I!Iejmu`LJu*xesan*6+>6+q+Ar4@w$Q4@~=$!gxWjjp^uJYrtHXkB06J z^CsYu8ZHoLSg4d)<@ETgY7In|gH^0-ioL&l zZqet<;L^Z1{`xe@d<&)Cta~F-K^MCX*PwOp&n0@0E)M*S3sw?cX19^LL#-MYp9C?I z_&ufn!NHy0&6aOHFLjUBrx5>L4oybK=jG=QdE_X@jg-GuEKa-q5~bsu^DN4 zjl`dZtCYk-P)Ov|MlY`<7gVRwFI-Z~2VHv8o72N&7Gpl9T*2z#U436F@*2+P|N0VV zc{G$Y?9z5pbl6=Zw>8G0W*vpaR`kxs1xy`-KnhKY%Zhi4E0 z6(@4;h>YLPz<+7}IbdBdjc-mQgtzQq=$f!y&u&6?@WhSbsO&0f?Ky6e^X9His+!Zo zB&1mpRY4cuaoMKvUMg~aTiVOciGOgSL9WKLCw_(hx8fUw>de!`#Yle(XkD})u!9&J zEo`vh#OV~LRu+xw*EAu1?H;FnhOFYi)ZdeMhs>AP?0I&X$impWa1K{L+k|CBFWTAS zE%l#Lip^+-c8CdG%SEw1#~~iC^CrnTv%y;;b#2*sEAsT=448ZOuJdd7#({hsbzh#q zNUv*hE;n*{?`-sW87`CW$=tY#&twu^ndUKeZOCmt z9UtAKEN9|_0gI8RPAfxkEh$#OraIn!GwBVijyir2b2wc~lt;Tn#@FEH@UHlmc!WU0 zzG44Cm{jcd^-pKM$}B?02;dK-luhIGZl7e*%r`_8k1N^sKuifs8{M3b#q||eCp4oQ zd==vvq0JRgw~+Q3TOhq#sK1#`i^(XRv~Afnj4j#lTWVB-T?; z=_X60abdTarnkY#_drs2MMES+LuQRN%&}0~=cIbV zrHr#^@h1UNHj{+9sNi3+Hh|cZ%yGhe(qSD`rBp@hc+s9Q>2+}XO#&zNeH}YPnc0wE zsX}H1IdbXYsY0$shg-W_KkA$s>{ z^;N*m!^G|P&waAmDv{~BofhGdh>56A5GVU{tnH<{BaF|q2jL#sMXcN`^`QdF!mhT% zkm6*ngzkRyQfzq#i>LyHp2xQsA$f(C{O&15?!xsuBlEmZK#QX~^Y6p*c7AMXkk+<3 zx{5uxpThMJnQcZ@vq(b8&F!1VchDMKE1J9r`o36g)B3)Euys7JvTl$b+}?N>sZtp(il{;v5JHN<%CWsl@J5HQ|h>( zLM4ZzC|Qnj8uEXTPn^kUQgfCF;~YM z9wqA=o|Ma|P!E%HoEKu%EvsM4`0(8P#6h*wgK;*u>zA&~;mD$bk^5o2oU%jBHhi3o zTc!B$zF^V1hOPd))q3G7bunSSmzz?em*o%_|8Ib#8=@O_*(D&!N%v9K@E!LV$Zd`o){PB9RvNSVcd|_;J{qn0mnFmnm znLcAW3l}s8^z6OSER@aw7I$Gcb>A11l$EN)CfNKbueFC z%j;a+xH~pqENw7IG|rM;)S+8zal@2tD%|}%zjI9n{Z9HO>#q0sZqoTc{a< z=SF3yGe12_q++(7qKSk^H1;2QS`k|bB6BHd#<@exN`9138+l1<#|MDm5Kz)LXOoYM zhh@g>1(=>=8$m-w+I(yglra$bU(?Sl94tkpf0)X^!KQt$lPe^G2w;xtbY&^%hPmbW z)!g&<+yjU9YX#9gN`i@mm;379(THy&0Lxy(4Y{l55Y9N=3$&nqbsEO~Z{p40 z=W3#l=PFC?XMpUB=-)Nc9_>{+Gij+4YZ8|Y2dVYkfyVyAkCSl$MxPXWR^xMcimYNR zem(Zag5^_eR8JzI>j@KMx}Oai5%k>umPN41k$2Ysm1e|5z3r>bv`h2OzxxtO9s@HT zV#UX}-7f@TmgB39hM4(OkP)WAn_9D8C7i9rH~*q5$kJPU#Dxl6FOdby(`|&3yJmXT zqY|FEm&ZN27cA&`=w5SnOri4o7R^LfQ3E@D)Z!zP@YDo+8Et{hBzyJwX|!q1*!+_7 zVm#0vKBB?_hufxyRL^*@?0fk?-xB{rQ>saPnH_fN9LAkI%EiTe$H3QUC9!4y!=Aoo zz6(Tw$x+d_{jaz?a@-!mNhRcR+rtS7)I?I^1k+!SMc$7P*VIfD<#X%4gRrl6LBO;Y zBNz9rU+`B%fR;Q4_~%1^37BjEZA@`%nJmV+a`hEs9u$}XSXa5=-@LQZ{KAs{xuA|9 z3X#JtWod0G^X(Oa!-3Yad-$QB82|1TSG@OqLHy)fWs%Hb!qH5uCiI}l|0|gX_6N`- z4(esvE57%)kIav5;xdD@MF}=9=N|s<1HgM14uFc7>P@Bq^;U`} z;={Esl_1`DKfFhO_B7)ZIEt{%F!fY0!=HV@ocDsq=+B;gap@EPr|D6WfVsf)s>^B8 z|M|gX-%r8^CdAZ{spig~8O*ma@>(`x+HJJf><_PZnM+c7{9HurIAg!5SWD-ywZtsF*GZ>PGN~>+zxQnBCVGqhw{Xphef-~V#Pu!~ z`Et?t`}?9C{p;Uvpu&EO!9hRE#Q4k2^t$Zs{IAvW*PSYPF)(8x%^s(C4O{L{-zm|2 z+gX9ARW|Z(i=AioIciCs9EtdM=sf8aP^iVY$+_{&wk#ZgicY>0zG7v zJ|D4eoKz-aW`N@Q;d3a3$xPDn2rX&Kg?m&|MU`#X-uBybeV#|(*}G4R|I z(E6+I*RM!P{{9j8qxkb|#0FHfo#u>cq`bSLX$D6LLtuB?f5t{qwXG{d_bb_$b8b@u z%di@836K>KwaW`?Hk-z=$3eToYu_x3l&W*+xJ;1MaCPlWR?T-AcYn@Q{sBjCHe#I~ zA9MlW6>6Mvw+WE^&gn%jBNLxKt|DbBI02fu7~~!-6SpTyYm@Ws{3d$B!p&r?86i~d z$zgRNMcA(bitCsh4Tl)-*pBEz4u(X&j{r>OpE%w#zl&!&KUz0CoPvFnfv<VcIs0Y@_jN0f(4$bnL z{#b;aonaIsJ&&<4*U`x4bT!ntv|(hc13RM=ckg4f9*rYC>gU&4X0W<~?@)`L2m>8^ z_(^xzIXJq;%Z!zF_#EX0&Xa8$V_nHtBqym&j7RiaG_%f=AT{u&HQ9ly9Fq`A&vnI=BD{d`+lZEwAIxhFMR<4dX z6GHV}sU*XwQ;EAbJ#M|ShMYmIs#YA%;zpyMq?fI~h1b6Iwvz&*t~W#ZV5|&^IGax!*ZL z*e%u_s#j+S*VFC!iJOC16rQ9CYFjkId3>L`+3O3v|wlz2Z$Q%!SF)pU4ad}ap)SxO}{TFH-O*&`h zMN${$nFTUc(5yax!&GPf^Q-Qf1q* zfXaOvdr|=80Wb~@qVBK7blppHLB5*~@)DQx`+uun^e0^S`_y?O0bvQTh4%S8Fcf3A z^DKfsDVQm$#M$f)kwy+lKTPbkoH(XZ(z0ABM!I8xE27~ zVfS61x4FDbWzR{v?fB;h99>w0f~`ItiJujYy?3MJ zm{fJwP=J_9Zhsu)RZZji@_!oo@wElMzqA1VxcDMIqmHR+5FHAix9H5QQb5I0^$9^1 zxd+dY>>%w)Rl66?`}wK~2V0(Oy*eL*vRtwQD!?iBo#c6EkPivzj(=#YXJQw&>bC{! z5dG2W0B-xt7Qk`+=wmzQzlZuO-WR1@%LcJDw@gz1*-wb?GKgEi8%hY(rT(Yk{&B%j z4}e`EJ9vGFOX=GGjP3vbBm8go_8_TC8f`JEBrP|v5K9q|X9?^lY`;)Y$+gAuXwz3PVB zH+)`hPeAAPn(!YEjn7lq$xy$z${T@95bGk5`SJ=l#QI3=jC%9z3!|jSzL^fsH`AZ% z%cnOP`&T{*6qwB(nnL1>kY^O1>i55QQzzR^hcdsRnxRNl^YQD*p5om3@>^GjEgQj( z0l(2c<)!7ghMFSm!im?5kT#uOQ!C-#oPKJ2H&-P^j^kdzG-YOu}i_ zioF-}v);|Kk#QEFRxeNFy8SPLI>dnxD%^yl&|G_?i7>^-8f((g=KjmaRr1kY!~FkD ziM|AiiwEvM2OHH4A& zQg@|%U@3KPan{&LCR(W%`i@}VI2c_g=veX53yME803MC9H{*(UL8tiSA}D3jOn(eB zk)HHMHPr!ww3bA&zwN90rkyd?>waNr0}grv6OHN-`(wCl>C*C`;db_E%&WS}qBbGI zur|3}_1R3sH5g4G5g~g^Kr7XI=Uw}yA0t0)CskC~()_a5-={4Lzwc=0o6G54Kr=g$ z3z7nV+pYIw)jM*%Ml|gkjT&6|&+9h^ny4IYDFfN@qilfewJXdDIpV);G`dHkq&?ka zEi6w_aZ_sYfPGUeyJi4vg{I)PUtBnHGu9;?YNC-mT@%pR$G=7qXS6oTXS7{!@-4)v zrBo=b>r_l9IQWL7VLzeNm-U(D`O$FbTsGRbU_mtf^!nxlTrwsh3}yLq?1PyDTwW=# z(Ri4n{kZ}<5t>$1%XOpkocvP|8xN8MEDUMzLw6~(`Yo>SH+Ti<1?IViw#*?S7e!*X zsbNCwg09k+JFVii3p1m2%5R+~*x#u`8lSy7&kVDt^y+0iG;)zh>LE37P4TYf3g($ON4`~5MH^|HN4(v_pe=%I>s4`KUO1-tXy<+Txh87v}yZp8`>shHrPQjAAao& zd8$E~81PoA!7n&Bye(?;G3Ro4B>!!n)_Ej;tE!l0=CF%&{D}Q|1vh7*vUt|5Y=mP6 zzvqI^q_ekz=ka(f*>*2i=y7}c)8qI38iGd}Z*;gHCo+Q zs$!@*O!IIlm%VK}`qR@zq@#TfosM#ZI#5V$hT+^|#5o-ly3ll$wB0gtN zEsKDN`4p)I+&mW;D0wYkY0+3z+%xFWzdzgOz~VLq#x)fw=oAT}=f!D;5AD9ZV)?Tr zUBsr3`rf`pLeUAR3ifZI=o5vdPh#UHkbG{ z-Q5K`!>Ro7*lX?ptTkwecs&}jQzvYb^o;ypc}ke?4Xm7jDvJ?@+ce(P1ET1SXWQ#( zVr$)xFN#){MWDr=Sh9bDWx+b<^+2mm-7qjRIg!`ZpZB}}YlD)JmSd)L=%rUv1$THyaeSDQCcEw<;biSPVuPjT(`zWKmlWDrXME84R zGagSQn@R4JFeOMvB@sT@dp2kq4Y3fh8i~jo9pSY*iKR&-d^vf3cKf!YNo#yqm!Yv} zTflvv2qNq(hka;8BKQa4NUsEv3Yu0Alt(Y~N%PwmjD$kdo7uy>^)eerO6=>62Vq7N zaw0#$Gh}Jrj#xROr=)5=YGLgJPukeTChq4_O}oTH*`w?(C;}a0x+V5>H$v*6U&CtO z*CquVIYGxxQzpC?EMHHiT&G8&COXNy3}m?G~%|*lTzzhmP;#POlImJnEK} zlKt-x#v4qNK(75*Zen6TTECrS`R;FjKKyuykf`1Vd=P=;xy}0a*?CN6mk)K|Zw1ko zKy%wY^h>cBkp~KEGvBp3PsI z++XtNpUSbt9~I69aaw3M-BhRIp|`JR z*wy9Kg%h+16RQzgd+)zWKzrYaf;_JOJ-Qy=zOpD~&=x9v_`I1o252`X{kd+p z`4s=rl$}NLB#KDw+!0U4!}Y~tGfSE6!A;RAvd-&RgfAj0m1^-d??*7PUI(XBKf6&f z&41IxYPvJzzS-|hM(7vh;c}_lJF#o3waB8h2;;SpQ_kaMsV|2kjGSvhBX8*)oL7`m z+}E6}k5SGw;2lD{mbm+6hv|%2K;=HU(hGtYICX~2Kt@p8fXj@-gDWo0PFnO{elZwE`3X?i z?Lgtb8;pm~F}-$>P2VTutLO4qz_W+%zj$poI=nfbS9d$Q4@ogh8+yeVQcCsA{4;~F z|Bk} z)%Wq2ks*o~a@~BHF6yPyRJY7MQXx+*)XFl`3^Wea@w&f@lZvZqIvUf?Ycx&cb3D@- z=2iX*gq~Kq&1ePD42OxQc7OmTAh-r-zPE$wy5`EL)jRgM)tVMpR@4XLv+buW7quDE z4fS9zJQ1p&b0ynuOx}_l4w*o(*d27J8=av|o}5JxCD@%%BnS@1$xmlzffT-2;-``M z&~NHPZo#9)(M|1~+ZkB)rX_)2KsA-I73B74OlIM0KT#{vhVajyN-4t24py;HDv<;$ zTq|y&X`JNC)T0?oC$*bJys}SM+bGHoGr1lPU%w%r99oa$r}BE-f^uD+&k5!(a{@5h z8E1;ne0gHbUWwoR?Gy>@#5voBeLy^RX1ZH&K8{C(YcxF zEEG+KOgVa9kMA{W2Qq7pOjH|hhP4m9>Y-PLZ3covaxaUpfGXEtyfq7PUA?elt==P3 z!h%0+G6?`&o`|mTBRij%@0Rk_jl&Wp1!G3NI?_`M2Y1}v0M*4)bcO))E$&zE*vS(R zN7nQ~x*_Vvb*Af&SW2+yZ6T`5%;(8+hqt;+o2VzPfC6uY4BYz^of}oO->)BE8S5}2 z54o5%W9|~)Xo$m!*TM9S>YTo1|6#Q;=7`A0K4~)_mY)Fn0yXh3_QPt|zSW$5qeP?3 z+90Hpcj`fSRUTuT5bi6tY`V{4zJAj1I(hdEP&$Ag>a${BoFQWuYY>F`bFs-g3NX`< zA75U^?wYN4B+0$#(O?A4Xq3@{R*O$cvy62$jpDsyec6fDJ8P$lZK8BuaKU$v=rmph zF0p`2@ShVsNYU?M!lEzHw#^CXUx;N3rnn1xE>I6BOH^!iq!;S;-^n=2z)xnrQ2rC` z4)Ft-W7vFG=B1$+sP+;1PSJw0@9una*Ec|Yv^hli#28JiIfN#1h=9NbD~3n=mHqUZ zn9;9Qnfz1OsFK;{WDGdCii|g&K2|W>{nd&VX1Z&LYqc?E+>~) zVxb6mBa(AV-yd#u3~`M&*GqATWgw7IIzoSa^~rL-(hD_V<{*kZ?lfgEo%-q^J)^T8 zxvSZ>NLHur#2I#VX~fp%1+`qL4elkv9cY@v~(f z_u@grX-Z6j{2eLB3)E!@%-E-KewCMV0Wz=ayVPkVR(PTuUGL&_2k1fBJ|)o^HVP9x zR8SrtcJK1=FE8;KJ}*`o5YEktE|R&20*v%CfPiHzUbjeQsntQ^xBOZCVn_z(Sze{? zsU}a8z{Oc)Fe)=b8+E)nTXpL$j(c9g=ZzKjVfTWxnSL)d7a1VoI>GZV5VRWQwTg}o z)R*F{zefl29~bJ(GpK)hrmi&EF8c%rVPM14s>O@r(E{>BNISjN)&>uHUg(C5Bkj>{kHedzc6GPKoHRCUig=mJQ^UXU zzW`GIa3OVsaLO*QUIE+x zJU}Y0H$o=KFJDN1{|RxthXARF!A>0i`p+KwKOaFHgwaG8@Z!B^tkf2A!p#J%n-(3E z%)(+uv*r1ave=!+Z9oUg08cqiVRyM-@@>j^EYqQ1wd-kX{BX_qeLrie$JM6pY~2`5 zbMc4-MY1hnN6>Kb+s-Py@*ell#?DO-%w|M0hfXlEDRt9mcxI_E#84~sdX+!jn!s3EqMYR6o&#$ zn^m*B77n}*C#STyBkaMrJ>#<)Gkj1^es;&=hVP!cji zq8JkaAaTm$rijm^uCz~7i|dU2h{wXWB_2^B1G3Q_kGOc{T0(|mf=Ik~JZLj^!5Mej zGVUypi6s`MCdL}mM*ec&OX1SXA-T@tSYMeBmsJ!LOne~hs(5Tll5aEkJeb*N0`W-M zTAQfQ3K!R`+BP|hwCVT`MBV{z#So<((f_&JMm~0yw$=oRf@Lj{*!Wi3z@E0rhnMZ< zzG!v9&(oG10Ckz9Nu|5GFR3W;KhC4@YyysREV+99EdbG-*H(q{A6^F3PVT)eC?CXq zsjSN%L!2XjQ}F0p`C#~HbAe;!qML|E-*IT%Y!PTSZb-_v}C2QxIMEzvqV-@;*tFz5O22Q{`&LDZwG1?JKYI$69eV4@)Uy? zV?WGdOPWf+k&ZvkVsqK(5l%5XWpAhm1zf*7*k@<$Q*sULe#QDbnsV8&4- z&kR;`Z!I2KM7?8|hKH1nI>X$Ra5N)7xgeNdUZ*nw?KDL9sd4o<2EW6YQ`EpL_e6FE z-%+jI2*?ysqc4sw=fzO5rqqHmp%p>z%Y|GO>_R-_Ki|-?KQ5&-)hGyq1k}*xN z5^FTwiW>7MD&&k`R^5T|Sdg1fcmr-x7OUsil z)FN8-=m>ch^Sv+*58|b$+E?e$LTdn$)@9pXC(eRM)$|`L7{AG-IpfU)KlZ ziRh8@Yo@$;A&W2c6J*PAfTu*7Ew_-9RVGJN?_J7VU!)Ruw-zF*WQG_nVmy28x-k!hdtsak4&D0F-I zSU9_(q+2Am3Fbexm!&$lEJG?Q{ccd=4RLAld11;7t^#Sp54x{uK{a#^ARIGBjVZlX z4=iy^S)Nqo7R7e6?=wgJls22lw9$>KRZFQ;YDsz+mjzOMBAB-!Ljm9EPt4AFRg+&i z_FWh(!uX}w^#YL~ zIT>~Ia$QVu;j12btZ*Wxf++PKX?XLx4oZO0B(+=hhZTKsh*R>GYAwZvD@6^X5Qomk zuk+#DG_<7-lhSA{S9)W^&PfQ6e13<=OzD? z{W7ClORg{^ixg+|U6F_yNuB)7Pb)lHY7tCP!{61S?)Mk|r3DbRWHM&EvC+PyZ*?=9 z?bXKfh3r-BWae}hyuBzTS8{LufX`-1MVX zy`$M~|Nrs3?z)Q(qo`7pq9k;vQHtKRN=VHlB=#19TD6thiW))fJu<{dV$)hJY7_|) zquQc`YHJmxzufQd_xn5F&-c9V|6V8ObzZM?UU^>E^}HU}V^D3L#dJt{a9esBNHT)b zyOq$`$ecT~t5&T}g^T+3!*65<**w8Cjgz}cUB@Ui`76x{O`bjNQ5S>hw5i>xKRWe7 zCit0yQf|h1z1=Bz!SieeSHX3^)OU)ku>mV0zWOtUr#41c_m&0PwiuaXyXd0P)v%YV z{)Ts!16DZcZQ6d}Z^%=kb5_d#x))Hm&78EQ{(86e&mws>^Y8LXeEztkF5VD~*5x;m zcJwTRI>Qw(GDdvSN?;+f4B~iUuZiM_>ww5fXWZnUbhOHY4e0rrO|h!=RY z2^*O8uVuThUzxO#)mJ<8DF0l5K1`)p!!@?5$_*6){fw82vPMo^AvVsQwMl6Lrmw;ma!e-9ufZf+n2Y3of+c^~yB-<`(uSiv(xS1ugzHqzcqJ zWq)*xq*)5Q_4}%L7NmnyuLefpdgl%culh@-^R=zHGPKM0S^^wk6Mc;~mtD=sd4c2~ zkj!Q>t}#0&yTSJ)NKG=Nu;siYk4vZTbMz|!eRIzJ7=^!zK60|vXC^y&9#3T77x_0D z?*IBd>y=kJ`8!B7@^}*(#7<$8Mv1<@eSiMCYo)f0&_C^j+sJ8O5Iuv(pP5|T)-0m; z2a;uCPzQJpf}~|M*KyYBA-Kx{^B*7I>6Swj5C@GC3Ytu(B;^%NFwXJ)$CA&-dDFjy z6eit?kufW%?=kMH=g_$K$lwG%h^;kfk7)MV>(}Bk zvtDF7ddkCXE?vg!+s!)P&B{2)s}F2ktioK&=ps-5Jt`UL)UDb*zfJxADcy=RG!I7e}!B=yL=bx;{95004++T^7L=;v8BpSeYVA|!!^z;lp2B19P?1zWE1 zd{Y5~0Fts&bh-C=5@O1A^DvRS=50eTbiNYnhIS3d!^_ud?WV6$k}PNAs{(XX+bTTm zo=e2;S{&u&8;m(lzLBra)+3uz@H&%&%n}4X@H~5wp{j#i7r2*mv8S{m-dMq5@4Dw^ zu)^zg?ZpC^&4)ahN;#?jv+PS)9Mf=OP&BT&L*jad_UKbVMhP{;h9p`YfO|w$!6K{gjD>gycuYER>XhL9udRGGno#z-z+Csu+Mr{gc{_C6jdbjCfHlw5pt(x%=m)Kk35;xZ z42G`(U@Xb!F?=;yVk)DnPCEv8`-@o%^a zuhO6u7!#sDGl#F!3hhWbhd!PyYU{cwuNtkVH}f{)`Af##d-XXKnc_a%?G z-(xGQ<`+= zl>K9z7JoNdPr`F%xNSKtE9$~St_x=}T7Ps{=Zwm(o~}prv6*-7!7;`7p;;e+LXqIp zl5a%vL+-V$S~`?V{)+j?Y)<}l&$xFwN{UH=_7oM_zfA4&;35C`Lc12dr`}wOQs$&j zqaD6+?!Doz4w$)kw4(CQbL`T%-Li-Q;hy+kQ#N%_)g)q8YW)u@ZJum#I9_dvr!O%^ zrqJ`K>+pLFrRr(%t&h+c6i7shG+qz74`w8tp3=q#y5ME*$?}-@5?wm7xB_`BBunH9 zab*@}wJyv&x3{%_UtFJ7zk>^^cD0*`$RvWaMMuSPiK7Sa_0-evE#@Er#reD9vJiBVPBob^`xwZ;Hh8p zSYYY9VWzhKG&d<&di2I)z^(=lou&$Fg&OuE8uLr3LR` zp=(0#{qa>f%iM_qdl^&`!}z5K;hDi9hwb#(PXizdV%lCb@Q4Nq{%j|ovz)#^Gj^pY z_g_D`Udh2lHy(Fj&)zgFJT9^ucFi+%C-sBJ!%K|rKfPz${Q4*aK&5>0my88dA?Is2bPir(&bvvn zV{FV$L85+fM-smzmR{Y=5#4N-ke&%?PY{mi1Zc&zEf5R^N+45ed-Vaw8} z&hVtHIz2kjU`bER3qxzyRI92;BCT&i3vF$<2W9+XB~Kx#o|kI3lRg+GaVZTe@k~}d zg+yR(XB|}b`S$FPgQq-IY@E7Sr8-&>C>e~6Z04E4 z4_jSSS$H^;nK5WAf^lQb8?sw#!h)Ydves3!8|kMBbyh3naBoOaymoF}I&s>(l5jdg z5h7=x*mIZnb{MIn0n%Ia*2217e_pV3{e(wT0xnw8%wHy^=VzJUpV9q0p@7@JAuP?05y9@g92)_s(dS^TOVrkHFp};^yU!hmmeS zO@~y&S!+cmEDy!7Q=^O}v0C?l$?G%UeJU%?yO;&ip}nIX)HqK0t=V@0EfHfY8p@0* zjln%Zt_~dkL4ch>_6#lJrwSenJ&&#&0od z&szN!!c7zpP<((1czRxl+g3I6tv+|7>3~J90JcyH4bGx`Gr3$Rt`gIdGj-D52r`SK zRfIb8G=*aobvWqxhGwnlHF9S+t4LC`Y!tMXk^f^5@NNn>B&Z`nze1?hLJ7=#CR~Tk zaKLk7v)|va-#`T8Yc2CJkoU>Ppt|IkNnmZMo0*Meu3HGK&bv>~)vu|Afg1AkSVPhs zgN7w@O5hcEFm1>u{ZLpmoVTB8iWx(i&s4Xh(Z>@tt*g<|ZGVN%iG+_G>hG?GcUsHa zY+LI!Td)jvGbtgzSOz{Y7Z%;u(XH?r_4QA`doR1&8M316Z|P5zWzIbMcW;-_Y$p8% z&qlYk`F@#7r~_>xyBGdmBvcl=9PRmrwfdbcTVng`++D8gHX(Kkyz-U!u>1cJSO6zg zTt*_;nA{smH&g1(-Wy*WZ$!oSGARm6AGt4mVp=V}7Jl-`AQaEMXZ3XyT z{ikPF$ogBW63jb^46#Pt7L?5A9XT{k%`X2Rj2DShpA#hFEe>M93oJI zPAc&r?dr2AeJ9I6(9ZRzikXt;YIJOwfMJF?A1)iJlxTigIAyTyxx`sZ zKRm;KIm`OHBWD6V!Pi2fC{>ZMttld&caa=T+vg7W0bgmjWXM`fFE5znH>B=b9yn`f zquaeM-Vnedd{`(Omd?^)^G%yKJhfl;Jn?&ZyJw2H!1ut*K-!=i-n#N=0ba`Z+*0f0 zDbYUUi(b-?$*fvzXhny4w(gjk%h`Gf%*fCD2)lkAHBXvMXtrM#ndNC-QAr=DlHC>w z4|;6wLHU*!kZU>z)nyVNZHm~E$_^w^olaNS<N+j7I0-TPRwgMa#FxL=>Ih@lEP$rpTym~YYb}-2s@W|j5mX6X=K?lE@DGqB0+Bj9**IW2LCJl% zJM>LV#4sjP#$47xFfSwEBea%wILLm=e%juYw8xz>(IXxMW_LI+GT$o=j|BTFO7zTC z#i1iUG}i%pHzpcQ{oo*BdeU_5VvZC$(0#F<1JN2daEGqZ+~wwHHe`9RRf;9CKHEjf z8MnL8AX(ykzDJ3-A<=#|(o`UpWZw8KG1gwY2@GMJ9*jvsOm{;o=Cg-y80aISZV(1f z?x{0X?5P^Gr;VMSq%p6OEA@M@?9jgreB1jP*GL{2baF-GrYLSl_}ui^pmpzqIQPPKsflS`+w%8y*L%ny)KS9$++<25eu4--~Y_U4=NY47pKUz>r(EY_iK7}kjHa@D2WJDfF73WSVd z!A{&w7q35hW&LD6Wq&bJG)?1>?SK2_d|46?$m!ZClR`y4?%idY1l&JbS^*dRqsQaW zDJh+xzE%J53G|_m!w!pg1B}IHibKP85@LPy^x1?-Y7OaNaemZWC~FoQ&v(EhPZ0Z5 zOZKA+wYi;uvQ1=vPH`cZdR?(K_XCchY>D-awN|q= z3HQLK=%6W6fW_p1c&ARmy~nk_b1k0S&Q(TrosTr<_aZ4yxWwOiesS9V`6?c~UFZ?0 zo=33b%dzaJK+*gOto<-x7oXf%4`A2yj1*v)ucDDk+KNofJLI{SmYt**6N--qXjC}k{am8q7R zF0xx#=WH~$3F-$I569oxF}uj!msELK;lp)|37*+5I#-GYXd4ij2YpwI6!xQW>TvTi zeWy1MTZUVAdU;G*7o-A)&-^ROwK4LKAeOZvfxhYz`TW8WB};FVHK_X;?c>z@W0IkB zSDK-_kZhE!pWbCBeOeMRAeuTNoJC$pxJp#Jy}|R*B>8`+0uBa2KfxIY@pQ@Hj5Ma>+ZEDM=JGL+!zq@hQyJ(LXum*qmrK$SH8%aVqXLsB0H(vlBx05g3+9bO+v=T)l z?w=FqVmRXv&#l%7s ze15P+58|G>@tu_`O1qm8y_m|fIiqMe+)+lAFXcaQtZZE@@S z+DxX#7uy>J{{|40=vVHWFc%nPJ zkT>yGe6x==QF@{;pOg29!gd|=*kL*la7yQk?w^EYBKk`0Tm8rOlp=4_7!70L{q%a3 zM_`?yVx;J@SV?#~n_|vk%$`(Q9C*$7@r{Yd$sHv{w~91OkU1o;Yx5pfe)KaEjHD!3 zQ~XU2r}s+u(yuv(?f$JFdMJ|AgDJv)K(D?FuH5kq&;*gIIR5s~Bv0&{0D7R+4Y3JY z+~6}#noPPrn zDn;wlKZx@|YV_S5@7sqw8xqgd@4S(r?H`4(E~`oc4?4Emz)dYA^9c@(WkU~%ch8+? zzpn0Sl=qz_vUox|X+u#DI?zC#yRs!2wxd#i`iL#QvC!g|Hz$Oh6QWTAtSGg4_deA^Tzf!Jd<3Tg?Fg{tGHwmcN=_P zORFtzD?JyY=_CgZAG`ioTG9HW>QaNm?earUdH12cJeykig)luaku-rkwwL3TiEXNZdj64vK;dZI~H@J?lB%J}(f zP+ra5JwQ-KZi?=XYa*@{)4`FDlmJbFbIcmWaRFsaTMLN#^aNNeM?jZJjFt(}-S)I? zMmxlLrk>P;)A~V=(44;VHIS`~TCUB6z(SIf95&qPC5sYj3IDQYOag1$mJ-db(gt9ue98B5l8$78h_2^X=I&?Ndv* z!7j;x+r*ULB4Yiz){83zKbdFy`Ffa|i}t{zYQWcWK#_vvbH!M8iBBRQm$*QVCfc7Z zD7U!5z5KI0c=<;Y)pnhf2v4i?_10@K*<(P8@NjyvY(My{gsn-Ij zf80(ls&e-R3}W4_jhxCy*5MCc>iw+QNzvomyiQ0ub8!|bJE)%@0s-R&4b3Jk?k3D- zbk(p}YFFux!E3+78gELP$U?+F-?eVKZ00}<&x`2Fb0k)#p$^|mNP|CGPP~}NgzPE_<-dgm{=~P@fgws!ZEOux^%O1QU2?<=+o-B`s?XGuS!lzqr~4#Pwn_ zW^C5V{Sane&A+f9+z;`eD_xoohqw(u_i*}j3!TefZHr!!u0K(d3||iWez#}hCWPwT zznKjrXrs~Oyw5~#pLBAAxe>koE5wPI%|ktD5`o?jNNG;Xi!DC?B0L0I7sB??q3Sb% z8QZ0uEFlc+8LNQnBpGbkT^kBFhe2z(s84Zv-g4=ihiCG6-ifPx(l4nYY8zNE&YV`z zy`p1cxFDq8hpM%eQU`jZaKRJskky&(OfMwjJwpLXhSN?q0cC?}$vYL;%w zbE*)qEQ4tanHZ!wEPwWFuyt$ONy}i2zE{5Mk|atK7Skfphf495alev!>btNwO7d9n z{cmq6(HAw+ggA4l>C;`JnudEH4Qd@lAK!fL)+mTR6pP{lfgIQ#CUd8F_hzq)6M-3x z9~()zNgv8A*g-w_>i2quXP&%@`XarBlBt&M^wQyd2xiIqcTcLBe0E+d8ppf!A&H1P zpVV=`>))LuHy-6IbA{|}J@vaDA^hIjaHcIe%TmO0qG|tKZs|=<5qa%ykP#xiMLzIz z|21PMw8}a2rpV*l3*r01b4;%+j6&n{`%LQW#6)xG_W9SHOlp{ddend(V$B)I$sN3y zbV}H~M#6+ew0N`Tm5^1_k#foBL$I#EL7m6}AGV`*3=)eso6vd>V%%c!DB}p_MtLPj-GRNUwXL&wi zv(ul%DC~h-Ex}&|cY)k+855kkd1V&qR~=~(0k*oXP0QQys{cj!g*gU@Xu5un7N?6jg8(){UR~JpP;KL%@Lb*R4wZH`kwV(zfpVs30d6nT7)SRHgX3F(R_C) zHB8@+=QxrkbYpEqvNEVyFJ^B4@w7*?-L0oa#e&HO_glG)(ipM#7kuQ3W-XqVeH72y zQNQ>plE3Fp_d#Xoo-Ot5`fHpB=2qT=bQGhb*xnTg!MFZ1OHJLK_#X60QB~$;bVCKM18}8)R(a#sx*3zHP zhdQRlq)+MOHjb&*N+e@>*!0Drhil07=`jBXp=TNDE>7W(9}7U}K(6UDc5QB8>e^BN z74P91dQsDo@wrNr_~GkR<3YCHY+Ln$dG4HZ0#8$>09cq zw8!Kbb=qK;j>sFXlmV{f*&jpT-{m>~PV|X#0hj1k;5Y8tY%ZnK7kkE;p??&$k;tbyED%$9h3#p#=b;YyCR)9v>|;TQYc*91ue=19#`O%K-KG zPfPVxRn96s^&rh0z}ScuA!-k_YZA)^^6a)zj*pQ8J~4N6B(j}B=Sg_#->s+&bWHU9 z^|km_TB>K-sJNb@J22x$l%DA!O)8~gS1l}<0;#d-n){rnLqiU!#`F;)WB*Lm5FD-+ zi5C*TQ@?RZtNx_6L4b53F5C6d2i)7$ZKtrH_Ul1``m2Xd4BvOfM*wp8@pS<|(@1U2 zd*7EDbLvZ{2Y-&*7jo@o=naW(K9glpO4M#-aTaR6YBMDw`Qucy6Dt5y@ynkT15non z(aRK&UmJq@ADJKYyV2`TbB{eFR#+H>#yj5T#kwgwqpgFTI?0rK^{qG_vGnASE(r_( zqYx~d$X9w7Pz2}c7BQas>!{E0<>TFbd7B~WOP4(4)w$D{Tb5&M=Xr=ex{H!QlgqC0 zP|O&DQ@I|@7?of!f;Nvs{k?>=naQ&Huk^Aoh@ zC^x*JV?3w3aL-->qitv~qgfrcEyTcyFu3 z-79^#!J?t+$#Re!qiguSe(?XzX8)hp#2YMNDHN+&hianj;*a}3_wSR-wqA`2Jf1X3 zSnK|K>m|o+g*JfDIHppo>He>fQ^h} zL6-b*G_b-Qwz+)yuL^Jt(joX!f2|A&EBqK=!guNlsmJ_TMaD{2M$5#Tsl8yndskAK zp3*VSY?UTv;?p~Wik&f)6}YS<^mL^Hp(1a&v=%N$0j_0Np%gjRA3= zHek_1SabC^K0R!@AuS}a!NGHh3gOPa8Xc3T>bL%TxAW_fMzF8^otK6#886?h{)+H) z87){1{(5v=a53nhkkK|JZej1QLFD#Q!Z(-IKRPE^Tch`|dHc*~Q6 z*1*a0XHi?33(j=@A+e1UMkFmwq|j^YXZy6}(oN3UYeq1fv9x9I5R`n34Z*c;=!B99 zZkgd-_=Sh(u~dx46Y`Vb(Y9*;(4Vy`O|5W(+*jbt=alP+R?B_MdJbs_idOpjhWiwr zV0wN#^sKWtcL70`9Gk2dO8z2-!%PCv#@3i|a>dhyXJb}wX}D+$@Avq<5yj$K+(ARz zDu)H7NM-T8eI?BlF-8A&)vDCHp}ZGiH2ot4HILA|^h1$>fRbFMo#;&42+#ieW*o-J zC;6u^M7%}aoU`z9fue#IbW0_cwBXe$*h9^LgQq)r=s@aO4g<(vh~~WL1*`XhGjK1Z|S&E6IHk;a;r|4ClL8AXxKqm)PTH*=NqF?CpQ zQ#^(bQSnWO4<9~^BL~%@83CcI{F$D`Deav@M1$8(0yOtvLL(kgncjRjvAp+o7>w({*lf>2g-ElCBQ)uNX!@xg_O}mZ(?5t0L&rIE;gv=`BY}EM|IZr5Tbaq zcAVF-S#XR$H2T|;ZLDGG?Zlyu3pE*{d zI^7M{#r!E~i1k`2s(@3d2OQhF)G}J8=>XXo;6N;YKwidLHa`Y)JmI5ZG%akgdE^?M zzP+~`$?5|`u_fdp4~yzEukz*vT_z5no07_@s%M9+cWHO=P)bL`NE6Eq)*<*ag6#ux z53-nnLBfRzd#$CJI&x59W>}fW7i`sK{F0yw=Wk7mPF&qBm3I@6@tV-Gq>U~ez9TKd ztVi{e+<_$|Ubr%8{MRo@Y9+GPOZ{ws&6Plh}o!lL+(bGY-ulQ&{3zZWSp zppb8HyoGJ0SEX-t_dbPcQJm(|FsZaIYf%Yrid*@-f;>5x=zx+Q)$HMOTo>`TC`%nptm@z{e%ml zH&*doR;REF2+YSDK@eivE4f&u(L?XsM(dPR3!@-6ENDIht?EnN{~_;zX;imuOnp?? z?a(=ltvdOfH;Y|&xnGywc%3kKmKnS*-;Ju39p=)k;49y3N(=VPb6KkEn5>5 zV$YPN4m}ri*=BYwdiI9BYM*6m^ZdV?(%av7c7yq7c>%~H4n;l?2GC$nZFg%`Z;|jO z6hVIsy+L~S7h(83C&MNIq$)WGV>vb2wrRG;Qpdg+;L6OJXLHfK=Mr13f?e@bsw~(Z zx@WD$N%%DtW>`E%`?gJmpx|d8RIIFHekfc*2*mq7fY4U(*otwpQ$}~pZ9>}ZAlQ{K z4eZ6BA?(C_m0Vr?OZ?E0`2#C#{HxhFVE|rAw3_sJJQ~u!K8ake1lbgOFnYbi2kT}fOc1aD)7Z(<@Vc+)Xcfr!xZtK0zO+MC!gxb1VDwf@OHF7kV9vCLmLiPp z4R2~wKk*-tLYa`)G_;!CV!SdncLh!8Q}fwT zyB&cR-6tWT%Oat9^G`}Qu}trYO?RX4qo8{$gXJ5kW`WzFcZ&<(9*5dBs|3Df2vWmN z7S)OKtHj89Jl~f!^&eBmd(T{-)N^i0Z2&30D}o+5?i)M%v`zwJJvzfrBHpjo)rPwZ?-nd~ zgh26GpNySHW+2NR<@^|Xh$TI}X}wIt!_A8bNK@2mnXO+~R`l-Q;A=m0PV89Vlw{lsZV;Zm#fGnjoVUToWpChja-SZo1$hLn# zIV`o7=lri}qEc;tlx}KAo~e&K&1JF5(t92iIq$JoY%rp#_T}6iV~Xy6X;+e*Og6sa zYTxSgisvMp(CLa?sPY)VxzrwpO}0I(;W*~)>)7cr4Xvnf;kk+njI8vbpiT7hUttUO0%gsN;uXJ{!`p02^;^90q2MK=%yUIOYm~ZfY5~&tXHgg4VYE zJiumAr7X7y=OD3!ZZ-4im9P(}8XQHx(-(@a!^rM!w5$apDYz2(cf;q=(Hx~(SjXd+ z+a8LY5TXrMiOf+nSwQ2e!(y7P*w9V2Ay7)TY6IXYC7L=W!yY?=Ov7nY>1tRGh1ESw zNmf1;yG2S^Xv4Vn8d^bQu+Qq^>qe^NP`Th63=JjR^bfL$4AEv(4omAb7_qt2>86e} zqTdQ#8cW`I4^|>S@oaMtA<%xWd&a^kt~5;EB>kGQ;9gJSb@*0Cu>B36Fq?h5Pt54? zy|>+MZgC?38~g6JG2|yjZS7~`w!1^J)PJscY>pS^QLTc~tRr`C;{0FT4*Zs{)Zp+{ zKmQ566Po)y6164V-l6E1>y8yNuD5D(8KLonew|ଊK$F?alhEbLTmRA}GGS0rT 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

a*UF{M8N^YKeJe)}thN9azY^#qNIp&on#iZv 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 GuKf@1N=y>~ literal 0 HcmV?d00001 diff --git a/docs/re-use-foundry-project.md b/docs/re-use-foundry-project.md new file mode 100644 index 000000000..a6713c035 --- /dev/null +++ b/docs/re-use-foundry-project.md @@ -0,0 +1,44 @@ +[← Back to *DEPLOYMENT* guide](/docs/DeploymentGuide.md#deployment-options--steps) + +# Reusing an Existing Azure AI Foundry Project +To configure your environment to use an existing Azure AI Foundry Project, follow these steps: +--- +### 1. Go to Azure Portal +Go to https://portal.azure.com + +### 2. Search for Azure AI Foundry +In the search bar at the top, type "Azure AI Foundry" and click on it. Then select the Foundry service instance where your project exists. + +![alt text](../docs/images/re_use_foundry_project/azure_ai_foundry_list.png) + +### 3. Navigate to Projects under Resource Management +On the left sidebar of the Foundry service blade: + +- Expand the Resource Management section +- Click on Projects (this refers to the active Foundry project tied to the service) + +### 4. Click on the Project +From the Projects view: Click on the project name to open its details + + Note: You will see only one project listed here, as each Foundry service maps to a single project in this accelerator + +![alt text](../docs/images/re_use_foundry_project/navigate_to_projects.png) + +### 5. Copy Resource ID +In the left-hand menu of the project blade: + +- Click on Properties under Resource Management +- Locate the Resource ID field +- Click on the copy icon next to the Resource ID value + +![alt text](../docs/images/re_use_foundry_project/project_resource_id.png) + +### 6. Set the Foundry Project Resource ID in Your Environment +Run the following command in your terminal +```bash +azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID '' +``` +Replace `` with the value obtained from Step 5. + +### 7. Continue Deployment +Proceed with the next steps in the [deployment guide](/documents/DeploymentGuide.md#deployment-options--steps). From 8b24bca993e92a0d4acfda57bbfef69247a77d8c Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Mon, 11 Aug 2025 11:55:48 +0530 Subject: [PATCH 15/84] corrected name --- infra/main.bicep | 3 +-- infra/main.json | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 5c958ab0b..d29ec2cde 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -121,7 +121,6 @@ var useInternalStream = 'True' var useAIProjectClientFlag = 'False' var sqlServerFqdn = '${sqlDBModule.outputs.sqlServerName}.database.windows.net' - var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for tables and columns provided below: 1. Table: Clients Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents @@ -318,7 +317,7 @@ output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName output RESOURCE_GROUP_NAME string = resourceGroup().name output AI_FOUNDRY_RESOURCE_ID string = aifoundry.outputs.aiFoundryId -output SQLDB_SERVER string = sqlDBModule.outputs.sqlServerName +output SQLDB_SERVER_NAME string = sqlDBModule.outputs.sqlServerName output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId diff --git a/infra/main.json b/infra/main.json index 04fac82c4..cf552cd7f 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "7745904353912380124" + "templateHash": "11128400850485102096" } }, "parameters": { @@ -772,7 +772,7 @@ "_generator": { "name": "bicep", "version": "0.36.177.2456", - "templateHash": "419718136593168234" + "templateHash": "16824105341593554305" } }, "parameters": { From d455118a26433b9fb7e9f2531a8dc5dd15d4a439 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Mon, 11 Aug 2025 15:50:10 +0530 Subject: [PATCH 16/84] updated CAdeploy.yml --- .github/workflows/CAdeploy.yml | 52 +++++++++++++++------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 786865dff..f63531201 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -7,7 +7,7 @@ on: - dev - demo schedule: - - cron: '0 6,18 * * *' # Runs at 6:00 AM and 6:00 PM GMT + - cron: "0 6,18 * * *" # Runs at 6:00 AM and 6:00 PM GMT env: GPT_MIN_CAPACITY: 200 @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - + - name: Install ODBC Driver 18 for SQL Server run: | curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - @@ -81,7 +81,7 @@ jobs: - name: Install Bicep CLI run: az bicep install - + - name: Set Deployment Region id: set_region run: | @@ -98,7 +98,7 @@ jobs: UNIQUE_RG_NAME="arg-${ACCL_NAME}-${SHORT_UUID}" echo "RESOURCE_GROUP_NAME=${UNIQUE_RG_NAME}" >> $GITHUB_ENV echo "Generated RESOURCE_GROUP_NAME: ${UNIQUE_RG_NAME}" - + - name: Check and Create Resource Group id: check_create_rg run: | @@ -114,7 +114,7 @@ jobs: fi # Set output for other jobs echo "RESOURCE_GROUP_NAME=${{ env.RESOURCE_GROUP_NAME }}" >> $GITHUB_OUTPUT - + - name: Generate Unique Solution Prefix id: generate_solution_prefix run: | @@ -137,7 +137,7 @@ jobs: else TAG="latest"; fi echo "IMAGE_TAG=$TAG" >> $GITHUB_ENV echo "Image Tag: $TAG" - + - name: Deploy and extract values from deployment output id: get_output run: | @@ -152,7 +152,7 @@ jobs: --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} environmentName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ --query "properties.outputs" -o json) - + echo "Deployment output: $DEPLOY_OUTPUT" if [[ -z "$DEPLOY_OUTPUT" ]]; then @@ -160,8 +160,8 @@ jobs: exit 1 fi - export AI_FOUNDARY_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_FOUNDRY_NAME.value') - echo "AI_FOUNDARY_NAME=$AI_FOUNDARY_NAME" >> $GITHUB_ENV + export AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_FOUNDRY_RESOURCE_ID.value') + echo "AI_FOUNDRY_RESOURCE_ID=$AI_FOUNDRY_RESOURCE_ID" >> $GITHUB_ENV export SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_SEARCH_SERVICE_NAME.value') echo "SEARCH_SERVICE_NAME=$SEARCH_SERVICE_NAME" >> $GITHUB_ENV export COSMOS_DB_ACCOUNT_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.cosmosdB_ACCOUNT_NAME.value') @@ -182,8 +182,6 @@ jobs: echo "CLIENT_NAME=$CLIENT_NAME" >> $GITHUB_ENV export RG_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.resourcE_GROUP_NAME.value') echo "RG_NAME=$RG_NAME" >> $GITHUB_ENV - export RESOURCE_GROUP_NAME_FOUNDRY=$(echo "$DEPLOY_OUTPUT" | jq -r '.resourcE_GROUP_NAME_FOUNDRY.value') - echo "RESOURCE_GROUP_NAME_FOUNDRY=$RESOURCE_GROUP_NAME_FOUNDRY" >> $GITHUB_ENV WEBAPP_URL=$(echo $DEPLOY_OUTPUT | jq -r '.weB_APP_URL.value') echo "WEBAPP_URL=$WEBAPP_URL" >> $GITHUB_OUTPUT WEB_APP_NAME=$(echo $DEPLOY_OUTPUT | jq -r '.weB_APP_NAME.value') @@ -195,9 +193,7 @@ jobs: sleep 30 - - - - name: Deploy Infra and Import Sample Data + - name: Deploy Infra and Import Sample Data run: | set -e az account set --subscription "${{ secrets.AZURE_SUBSCRIPTION_ID }}" @@ -221,9 +217,8 @@ jobs: "${{ secrets.AZURE_CLIENT_ID }}" \ "${{ env.RG_NAME }}" \ "${{ env.SQL_SERVER_NAME }}" \ - "${{ env.AI_FOUNDARY_NAME }}" \ "${{ env.SEARCH_SERVICE_NAME }}" \ - "${{ env.RESOURCE_GROUP_NAME_FOUNDRY }}" + "${{ env.AI_FOUNDRY_RESOURCE_ID}}" user_roles_json='[ @@ -236,9 +231,8 @@ jobs: "${{ env.SQL_DATABASE }}" \ "$user_roles_json" \ "${{ secrets.AZURE_CLIENT_ID }}" - + echo "=== Post-Deployment Script Completed Successfully ===" - - name: Get AI Services name and store in variable if: always() && steps.check_create_rg.outcome == 'success' @@ -262,7 +256,7 @@ jobs: run: | set -e echo "Listing all KeyVaults in the resource group ${{ env.RESOURCE_GROUP_NAME }}..." - + # Get the list of KeyVaults in the specified resource group keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv) @@ -288,7 +282,7 @@ jobs: # Output the formatted array and save it to the job output echo "KEYVAULTS=$keyvault_array" >> $GITHUB_OUTPUT fi - + - name: Set Deployment Status id: deployment_status if: always() @@ -312,7 +306,7 @@ jobs: secrets: inherit cleanup: - if: always() + if: always() needs: [deploy, e2e-test] runs-on: ubuntu-latest env: @@ -336,12 +330,12 @@ jobs: set -e echo "Checking if resource group exists..." echo "Resource group name: ${{ env.RESOURCE_GROUP_NAME }}" - + if [ -z "${{ env.RESOURCE_GROUP_NAME }}" ]; then echo "Resource group name is empty. Skipping deletion." exit 0 fi - + rg_exists=$(az group exists --name "${{ env.RESOURCE_GROUP_NAME }}") if [ "$rg_exists" = "true" ]; then echo "Resource group exists. Cleaning..." @@ -368,12 +362,12 @@ jobs: # Remove the surrounding square brackets and quotes, if they exist stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g' | sed 's/"//g') - + # Convert the comma-separated string into an array IFS=',' read -r -a resources_to_check <<< "$stripped_keyvaults" - + echo "List of resources to check: ${resources_to_check[@]}" - + # Check if resource group still exists before listing resources rg_exists=$(az group exists --name "${{ env.RESOURCE_GROUP_NAME }}") if [ "$rg_exists" = "false" ]; then @@ -472,7 +466,7 @@ jobs: # Remove the surrounding square brackets and quotes, if they exist stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g' | sed 's/"//g') - + # Convert the comma-separated string into an array IFS=',' read -r -a keyvault_array <<< "$stripped_keyvaults" @@ -506,7 +500,7 @@ jobs: if: failure() || needs.deploy.result == 'failure' || needs.e2e-test.result == 'failure' run: | RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - + # Construct the email body EMAIL_BODY=$(cat < Date: Mon, 11 Aug 2025 16:06:37 +0530 Subject: [PATCH 17/84] update CAdeploy.yml --- .github/workflows/CAdeploy.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index f63531201..ae964dbed 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -29,7 +29,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Install ODBC Driver 18 for SQL Server run: | curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - @@ -81,7 +80,6 @@ jobs: - name: Install Bicep CLI run: az bicep install - - name: Set Deployment Region id: set_region run: | @@ -98,7 +96,6 @@ jobs: UNIQUE_RG_NAME="arg-${ACCL_NAME}-${SHORT_UUID}" echo "RESOURCE_GROUP_NAME=${UNIQUE_RG_NAME}" >> $GITHUB_ENV echo "Generated RESOURCE_GROUP_NAME: ${UNIQUE_RG_NAME}" - - name: Check and Create Resource Group id: check_create_rg run: | @@ -114,7 +111,6 @@ jobs: fi # Set output for other jobs echo "RESOURCE_GROUP_NAME=${{ env.RESOURCE_GROUP_NAME }}" >> $GITHUB_OUTPUT - - name: Generate Unique Solution Prefix id: generate_solution_prefix run: | @@ -137,7 +133,6 @@ jobs: else TAG="latest"; fi echo "IMAGE_TAG=$TAG" >> $GITHUB_ENV echo "Image Tag: $TAG" - - name: Deploy and extract values from deployment output id: get_output run: | @@ -152,8 +147,6 @@ jobs: --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} environmentName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ --query "properties.outputs" -o json) - - echo "Deployment output: $DEPLOY_OUTPUT" if [[ -z "$DEPLOY_OUTPUT" ]]; then echo "Error: Deployment output is empty. Please check the deployment logs." @@ -231,7 +224,6 @@ jobs: "${{ env.SQL_DATABASE }}" \ "$user_roles_json" \ "${{ secrets.AZURE_CLIENT_ID }}" - echo "=== Post-Deployment Script Completed Successfully ===" - name: Get AI Services name and store in variable @@ -256,7 +248,6 @@ jobs: run: | set -e echo "Listing all KeyVaults in the resource group ${{ env.RESOURCE_GROUP_NAME }}..." - # Get the list of KeyVaults in the specified resource group keyvaults=$(az resource list --resource-group ${{ env.RESOURCE_GROUP_NAME }} --query "[?type=='Microsoft.KeyVault/vaults'].name" -o tsv) @@ -282,7 +273,6 @@ jobs: # Output the formatted array and save it to the job output echo "KEYVAULTS=$keyvault_array" >> $GITHUB_OUTPUT fi - - name: Set Deployment Status id: deployment_status if: always() @@ -330,12 +320,10 @@ jobs: set -e echo "Checking if resource group exists..." echo "Resource group name: ${{ env.RESOURCE_GROUP_NAME }}" - if [ -z "${{ env.RESOURCE_GROUP_NAME }}" ]; then echo "Resource group name is empty. Skipping deletion." exit 0 fi - rg_exists=$(az group exists --name "${{ env.RESOURCE_GROUP_NAME }}") if [ "$rg_exists" = "true" ]; then echo "Resource group exists. Cleaning..." @@ -362,12 +350,9 @@ jobs: # Remove the surrounding square brackets and quotes, if they exist stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g' | sed 's/"//g') - # Convert the comma-separated string into an array IFS=',' read -r -a resources_to_check <<< "$stripped_keyvaults" - echo "List of resources to check: ${resources_to_check[@]}" - # Check if resource group still exists before listing resources rg_exists=$(az group exists --name "${{ env.RESOURCE_GROUP_NAME }}") if [ "$rg_exists" = "false" ]; then @@ -466,7 +451,6 @@ jobs: # Remove the surrounding square brackets and quotes, if they exist stripped_keyvaults=$(echo "$KEYVAULTS" | sed 's/\[\|\]//g' | sed 's/"//g') - # Convert the comma-separated string into an array IFS=',' read -r -a keyvault_array <<< "$stripped_keyvaults" @@ -500,7 +484,6 @@ jobs: if: failure() || needs.deploy.result == 'failure' || needs.e2e-test.result == 'failure' run: | RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - # Construct the email body EMAIL_BODY=$(cat < Date: Mon, 11 Aug 2025 18:10:23 +0530 Subject: [PATCH 18/84] changed pipeline variable --- .github/workflows/CAdeploy.yml | 4 +- infra/main.json | 1675 +++++++++++++++++--------------- 2 files changed, 906 insertions(+), 773 deletions(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 786865dff..d3c22bc54 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -149,13 +149,13 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} environmentName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ + --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT" - if [[ -z "$DEPLOY_OUTPUT" ]]; then + if [[ -z "$DEPLOY_OUTPUT" ]]; thenSOLUTION_PREFIX echo "Error: Deployment output is empty. Please check the deployment logs." exit 1 fi diff --git a/infra/main.json b/infra/main.json index 43c2bf875..3ca2ea982 100644 --- a/infra/main.json +++ b/infra/main.json @@ -4,38 +4,39 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "8388189115253050142" + "version": "0.37.4.10188", + "templateHash": "17838877322354490768" } }, "parameters": { - "environmentName": { + "solutionName": { "type": "string", + "defaultValue": "clientadvisor", "minLength": 3, "maxLength": 20, "metadata": { - "description": "A unique prefix for all resources in this deployment. This should be 3-20 characters long:" + "description": "Required. A unique prefix for all resources in this deployment. This should be 3-20 characters long:" } }, "existingLogAnalyticsWorkspaceId": { "type": "string", "defaultValue": "", "metadata": { - "description": "Optional: Existing Log Analytics Workspace Resource ID" + "description": "Optional. Existing Log Analytics Workspace Resource ID" } }, "azureExistingAIProjectResourceId": { "type": "string", "defaultValue": "", "metadata": { - "description": "Use this parameter to use an existing AI project resource ID" + "description": "Optional. Use this parameter to use an existing AI project resource ID" } }, "cosmosLocation": { "type": "string", "defaultValue": "eastus2", "metadata": { - "description": "CosmosDB Location" + "description": "Optional. CosmosDB Location" } }, "deploymentType": { @@ -47,7 +48,7 @@ ], "minLength": 1, "metadata": { - "description": "GPT model deployment type:" + "description": "Optional. GPT model deployment type:" } }, "gptModelName": { @@ -58,19 +59,22 @@ ], "minLength": 1, "metadata": { - "description": "Name of the GPT model to deploy:" + "description": "Optional. Name of the GPT model to deploy:" } }, "azureOpenaiAPIVersion": { "type": "string", - "defaultValue": "2025-04-01-preview" + "defaultValue": "2025-04-01-preview", + "metadata": { + "description": "Optional. API version for the Azure OpenAI service." + } }, "gptDeploymentCapacity": { "type": "int", "defaultValue": 200, "minValue": 10, "metadata": { - "description": "Capacity of the GPT deployment:" + "description": "Optional. Capacity of the GPT deployment:" } }, "embeddingModel": { @@ -81,7 +85,7 @@ ], "minLength": 1, "metadata": { - "description": "Name of the Text Embedding model to deploy:" + "description": "Optional. Name of the Text Embedding model to deploy:" } }, "embeddingDeploymentCapacity": { @@ -89,7 +93,7 @@ "defaultValue": 80, "minValue": 10, "metadata": { - "description": "Capacity of the Embedding Model deployment" + "description": "Optional. Capacity of the Embedding Model deployment" } }, "imageTag": { @@ -120,253 +124,40 @@ "OpenAI.GlobalStandard.text-embedding-ada-002,80" ] }, - "description": "Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." + "description": "Rquired. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." } }, "AZURE_LOCATION": { "type": "string", "defaultValue": "", "metadata": { - "description": "Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default." + "description": "Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default." } + }, + "solutionUniqueToken": { + "type": "string", + "defaultValue": "[substring(uniqueString(subscription().id, resourceGroup().name, parameters('solutionName')), 0, 5)]", + "maxLength": 5, + "metadata": { + "description": "Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Resources/resourceGroups@2025-04-01#properties/tags" + }, + "description": "Optional. The tags to apply to all deployed Azure resources." + }, + "defaultValue": {} } }, "variables": { - "$fxv#0": { - "ai": { - "aiSearch": "srch-", - "aiFoundry": "aif-", - "aiFoundryProject": "aifp-", - "aiServices": "aisa-", - "aiVideoIndexer": "avi-", - "machineLearningWorkspace": "mlw-", - "openAIService": "oai-", - "botService": "bot-", - "computerVision": "cv-", - "contentModerator": "cm-", - "contentSafety": "cs-", - "customVisionPrediction": "cstv-", - "customVisionTraining": "cstvt-", - "documentIntelligence": "di-", - "faceApi": "face-", - "healthInsights": "hi-", - "immersiveReader": "ir-", - "languageService": "lang-", - "speechService": "spch-", - "translator": "trsl-", - "aiHub": "aih-", - "aiHubProject": "aihp-" - }, - "analytics": { - "analysisServicesServer": "as", - "databricksWorkspace": "dbw-", - "dataExplorerCluster": "dec", - "dataExplorerClusterDatabase": "dedb", - "dataFactory": "adf-", - "digitalTwin": "dt-", - "streamAnalytics": "asa-", - "synapseAnalyticsPrivateLinkHub": "synplh-", - "synapseAnalyticsSQLDedicatedPool": "syndp", - "synapseAnalyticsSparkPool": "synsp", - "synapseAnalyticsWorkspaces": "synw", - "dataLakeStoreAccount": "dls", - "dataLakeAnalyticsAccount": "dla", - "eventHubsNamespace": "evhns-", - "eventHub": "evh-", - "eventGridDomain": "evgd-", - "eventGridSubscriptions": "evgs-", - "eventGridTopic": "evgt-", - "eventGridSystemTopic": "egst-", - "hdInsightHadoopCluster": "hadoop-", - "hdInsightHBaseCluster": "hbase-", - "hdInsightKafkaCluster": "kafka-", - "hdInsightSparkCluster": "spark-", - "hdInsightStormCluster": "storm-", - "hdInsightMLServicesCluster": "mls-", - "iotHub": "iot-", - "provisioningServices": "provs-", - "provisioningServicesCertificate": "pcert-", - "powerBIEmbedded": "pbi-", - "timeSeriesInsightsEnvironment": "tsi-" - }, - "compute": { - "appServiceEnvironment": "ase-", - "appServicePlan": "asp-", - "loadTesting": "lt-", - "availabilitySet": "avail-", - "arcEnabledServer": "arcs-", - "arcEnabledKubernetesCluster": "arck", - "batchAccounts": "ba-", - "cloudService": "cld-", - "communicationServices": "acs-", - "diskEncryptionSet": "des", - "functionApp": "func-", - "gallery": "gal", - "hostingEnvironment": "host-", - "imageTemplate": "it-", - "managedDiskOS": "osdisk", - "managedDiskData": "disk", - "notificationHubs": "ntf-", - "notificationHubsNamespace": "ntfns-", - "proximityPlacementGroup": "ppg-", - "restorePointCollection": "rpc-", - "snapshot": "snap-", - "staticWebApp": "stapp-", - "virtualMachine": "vm", - "virtualMachineScaleSet": "vmss-", - "virtualMachineMaintenanceConfiguration": "mc-", - "virtualMachineStorageAccount": "stvm", - "webApp": "app-" - }, - "containers": { - "aksCluster": "aks-", - "aksSystemNodePool": "npsystem-", - "aksUserNodePool": "np-", - "containerApp": "ca-", - "containerAppsEnvironment": "cae-", - "containerRegistry": "cr", - "containerInstance": "ci", - "serviceFabricCluster": "sf-", - "serviceFabricManagedCluster": "sfmc-" - }, - "databases": { - "cosmosDBDatabase": "cosmos-", - "cosmosDBApacheCassandra": "coscas-", - "cosmosDBMongoDB": "cosmon-", - "cosmosDBNoSQL": "cosno-", - "cosmosDBTable": "costab-", - "cosmosDBGremlin": "cosgrm-", - "cosmosDBPostgreSQL": "cospos-", - "cacheForRedis": "redis-", - "sqlDatabaseServer": "sql-", - "sqlDatabase": "sqldb-", - "sqlElasticJobAgent": "sqlja-", - "sqlElasticPool": "sqlep-", - "mariaDBServer": "maria-", - "mariaDBDatabase": "mariadb-", - "mySQLDatabase": "mysql-", - "postgreSQLDatabase": "psql-", - "sqlServerStretchDatabase": "sqlstrdb-", - "sqlManagedInstance": "sqlmi-" - }, - "developerTools": { - "appConfigurationStore": "appcs-", - "mapsAccount": "map-", - "signalR": "sigr", - "webPubSub": "wps-" - }, - "devOps": { - "managedGrafana": "amg-" - }, - "integration": { - "apiManagementService": "apim-", - "integrationAccount": "ia-", - "logicApp": "logic-", - "serviceBusNamespace": "sbns-", - "serviceBusQueue": "sbq-", - "serviceBusTopic": "sbt-", - "serviceBusTopicSubscription": "sbts-" - }, - "managementGovernance": { - "automationAccount": "aa-", - "applicationInsights": "appi-", - "monitorActionGroup": "ag-", - "monitorDataCollectionRules": "dcr-", - "monitorAlertProcessingRule": "apr-", - "blueprint": "bp-", - "blueprintAssignment": "bpa-", - "dataCollectionEndpoint": "dce-", - "logAnalyticsWorkspace": "log-", - "logAnalyticsQueryPacks": "pack-", - "managementGroup": "mg-", - "purviewInstance": "pview-", - "resourceGroup": "rg-", - "templateSpecsName": "ts-" - }, - "migration": { - "migrateProject": "migr-", - "databaseMigrationService": "dms-", - "recoveryServicesVault": "rsv-" - }, - "networking": { - "applicationGateway": "agw-", - "applicationSecurityGroup": "asg-", - "cdnProfile": "cdnp-", - "cdnEndpoint": "cdne-", - "connections": "con-", - "dnsForwardingRuleset": "dnsfrs-", - "dnsPrivateResolver": "dnspr-", - "dnsPrivateResolverInboundEndpoint": "in-", - "dnsPrivateResolverOutboundEndpoint": "out-", - "firewall": "afw-", - "firewallPolicy": "afwp-", - "expressRouteCircuit": "erc-", - "expressRouteGateway": "ergw-", - "frontDoorProfile": "afd-", - "frontDoorEndpoint": "fde-", - "frontDoorFirewallPolicy": "fdfp-", - "ipGroups": "ipg-", - "loadBalancerInternal": "lbi-", - "loadBalancerExternal": "lbe-", - "loadBalancerRule": "rule-", - "localNetworkGateway": "lgw-", - "natGateway": "ng-", - "networkInterface": "nic-", - "networkSecurityGroup": "nsg-", - "networkSecurityGroupSecurityRules": "nsgsr-", - "networkWatcher": "nw-", - "privateLink": "pl-", - "privateEndpoint": "pep-", - "publicIPAddress": "pip-", - "publicIPAddressPrefix": "ippre-", - "routeFilter": "rf-", - "routeServer": "rtserv-", - "routeTable": "rt-", - "serviceEndpointPolicy": "se-", - "trafficManagerProfile": "traf-", - "userDefinedRoute": "udr-", - "virtualNetwork": "vnet-", - "virtualNetworkGateway": "vgw-", - "virtualNetworkManager": "vnm-", - "virtualNetworkPeering": "peer-", - "virtualNetworkSubnet": "snet-", - "virtualWAN": "vwan-", - "virtualWANHub": "vhub-" - }, - "security": { - "bastion": "bas-", - "keyVault": "kv-", - "keyVaultManagedHSM": "kvmhsm-", - "managedIdentity": "id-", - "sshKey": "sshkey-", - "vpnGateway": "vpng-", - "vpnConnection": "vcn-", - "vpnSite": "vst-", - "webApplicationFirewallPolicy": "waf", - "webApplicationFirewallPolicyRuleGroup": "wafrg" - }, - "storage": { - "storSimple": "ssimp", - "backupVault": "bvault-", - "backupVaultPolicy": "bkpol-", - "fileShare": "share-", - "storageAccount": "st", - "storageSyncService": "sss-" - }, - "virtualDesktop": { - "labServicesPlan": "lp-", - "virtualDesktopHostPool": "vdpool-", - "virtualDesktopApplicationGroup": "vdag-", - "virtualDesktopWorkspace": "vdws-", - "virtualDesktopScalingPlan": "vdscaling-" - } - }, "solutionLocation": "[if(empty(parameters('AZURE_LOCATION')), resourceGroup().location, parameters('AZURE_LOCATION'))]", - "uniqueId": "[toLower(uniqueString(parameters('environmentName'), subscription().id, variables('solutionLocation'), resourceGroup().name))]", - "solutionPrefix": "[format('ca{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]", - "abbrs": "[variables('$fxv#0')]", - "hostingPlanName": "[format('{0}{1}', variables('abbrs').compute.appServicePlan, variables('solutionPrefix'))]", - "websiteName": "[format('{0}{1}', variables('abbrs').compute.webApp, variables('solutionPrefix'))]", + "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", + "hostingPlanName": "[format('asp-{0}', variables('solutionSuffix'))]", + "websiteName": "[format('app-{0}', variables('solutionSuffix'))]", "appEnvironment": "Prod", "azureSearchIndex": "transcripts_index", "azureSearchUseSemanticSearch": "True", @@ -392,7 +183,8 @@ "useAIProjectClientFlag": "False", "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n Only return the generated SQL query. Do not return anything else.", "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", - "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'." + "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'.", + "aiFoundryAiServicesAiProjectResourceName": "[format('proj-{0}', variables('solutionSuffix'))]" }, "resources": [ { @@ -400,9 +192,7 @@ "apiVersion": "2021-04-01", "name": "default", "properties": { - "tags": { - "TemplateName": "Client Advisor" - } + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('TemplateName', 'Client Advisor')))]" } }, { @@ -417,13 +207,16 @@ "mode": "Incremental", "parameters": { "solutionName": { - "value": "[variables('solutionPrefix')]" + "value": "[variables('solutionSuffix')]" }, "solutionLocation": { "value": "[variables('solutionLocation')]" }, "miName": { - "value": "[format('{0}{1}', variables('abbrs').security.managedIdentity, variables('solutionPrefix'))]" + "value": "[format('id-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -432,8 +225,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "17366528426252264029" + "version": "0.37.4.10188", + "templateHash": "6639330323573416008" } }, "parameters": { @@ -442,19 +235,26 @@ "minLength": 3, "maxLength": 15, "metadata": { - "description": "Solution Name" + "description": "Required. Name of the solution." } }, "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Deployment location for the solution." } }, "miName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the managed identity." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." } } }, @@ -464,10 +264,7 @@ "apiVersion": "2023-01-31", "name": "[parameters('miName')]", "location": "[parameters('solutionLocation')]", - "tags": { - "app": "[parameters('solutionName')]", - "location": "[parameters('solutionLocation')]" - } + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]" }, { "type": "Microsoft.Authorization/roleAssignments", @@ -487,15 +284,15 @@ "apiVersion": "2023-01-31", "name": "[format('{0}-webapp', parameters('miName'))]", "location": "[parameters('solutionLocation')]", - "tags": { - "app": "[parameters('solutionName')]", - "location": "[parameters('solutionLocation')]" - } + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]" } ], "outputs": { "managedIdentityOutput": { "type": "object", + "metadata": { + "description": "Details of the managed identity resource." + }, "value": { "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]", "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').principalId]", @@ -505,6 +302,9 @@ }, "managedIdentityWebAppOutput": { "type": "object", + "metadata": { + "description": "Details of the managed identity for the web app." + }, "value": { "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName')))]", "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName'))), '2023-01-31').principalId]", @@ -528,7 +328,7 @@ "mode": "Incremental", "parameters": { "solutionName": { - "value": "[variables('solutionPrefix')]" + "value": "[variables('solutionSuffix')]" }, "solutionLocation": { "value": "[variables('solutionLocation')]" @@ -537,7 +337,10 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" }, "kvName": { - "value": "[format('{0}{1}', variables('abbrs').security.keyVault, variables('solutionPrefix'))]" + "value": "[format('kv-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -546,75 +349,76 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "18360475517235523175" + "version": "0.37.4.10188", + "templateHash": "12303448646842336311" } }, "parameters": { "solutionName": { "type": "string", - "minLength": 3, - "maxLength": 15, "metadata": { - "description": "Solution Name" + "description": "Required. Solution Name" } }, "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Solution Location" } }, "utc": { "type": "string", - "defaultValue": "[utcNow()]" + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Current UTC timestamp." + } }, "kvName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the Azure Key Vault." } }, "createMode": { "type": "string", "defaultValue": "default", "metadata": { - "description": "Create Mode" + "description": "Optional. Specifies the create mode for the resource." } }, "enableForDeployment": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault." + "description": "Optional. Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault." } }, "enableForDiskEncryption": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys." + "description": "Optional. Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys." } }, "enableForTemplateDeployment": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault." + "description": "Optional. Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault." } }, "enableRBACAuthorization": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Enable RBAC Authorization. Property that controls how data actions are authorized." + "description": "Optional. Enable RBAC Authorization. Property that controls how data actions are authorized." } }, "softDeleteRetentionInDays": { "type": "int", "defaultValue": 7, "metadata": { - "description": "Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90." + "description": "Optional. Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90." } }, "publicNetworkAccess": { @@ -625,7 +429,7 @@ "disabled" ], "metadata": { - "description": "Public Network Access, Property to specify whether the vault will accept traffic from public internet." + "description": "Optional. Public Network Access, Property to specify whether the vault will accept traffic from public internet." } }, "sku": { @@ -636,11 +440,21 @@ "premium" ], "metadata": { - "description": "SKU" + "description": "Optional. SKU" } }, "managedIdentityObjectId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Object ID of the managed identity." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "variables": { @@ -652,10 +466,7 @@ "apiVersion": "2022-07-01", "name": "[parameters('kvName')]", "location": "[parameters('solutionLocation')]", - "tags": { - "app": "[parameters('solutionName')]", - "location": "[parameters('solutionLocation')]" - }, + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]", "properties": { "accessPolicies": [ { @@ -707,10 +518,16 @@ "outputs": { "keyvaultName": { "type": "string", + "metadata": { + "description": "Name of the Key Vault." + }, "value": "[parameters('kvName')]" }, "keyvaultId": { "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault." + }, "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('kvName'))]" } } @@ -732,7 +549,7 @@ "mode": "Incremental", "parameters": { "solutionName": { - "value": "[variables('solutionPrefix')]" + "value": "[variables('solutionSuffix')]" }, "solutionLocation": { "value": "[parameters('aiDeploymentsLocation')]" @@ -763,6 +580,12 @@ }, "azureExistingAIProjectResourceId": { "value": "[parameters('azureExistingAIProjectResourceId')]" + }, + "aiFoundryAiServicesAiProjectResourceName": { + "value": "[variables('aiFoundryAiServicesAiProjectResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -771,287 +594,106 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "5605479917210969670" + "version": "0.37.4.10188", + "templateHash": "12835630761460473235" } }, "parameters": { "solutionName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Solution Name" + } }, "solutionLocation": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Solution Location" + } }, "keyVaultName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Contains Name of KeyVault." + } }, "deploymentType": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Indicates the type of Deployment." + } }, "gptModelName": { - "type": "string" + "type": "string", + "defaultValue": "gpt-4o-mini", + "metadata": { + "description": "Optional. GPT Model Name" + } }, "azureOpenaiAPIVersion": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Azure OepnAI API Version." + } }, "gptDeploymentCapacity": { - "type": "int" + "type": "int", + "metadata": { + "description": "Required. Param to get Deployment Capacity." + } }, "embeddingModel": { - "type": "string" + "type": "string", + "defaultValue": "text-embedding-ada-002", + "metadata": { + "description": "Optional. Embedding Model." + } }, "embeddingDeploymentCapacity": { - "type": "int" + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Optional. Info about Embedding Deployment Capacity." + } }, "existingLogAnalyticsWorkspaceId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Existing Log Analytics WorkspaceID." + } }, "azureExistingAIProjectResourceId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Azure Existing AI Project ResourceID." + } + }, + "aiFoundryAiServicesAiProjectResourceName": { + "type": "string", + "metadata": { + "description": "Required. The name of the AI Foundry AI Project resource in Azure." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "variables": { - "$fxv#0": { - "ai": { - "aiSearch": "srch-", - "aiFoundry": "aif-", - "aiFoundryProject": "aifp-", - "aiServices": "aisa-", - "aiVideoIndexer": "avi-", - "machineLearningWorkspace": "mlw-", - "openAIService": "oai-", - "botService": "bot-", - "computerVision": "cv-", - "contentModerator": "cm-", - "contentSafety": "cs-", - "customVisionPrediction": "cstv-", - "customVisionTraining": "cstvt-", - "documentIntelligence": "di-", - "faceApi": "face-", - "healthInsights": "hi-", - "immersiveReader": "ir-", - "languageService": "lang-", - "speechService": "spch-", - "translator": "trsl-", - "aiHub": "aih-", - "aiHubProject": "aihp-" - }, - "analytics": { - "analysisServicesServer": "as", - "databricksWorkspace": "dbw-", - "dataExplorerCluster": "dec", - "dataExplorerClusterDatabase": "dedb", - "dataFactory": "adf-", - "digitalTwin": "dt-", - "streamAnalytics": "asa-", - "synapseAnalyticsPrivateLinkHub": "synplh-", - "synapseAnalyticsSQLDedicatedPool": "syndp", - "synapseAnalyticsSparkPool": "synsp", - "synapseAnalyticsWorkspaces": "synw", - "dataLakeStoreAccount": "dls", - "dataLakeAnalyticsAccount": "dla", - "eventHubsNamespace": "evhns-", - "eventHub": "evh-", - "eventGridDomain": "evgd-", - "eventGridSubscriptions": "evgs-", - "eventGridTopic": "evgt-", - "eventGridSystemTopic": "egst-", - "hdInsightHadoopCluster": "hadoop-", - "hdInsightHBaseCluster": "hbase-", - "hdInsightKafkaCluster": "kafka-", - "hdInsightSparkCluster": "spark-", - "hdInsightStormCluster": "storm-", - "hdInsightMLServicesCluster": "mls-", - "iotHub": "iot-", - "provisioningServices": "provs-", - "provisioningServicesCertificate": "pcert-", - "powerBIEmbedded": "pbi-", - "timeSeriesInsightsEnvironment": "tsi-" - }, - "compute": { - "appServiceEnvironment": "ase-", - "appServicePlan": "asp-", - "loadTesting": "lt-", - "availabilitySet": "avail-", - "arcEnabledServer": "arcs-", - "arcEnabledKubernetesCluster": "arck", - "batchAccounts": "ba-", - "cloudService": "cld-", - "communicationServices": "acs-", - "diskEncryptionSet": "des", - "functionApp": "func-", - "gallery": "gal", - "hostingEnvironment": "host-", - "imageTemplate": "it-", - "managedDiskOS": "osdisk", - "managedDiskData": "disk", - "notificationHubs": "ntf-", - "notificationHubsNamespace": "ntfns-", - "proximityPlacementGroup": "ppg-", - "restorePointCollection": "rpc-", - "snapshot": "snap-", - "staticWebApp": "stapp-", - "virtualMachine": "vm", - "virtualMachineScaleSet": "vmss-", - "virtualMachineMaintenanceConfiguration": "mc-", - "virtualMachineStorageAccount": "stvm", - "webApp": "app-" - }, - "containers": { - "aksCluster": "aks-", - "aksSystemNodePool": "npsystem-", - "aksUserNodePool": "np-", - "containerApp": "ca-", - "containerAppsEnvironment": "cae-", - "containerRegistry": "cr", - "containerInstance": "ci", - "serviceFabricCluster": "sf-", - "serviceFabricManagedCluster": "sfmc-" - }, - "databases": { - "cosmosDBDatabase": "cosmos-", - "cosmosDBApacheCassandra": "coscas-", - "cosmosDBMongoDB": "cosmon-", - "cosmosDBNoSQL": "cosno-", - "cosmosDBTable": "costab-", - "cosmosDBGremlin": "cosgrm-", - "cosmosDBPostgreSQL": "cospos-", - "cacheForRedis": "redis-", - "sqlDatabaseServer": "sql-", - "sqlDatabase": "sqldb-", - "sqlElasticJobAgent": "sqlja-", - "sqlElasticPool": "sqlep-", - "mariaDBServer": "maria-", - "mariaDBDatabase": "mariadb-", - "mySQLDatabase": "mysql-", - "postgreSQLDatabase": "psql-", - "sqlServerStretchDatabase": "sqlstrdb-", - "sqlManagedInstance": "sqlmi-" - }, - "developerTools": { - "appConfigurationStore": "appcs-", - "mapsAccount": "map-", - "signalR": "sigr", - "webPubSub": "wps-" - }, - "devOps": { - "managedGrafana": "amg-" - }, - "integration": { - "apiManagementService": "apim-", - "integrationAccount": "ia-", - "logicApp": "logic-", - "serviceBusNamespace": "sbns-", - "serviceBusQueue": "sbq-", - "serviceBusTopic": "sbt-", - "serviceBusTopicSubscription": "sbts-" - }, - "managementGovernance": { - "automationAccount": "aa-", - "applicationInsights": "appi-", - "monitorActionGroup": "ag-", - "monitorDataCollectionRules": "dcr-", - "monitorAlertProcessingRule": "apr-", - "blueprint": "bp-", - "blueprintAssignment": "bpa-", - "dataCollectionEndpoint": "dce-", - "logAnalyticsWorkspace": "log-", - "logAnalyticsQueryPacks": "pack-", - "managementGroup": "mg-", - "purviewInstance": "pview-", - "resourceGroup": "rg-", - "templateSpecsName": "ts-" - }, - "migration": { - "migrateProject": "migr-", - "databaseMigrationService": "dms-", - "recoveryServicesVault": "rsv-" - }, - "networking": { - "applicationGateway": "agw-", - "applicationSecurityGroup": "asg-", - "cdnProfile": "cdnp-", - "cdnEndpoint": "cdne-", - "connections": "con-", - "dnsForwardingRuleset": "dnsfrs-", - "dnsPrivateResolver": "dnspr-", - "dnsPrivateResolverInboundEndpoint": "in-", - "dnsPrivateResolverOutboundEndpoint": "out-", - "firewall": "afw-", - "firewallPolicy": "afwp-", - "expressRouteCircuit": "erc-", - "expressRouteGateway": "ergw-", - "frontDoorProfile": "afd-", - "frontDoorEndpoint": "fde-", - "frontDoorFirewallPolicy": "fdfp-", - "ipGroups": "ipg-", - "loadBalancerInternal": "lbi-", - "loadBalancerExternal": "lbe-", - "loadBalancerRule": "rule-", - "localNetworkGateway": "lgw-", - "natGateway": "ng-", - "networkInterface": "nic-", - "networkSecurityGroup": "nsg-", - "networkSecurityGroupSecurityRules": "nsgsr-", - "networkWatcher": "nw-", - "privateLink": "pl-", - "privateEndpoint": "pep-", - "publicIPAddress": "pip-", - "publicIPAddressPrefix": "ippre-", - "routeFilter": "rf-", - "routeServer": "rtserv-", - "routeTable": "rt-", - "serviceEndpointPolicy": "se-", - "trafficManagerProfile": "traf-", - "userDefinedRoute": "udr-", - "virtualNetwork": "vnet-", - "virtualNetworkGateway": "vgw-", - "virtualNetworkManager": "vnm-", - "virtualNetworkPeering": "peer-", - "virtualNetworkSubnet": "snet-", - "virtualWAN": "vwan-", - "virtualWANHub": "vhub-" - }, - "security": { - "bastion": "bas-", - "keyVault": "kv-", - "keyVaultManagedHSM": "kvmhsm-", - "managedIdentity": "id-", - "sshKey": "sshkey-", - "vpnGateway": "vpng-", - "vpnConnection": "vcn-", - "vpnSite": "vst-", - "webApplicationFirewallPolicy": "waf", - "webApplicationFirewallPolicyRuleGroup": "wafrg" - }, - "storage": { - "storSimple": "ssimp", - "backupVault": "bvault-", - "backupVaultPolicy": "bkpol-", - "fileShare": "share-", - "storageAccount": "st", - "storageSyncService": "sss-" - }, - "virtualDesktop": { - "labServicesPlan": "lp-", - "virtualDesktopHostPool": "vdpool-", - "virtualDesktopApplicationGroup": "vdag-", - "virtualDesktopWorkspace": "vdws-", - "virtualDesktopScalingPlan": "vdscaling-" - } - }, - "abbrs": "[variables('$fxv#0')]", - "aiFoundryName": "[format('{0}{1}', variables('abbrs').ai.aiFoundry, parameters('solutionName'))]", - "applicationInsightsName": "[format('{0}{1}', variables('abbrs').managementGovernance.applicationInsights, parameters('solutionName'))]", + "aiFoundryName": "[format('aif-{0}', parameters('solutionName'))]", + "applicationInsightsName": "[format('appi-{0}', parameters('solutionName'))]", "keyvaultName": "[parameters('keyVaultName')]", "location": "[parameters('solutionLocation')]", - "aiProjectName": "[format('{0}{1}', variables('abbrs').ai.aiFoundryProject, parameters('solutionName'))]", + "aiProjectName": "[format('{0}-{1}', parameters('aiFoundryAiServicesAiProjectResourceName'), parameters('solutionName'))]", "aiProjectFriendlyName": "[variables('aiProjectName')]", "aiProjectDescription": "AI Foundry Project", - "aiSearchName": "[format('{0}{1}', variables('abbrs').ai.aiSearch, parameters('solutionName'))]", - "workspaceName": "[format('{0}{1}', variables('abbrs').managementGovernance.logAnalyticsWorkspace, parameters('solutionName'))]", + "aiSearchName": "[format('srch-{0}', parameters('solutionName'))]", + "workspaceName": "[format('log-{0}', parameters('solutionName'))]", "aiModelDeployments": [ { "name": "[parameters('gptModelName')]", @@ -1093,7 +735,7 @@ "apiVersion": "2023-09-01", "name": "[variables('workspaceName')]", "location": "[variables('location')]", - "tags": {}, + "tags": "[parameters('tags')]", "properties": { "retentionInDays": 30, "sku": { @@ -1113,6 +755,7 @@ "publicNetworkAccessForQuery": "Enabled", "WorkspaceResourceId": "[if(variables('useExisting'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingLawSubscription'), variables('existingLawResourceGroup')), 'Microsoft.OperationalInsights/workspaces', variables('existingLawName')), resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')))]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]" ] @@ -1140,7 +783,8 @@ }, "publicNetworkAccess": "Enabled", "disableLocalAuth": false - } + }, + "tags": "[parameters('tags')]" }, { "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", @@ -1155,6 +799,7 @@ "description": "[variables('aiProjectDescription')]", "displayName": "[variables('aiProjectFriendlyName')]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" ] @@ -1214,7 +859,8 @@ } }, "semanticSearch": "free" - } + }, + "tags": "[parameters('tags')]" }, { "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", @@ -1346,7 +992,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-PREVIEW-API-VERSION')]", "properties": { "value": "[parameters('azureOpenaiAPIVersion')]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -1355,6 +1002,7 @@ "properties": { "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoints['OpenAI Language Model Instance API'])]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" ] @@ -1365,7 +1013,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-EMBEDDING-MODEL')]", "properties": { "value": "[parameters('embeddingModel')]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -1374,6 +1023,7 @@ "properties": { "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" ] @@ -1384,7 +1034,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SEARCH-INDEX')]", "properties": { "value": "transcripts_index" - } + }, + "tags": "[parameters('tags')]" }, { "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", @@ -1424,28 +1075,46 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11938772240348515861" + "version": "0.37.4.10188", + "templateHash": "6038840175458269917" } }, "parameters": { "existingAIProjectName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Existing AI Project Name" + } }, "existingAIFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Existing AI Foundry Name" + } }, "aiSearchName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Name" + } }, "aiSearchResourceId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Resource ID" + } }, "aiSearchLocation": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Location" + } }, "aiSearchConnectionName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Search Connection Name" + } } }, "resources": [ @@ -1498,6 +1167,9 @@ "aiProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIProjectName')), createObject('value', variables('aiProjectName')))]", "aiModelDeployments": { "value": "[variables('aiModelDeployments')]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1506,32 +1178,57 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9088273662782005929" + "version": "0.37.4.10188", + "templateHash": "14256377996349985323" } }, "parameters": { "principalId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Principal ID to assign the role to." + } }, "roleDefinitionId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. ID of the role definition to assign." + } }, "roleAssignmentName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the role assignment." + } }, "aiFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry resource." + } }, "aiProjectName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the AI project." + } }, "aiModelDeployments": { "type": "array", - "defaultValue": [] + "defaultValue": [], + "metadata": { + "description": "Optional. List of AI model deployments." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ @@ -1556,7 +1253,8 @@ "sku": { "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Authorization/roleAssignments", @@ -1573,10 +1271,16 @@ "outputs": { "aiServicesPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Services resource." + }, "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), '2025-04-01-preview', 'full').identity.principalId]" }, "aiProjectPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Project resource if defined." + }, "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" } } @@ -1590,78 +1294,135 @@ "outputs": { "keyvaultName": { "type": "string", + "metadata": { + "description": "Contains Name of KeyVault." + }, "value": "[variables('keyvaultName')]" }, "keyvaultId": { "type": "string", + "metadata": { + "description": "Contains KeyVault ID." + }, "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" }, "resourceGroupNameFoundry": { "type": "string", + "metadata": { + "description": "Contains AI Foundry ResourceGroup Name" + }, "value": "[if(not(empty(variables('existingAIServiceResourceGroup'))), variables('existingAIServiceResourceGroup'), resourceGroup().name)]" }, "aiFoundryProjectEndpoint": { "type": "string", + "metadata": { + "description": "Contains Name of AI Foundry Project Endpoint." + }, "value": "[if(not(empty(variables('existingProjEndpoint'))), variables('existingProjEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview').endpoints['AI Foundry API'])]" }, "aoaiEndpoint": { "type": "string", + "metadata": { + "description": "Contains AI Endpoint." + }, "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoints['OpenAI Language Model Instance API'])]" }, "aiFoundryName": { "type": "string", + "metadata": { + "description": "Contains Name of AI Foundry." + }, "value": "[if(not(empty(variables('existingAIFoundryName'))), variables('existingAIFoundryName'), variables('aiFoundryName'))]" }, "aiSearchName": { "type": "string", + "metadata": { + "description": "Contains AI Search Name." + }, "value": "[variables('aiSearchName')]" }, "aiSearchId": { "type": "string", + "metadata": { + "description": "Contains AI SearchID." + }, "value": "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" }, "aiSearchTarget": { "type": "string", + "metadata": { + "description": "Contains AI Search Target." + }, "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" }, "aiSearchService": { "type": "string", + "metadata": { + "description": "Contains AI Search Service." + }, "value": "[variables('aiSearchName')]" }, "aiFoundryProjectName": { "type": "string", + "metadata": { + "description": "Contains Name of AI Foundry Project." + }, "value": "[if(not(empty(variables('existingAIProjectName'))), variables('existingAIProjectName'), variables('aiProjectName'))]" }, "applicationInsightsId": { "type": "string", + "metadata": { + "description": "Contains Application Insights ID." + }, "value": "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]" }, "instrumentationKey": { "type": "string", + "metadata": { + "description": "The Instrumentation Key for the Application Insights resource." + }, "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').InstrumentationKey]" }, "logAnalyticsWorkspaceResourceName": { "type": "string", + "metadata": { + "description": "Contains Log Analytics Workspace Resource Name." + }, "value": "[if(variables('useExisting'), variables('existingLawName'), variables('workspaceName'))]" }, "logAnalyticsWorkspaceResourceGroup": { "type": "string", + "metadata": { + "description": "Contains Log Analytics Workspace ResourceGroup Name." + }, "value": "[if(variables('useExisting'), variables('existingLawResourceGroup'), resourceGroup().name)]" }, "applicationInsightsConnectionString": { "type": "string", + "metadata": { + "description": "Contains Application Insights Connection String." + }, "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').ConnectionString]" }, "aiSearchFoundryConnectionName": { "type": "string", + "metadata": { + "description": "Contains AI Search Foundry Connection Name." + }, "value": "[variables('aiSearchConnectionName')]" }, "aiAppInsightsFoundryConnectionName": { "type": "string", + "metadata": { + "description": "Contains AI Foundry App Insights Connection Name." + }, "value": "[variables('aiAppInsightConnectionName')]" }, "aiModelDeployments": { "type": "array", + "metadata": { + "description": "Contains AI Model Deployments" + }, "value": "[variables('aiModelDeployments')]" } } @@ -1686,7 +1447,10 @@ "value": "[parameters('cosmosLocation')]" }, "cosmosDBName": { - "value": "[format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix'))]" + "value": "[format('cosmos-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1695,27 +1459,38 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "8645494058104543506" + "version": "0.37.4.10188", + "templateHash": "7830346183750605706" } }, "parameters": { "solutionLocation": { - "type": "string" + "type": "string", + "minLength": 3, + "maxLength": 20, + "metadata": { + "description": "Required. Solution location." + } }, "cosmosDBName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the Azure Cosmos DB account." } }, "databaseName": { "type": "string", - "defaultValue": "db_conversation_history" + "defaultValue": "db_conversation_history", + "metadata": { + "description": "Optional. Name of the Cosmos DB database." + } }, "collectionName": { "type": "string", - "defaultValue": "conversations" + "defaultValue": "conversations", + "metadata": { + "description": "Optional.Name of the Cosmos DB container (collection)." + } }, "containers": { "type": "array", @@ -1725,7 +1500,10 @@ "id": "[parameters('collectionName')]", "partitionKey": "/userId" } - ] + ], + "metadata": { + "description": "Optional. List of Cosmos DB containers to be created." + } }, "kind": { "type": "string", @@ -1734,11 +1512,17 @@ "GlobalDocumentDB", "MongoDB", "Parse" - ] + ], + "metadata": { + "description": "Optional. The API kind of the Cosmos DB account." + } }, "tags": { "type": "object", - "defaultValue": {} + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ @@ -1804,6 +1588,7 @@ "id": "[parameters('databaseName')]" } }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]" ] @@ -1812,14 +1597,23 @@ "outputs": { "cosmosAccountName": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB account." + }, "value": "[parameters('cosmosDBName')]" }, "cosmosDatabaseName": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB database." + }, "value": "[parameters('databaseName')]" }, "cosmosContainerName": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB container." + }, "value": "[parameters('collectionName')]" } } @@ -1844,10 +1638,13 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" }, "saName": { - "value": "[format('{0}{1}', variables('abbrs').storage.storageAccount, variables('solutionPrefix'))]" + "value": "[format('st{0}', variables('solutionSuffix'))]" }, "keyVaultName": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -1856,28 +1653,41 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15740867049667651602" + "version": "0.37.4.10188", + "templateHash": "3164769802823369019" } }, "parameters": { "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Deployment location for the solution." } }, "saName": { "type": "string", "metadata": { - "description": "Name" + "description": "Required. Name of the storage account." } }, "managedIdentityObjectId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Object ID of the managed identity." + } }, "keyVaultName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Key Vault." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ @@ -1917,7 +1727,8 @@ }, "accessTier": "Hot", "allowSharedKeyAccess": false - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Storage/storageAccounts/blobServices", @@ -1966,7 +1777,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'ADLS-ACCOUNT-NAME')]", "properties": { "value": "[parameters('saName')]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -1974,16 +1786,23 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'ADLS-ACCOUNT-CONTAINER')]", "properties": { "value": "data" - } + }, + "tags": "[parameters('tags')]" } ], "outputs": { "storageName": { "type": "string", + "metadata": { + "description": "Name of the storage account." + }, "value": "[parameters('saName')]" }, "storageContainer": { "type": "string", + "metadata": { + "description": "Name of the default storage container." + }, "value": "data" } } @@ -2018,10 +1837,13 @@ "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.name]" }, "serverName": { - "value": "[format('{0}{1}', variables('abbrs').databases.sqlDatabaseServer, variables('solutionPrefix'))]" + "value": "[format('sql-{0}', variables('solutionSuffix'))]" }, "sqlDBName": { - "value": "[format('{0}{1}', variables('abbrs').databases.sqlDatabase, variables('solutionPrefix'))]" + "value": "[format('sqldb-{0}', variables('solutionSuffix'))]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -2030,40 +1852,59 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "17137003456218736893" + "version": "0.37.4.10188", + "templateHash": "16739666735476206580" } }, "parameters": { "solutionLocation": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Deployment location for the solution." + } }, "keyVaultName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Key Vault." + } }, "managedIdentityObjectId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Object ID of the managed identity." + } }, "managedIdentityName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity." + } }, "serverName": { "type": "string", "metadata": { - "description": "The name of the SQL logical server." + "description": "Required. The name of the SQL logical server." } }, "sqlDBName": { "type": "string", "metadata": { - "description": "The name of the SQL Database." + "description": "Required. The name of the SQL Database." } }, "location": { "type": "string", "defaultValue": "[parameters('solutionLocation')]", "metadata": { - "description": "Location for all resources." + "description": "Required. Location for all resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." } } }, @@ -2086,7 +1927,8 @@ "administratorType": "ActiveDirectory", "azureADOnlyAuthentication": true } - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Sql/servers/firewallRules", @@ -2131,6 +1973,7 @@ "readScale": "Disabled", "zoneRedundant": false }, + "tags": "[parameters('tags')]", "dependsOn": [ "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" ] @@ -2141,7 +1984,8 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'SQLDB-SERVER')]", "properties": { "value": "[format('{0}.database.windows.net', parameters('serverName'))]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.KeyVault/vaults/secrets", @@ -2149,16 +1993,23 @@ "name": "[format('{0}/{1}', parameters('keyVaultName'), 'SQLDB-DATABASE')]", "properties": { "value": "[parameters('sqlDBName')]" - } + }, + "tags": "[parameters('tags')]" } ], "outputs": { "sqlServerName": { "type": "string", + "metadata": { + "description": "Name of the SQL logical server." + }, "value": "[parameters('serverName')]" }, "sqlDbName": { "type": "string", + "metadata": { + "description": "Name of the SQL database." + }, "value": "[parameters('sqlDBName')]" } } @@ -2183,88 +2034,88 @@ "solutionLocation": { "value": "[variables('solutionLocation')]" }, - "HostingPlanName": { + "hostingPlanName": { "value": "[variables('hostingPlanName')]" }, - "WebsiteName": { + "websiteName": { "value": "[variables('websiteName')]" }, - "AppEnvironment": { + "appEnvironment": { "value": "[variables('appEnvironment')]" }, - "AzureSearchService": { + "azureSearchService": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" }, - "AzureSearchIndex": { + "azureSearchIndex": { "value": "[variables('azureSearchIndex')]" }, - "AzureSearchUseSemanticSearch": { + "azureSearchUseSemanticSearch": { "value": "[variables('azureSearchUseSemanticSearch')]" }, - "AzureSearchSemanticSearchConfig": { + "azureSearchSemanticSearchConfig": { "value": "[variables('azureSearchSemanticSearchConfig')]" }, - "AzureSearchTopK": { + "azureSearchTopK": { "value": "[variables('azureSearchTopK')]" }, - "AzureSearchContentColumns": { + "azureSearchContentColumns": { "value": "[variables('azureSearchContentColumns')]" }, - "AzureSearchFilenameColumn": { + "azureSearchFilenameColumn": { "value": "[variables('azureSearchFilenameColumn')]" }, - "AzureSearchTitleColumn": { + "azureSearchTitleColumn": { "value": "[variables('azureSearchTitleColumn')]" }, - "AzureSearchUrlColumn": { + "azureSearchUrlColumn": { "value": "[variables('azureSearchUrlColumn')]" }, - "AzureOpenAIResource": { + "azureOpenAIResource": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" }, - "AzureOpenAIEndpoint": { + "azureOpenAIEndpoint": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, - "AzureOpenAIModel": { + "azureOpenAIModel": { "value": "[parameters('gptModelName')]" }, - "AzureOpenAITemperature": { + "azureOpenAITemperature": { "value": "[variables('azureOpenAITemperature')]" }, - "AzureOpenAITopP": { + "azureOpenAITopP": { "value": "[variables('azureOpenAITopP')]" }, - "AzureOpenAIMaxTokens": { + "azureOpenAIMaxTokens": { "value": "[variables('azureOpenAIMaxTokens')]" }, - "AzureOpenAIStopSequence": { + "azureOpenAIStopSequence": { "value": "[variables('azureOpenAIStopSequence')]" }, - "AzureOpenAISystemMessage": { + "azureOpenAISystemMessage": { "value": "[variables('azureOpenAISystemMessage')]" }, - "AzureOpenAIApiVersion": { + "azureOpenAIApiVersion": { "value": "[parameters('azureOpenaiAPIVersion')]" }, - "AzureOpenAIStream": { + "azureOpenAIStream": { "value": "[variables('azureOpenAIStream')]" }, - "AzureSearchQueryType": { + "azureSearchQueryType": { "value": "[variables('azureSearchQueryType')]" }, - "AzureSearchVectorFields": { + "azureSearchVectorFields": { "value": "[variables('azureSearchVectorFields')]" }, - "AzureSearchPermittedGroupsField": { + "azureSearchPermittedGroupsField": { "value": "[variables('azureSearchPermittedGroupsField')]" }, - "AzureSearchStrictness": { + "azureSearchStrictness": { "value": "[variables('azureSearchStrictness')]" }, - "AzureOpenAIEmbeddingName": { + "azureOpenAIEmbeddingName": { "value": "[parameters('embeddingModel')]" }, - "AzureOpenAIEmbeddingEndpoint": { + "azureOpenAIEmbeddingEndpoint": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, "USE_INTERNAL_STREAM": { @@ -2326,6 +2177,9 @@ }, "aiSearchProjectConnectionName": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -2334,18 +2188,18 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "17855260504239152628" + "version": "0.37.4.10188", + "templateHash": "1598037199910826757" } }, "parameters": { "solutionLocation": { "type": "string", "metadata": { - "description": "Solution Location" + "description": "Required. Solution Location" } }, - "HostingPlanSku": { + "hostingPlanSku": { "type": "string", "defaultValue": "B2", "allowedValues": [ @@ -2364,157 +2218,167 @@ "P0v3" ], "metadata": { - "description": "The pricing tier for the App Service plan" + "description": "Optional. The pricing tier for the App Service plan" } }, - "HostingPlanName": { - "type": "string" + "hostingPlanName": { + "type": "string", + "metadata": { + "description": "Required. Name of App Service plan" + } }, - "WebsiteName": { - "type": "string" + "websiteName": { + "type": "string", + "metadata": { + "description": "Required. Name of Web App" + } }, - "AppEnvironment": { - "type": "string" + "appEnvironment": { + "type": "string", + "metadata": { + "description": "Specifies the application environment" + } }, - "AzureSearchService": { + "azureSearchService": { "type": "string", "defaultValue": "", "metadata": { - "description": "Name of Azure Search Service" + "description": "Optional. Name of Azure Search Service" } }, - "AzureSearchIndex": { + "azureSearchIndex": { "type": "string", "defaultValue": "", "metadata": { - "description": "Name of Azure Search Index" + "description": "Optional. Name of Azure Search Index" } }, - "AzureSearchUseSemanticSearch": { + "azureSearchUseSemanticSearch": { "type": "string", "defaultValue": "False", "metadata": { - "description": "Use semantic search" + "description": "Optional. Use semantic search" } }, - "AzureSearchSemanticSearchConfig": { + "azureSearchSemanticSearchConfig": { "type": "string", "defaultValue": "default", "metadata": { - "description": "Semantic search config" + "description": "Optional. Semantic search config" } }, - "AzureSearchTopK": { + "azureSearchTopK": { "type": "string", "defaultValue": "5", "metadata": { - "description": "Top K results" + "description": "Optional. Top K results" } }, - "AzureSearchEnableInDomain": { + "azureSearchEnableInDomain": { "type": "string", "defaultValue": "False", "metadata": { - "description": "Enable in domain" + "description": "Optional. Enable in domain" } }, - "AzureSearchContentColumns": { + "azureSearchContentColumns": { "type": "string", "defaultValue": "content", "metadata": { - "description": "Content columns" + "description": "Optional. Content columns" } }, - "AzureSearchFilenameColumn": { + "azureSearchFilenameColumn": { "type": "string", "defaultValue": "filename", "metadata": { - "description": "Filename column" + "description": "Optional. Filename column" } }, - "AzureSearchTitleColumn": { + "azureSearchTitleColumn": { "type": "string", "defaultValue": "client_id", "metadata": { - "description": "Title column" + "description": "Optional. Title column" } }, - "AzureSearchUrlColumn": { + "azureSearchUrlColumn": { "type": "string", "defaultValue": "sourceurl", "metadata": { - "description": "Url column" + "description": "Optional. Url column" } }, - "AzureOpenAIResource": { + "azureOpenAIResource": { "type": "string", "metadata": { - "description": "Name of Azure OpenAI Resource" + "description": "Required. Name of Azure OpenAI Resource" } }, - "AzureOpenAIModel": { + "azureOpenAIModel": { "type": "string", + "defaultValue": "gpt-4o-mini", "metadata": { - "description": "Azure OpenAI Model Deployment Name" + "description": "Optional. Azure OpenAI Model Deployment Name" } }, - "AzureOpenAIEndpoint": { + "azureOpenAIEndpoint": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Open AI Endpoint" + "description": "Optional. Azure Open AI Endpoint" } }, - "AzureOpenAITemperature": { + "azureOpenAITemperature": { "type": "string", "defaultValue": "0", "metadata": { - "description": "Azure OpenAI Temperature" + "description": "Optional. Azure OpenAI Temperature" } }, - "AzureOpenAITopP": { + "azureOpenAITopP": { "type": "string", "defaultValue": "1", "metadata": { - "description": "Azure OpenAI Top P" + "description": "Optional. Azure OpenAI Top P" } }, - "AzureOpenAIMaxTokens": { + "azureOpenAIMaxTokens": { "type": "string", "defaultValue": "1000", "metadata": { - "description": "Azure OpenAI Max Tokens" + "description": "Optional. Azure OpenAI Max Tokens" } }, - "AzureOpenAIStopSequence": { + "azureOpenAIStopSequence": { "type": "string", "defaultValue": "\n", "metadata": { - "description": "Azure OpenAI Stop Sequence" + "description": "Optional. Azure OpenAI Stop Sequence" } }, - "AzureOpenAISystemMessage": { + "azureOpenAISystemMessage": { "type": "string", "defaultValue": "You are an AI assistant that helps people find information.", "metadata": { - "description": "Azure OpenAI System Message" + "description": "Optional. Azure OpenAI System Message" } }, - "AzureOpenAIApiVersion": { + "azureOpenAIApiVersion": { "type": "string", "defaultValue": "2024-02-15-preview", "metadata": { - "description": "Azure OpenAI Api Version" + "description": "Optional. Azure OpenAI Api Version" } }, - "AzureOpenAIStream": { + "azureOpenAIStream": { "type": "string", "defaultValue": "True", "metadata": { - "description": "Whether or not to stream responses from Azure OpenAI" + "description": "Optional. Whether or not to stream responses from Azure OpenAI" } }, - "AzureSearchQueryType": { + "azureSearchQueryType": { "type": "string", "defaultValue": "simple", "allowedValues": [ @@ -2525,24 +2389,24 @@ "vectorSemanticHybrid" ], "metadata": { - "description": "Azure Search Query Type" + "description": "Optional. Azure Search Query Type" } }, - "AzureSearchVectorFields": { + "azureSearchVectorFields": { "type": "string", "defaultValue": "contentVector", "metadata": { - "description": "Azure Search Vector Fields" + "description": "Optional. Azure Search Vector Fields" } }, - "AzureSearchPermittedGroupsField": { + "azureSearchPermittedGroupsField": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Search Permitted Groups Field" + "description": "Optional. Azure Search Permitted Groups Field" } }, - "AzureSearchStrictness": { + "azureSearchStrictness": { "type": "string", "defaultValue": "3", "allowedValues": [ @@ -2553,128 +2417,168 @@ "5" ], "metadata": { - "description": "Azure Search Strictness" + "description": "Optional. Azure Search Strictness" } }, - "AzureOpenAIEmbeddingName": { + "azureOpenAIEmbeddingName": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure OpenAI Embedding Deployment Name" + "description": "Optional. Azure OpenAI Embedding Deployment Name" } }, - "AzureOpenAIEmbeddingEndpoint": { + "azureOpenAIEmbeddingEndpoint": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Open AI Embedding Endpoint" + "description": "Optional. Azure Open AI Embedding Endpoint" } }, "USE_INTERNAL_STREAM": { "type": "string", "defaultValue": "True", "metadata": { - "description": "Use Azure Function" + "description": "Optional. Use Azure Function" } }, "SQLDB_SERVER": { "type": "string", "defaultValue": "", "metadata": { - "description": "SQL Database Server Name" + "description": "Optional. SQL Database Server Name" } }, "SQLDB_DATABASE": { "type": "string", "defaultValue": "", "metadata": { - "description": "SQL Database Name" + "description": "Optional. SQL Database Name" } }, "AZURE_COSMOSDB_ACCOUNT": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Cosmos DB Account" + "description": "Optional. Azure Cosmos DB Account" } }, "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Cosmos DB Conversations Container" + "description": "Optional. Azure Cosmos DB Conversations Container" } }, "AZURE_COSMOSDB_DATABASE": { "type": "string", "defaultValue": "", "metadata": { - "description": "Azure Cosmos DB Database" + "description": "Optional. Azure Cosmos DB Database" } }, "AZURE_COSMOSDB_ENABLE_FEEDBACK": { "type": "string", "defaultValue": "True", "metadata": { - "description": "Enable feedback in Cosmos DB" + "description": "Optional. Enable feedback in Cosmos DB" } }, "imageTag": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The container image tag to be deployed" + } }, "userassignedIdentityId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The resource ID of the user-assigned managed identity to be used by the deployed resources." + } }, "userassignedIdentityClientId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The client ID of the user-assigned managed identity." + } }, "applicationInsightsId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring." + } }, "azureSearchServiceEndpoint": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. The endpoint URL of the Azure Cognitive Search service." + } }, "sqlSystemPrompt": { "type": "string", "metadata": { - "description": "Azure Function App SQL System Prompt" + "description": "Required. Azure Function App SQL System Prompt" } }, "callTranscriptSystemPrompt": { "type": "string", "metadata": { - "description": "Azure Function App CallTranscript System Prompt" + "description": "Required. Azure Function App CallTranscript System Prompt" } }, "streamTextSystemPrompt": { "type": "string", "metadata": { - "description": "Azure Function App Stream Text System Prompt" + "description": "Required. Azure Function App Stream Text System Prompt" } }, "aiFoundryProjectEndpoint": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. AI Foundry project endpoint URL." + } }, "useAIProjectClientFlag": { "type": "string", - "defaultValue": "false" + "defaultValue": "false", + "metadata": { + "description": "Optional. Flag to enable AI project client." + } }, "aiFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry project." + } }, "applicationInsightsConnectionString": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Application Insights connection string." + } }, "aiSearchProjectConnectionName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Connection name for Azure Cognitive Search." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } }, "azureExistingAIProjectResourceId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the existing AI Foundry project." + } } }, "variables": { - "WebAppImageName": "[format('DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:{0}', parameters('imageTag'))]", + "webAppImageName": "[format('DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:{0}', parameters('imageTag'))]", "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], subscription().subscriptionId)]", "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], resourceGroup().name)]", "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", @@ -2684,21 +2588,22 @@ { "type": "Microsoft.Web/serverfarms", "apiVersion": "2020-06-01", - "name": "[parameters('HostingPlanName')]", + "name": "[parameters('hostingPlanName')]", "location": "[parameters('solutionLocation')]", "sku": { - "name": "[parameters('HostingPlanSku')]" + "name": "[parameters('hostingPlanSku')]" }, "properties": { - "name": "[parameters('HostingPlanName')]", + "name": "[parameters('hostingPlanName')]", "reserved": true }, - "kind": "linux" + "kind": "linux", + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Web/sites", "apiVersion": "2020-06-01", - "name": "[parameters('WebsiteName')]", + "name": "[parameters('websiteName')]", "location": "[parameters('solutionLocation')]", "identity": { "type": "SystemAssigned, UserAssigned", @@ -2707,12 +2612,12 @@ } }, "properties": { - "serverFarmId": "[parameters('HostingPlanName')]", + "serverFarmId": "[parameters('hostingPlanName')]", "siteConfig": { "appSettings": [ { "name": "APP_ENV", - "value": "[parameters('AppEnvironment')]" + "value": "[parameters('appEnvironment')]" }, { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", @@ -2724,107 +2629,107 @@ }, { "name": "AZURE_SEARCH_SERVICE", - "value": "[parameters('AzureSearchService')]" + "value": "[parameters('azureSearchService')]" }, { "name": "AZURE_SEARCH_INDEX", - "value": "[parameters('AzureSearchIndex')]" + "value": "[parameters('azureSearchIndex')]" }, { "name": "AZURE_SEARCH_USE_SEMANTIC_SEARCH", - "value": "[parameters('AzureSearchUseSemanticSearch')]" + "value": "[parameters('azureSearchUseSemanticSearch')]" }, { "name": "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG", - "value": "[parameters('AzureSearchSemanticSearchConfig')]" + "value": "[parameters('azureSearchSemanticSearchConfig')]" }, { "name": "AZURE_SEARCH_TOP_K", - "value": "[parameters('AzureSearchTopK')]" + "value": "[parameters('azureSearchTopK')]" }, { "name": "AZURE_SEARCH_ENABLE_IN_DOMAIN", - "value": "[parameters('AzureSearchEnableInDomain')]" + "value": "[parameters('azureSearchEnableInDomain')]" }, { "name": "AZURE_SEARCH_CONTENT_COLUMNS", - "value": "[parameters('AzureSearchContentColumns')]" + "value": "[parameters('azureSearchContentColumns')]" }, { "name": "AZURE_SEARCH_FILENAME_COLUMN", - "value": "[parameters('AzureSearchFilenameColumn')]" + "value": "[parameters('azureSearchFilenameColumn')]" }, { "name": "AZURE_SEARCH_TITLE_COLUMN", - "value": "[parameters('AzureSearchTitleColumn')]" + "value": "[parameters('azureSearchTitleColumn')]" }, { "name": "AZURE_SEARCH_URL_COLUMN", - "value": "[parameters('AzureSearchUrlColumn')]" + "value": "[parameters('azureSearchUrlColumn')]" }, { "name": "AZURE_OPENAI_RESOURCE", - "value": "[parameters('AzureOpenAIResource')]" + "value": "[parameters('azureOpenAIResource')]" }, { "name": "AZURE_OPENAI_MODEL", - "value": "[parameters('AzureOpenAIModel')]" + "value": "[parameters('azureOpenAIModel')]" }, { "name": "AZURE_OPENAI_ENDPOINT", - "value": "[parameters('AzureOpenAIEndpoint')]" + "value": "[parameters('azureOpenAIEndpoint')]" }, { "name": "AZURE_OPENAI_TEMPERATURE", - "value": "[parameters('AzureOpenAITemperature')]" + "value": "[parameters('azureOpenAITemperature')]" }, { "name": "AZURE_OPENAI_TOP_P", - "value": "[parameters('AzureOpenAITopP')]" + "value": "[parameters('azureOpenAITopP')]" }, { "name": "AZURE_OPENAI_MAX_TOKENS", - "value": "[parameters('AzureOpenAIMaxTokens')]" + "value": "[parameters('azureOpenAIMaxTokens')]" }, { "name": "AZURE_OPENAI_STOP_SEQUENCE", - "value": "[parameters('AzureOpenAIStopSequence')]" + "value": "[parameters('azureOpenAIStopSequence')]" }, { "name": "AZURE_OPENAI_SYSTEM_MESSAGE", - "value": "[parameters('AzureOpenAISystemMessage')]" + "value": "[parameters('azureOpenAISystemMessage')]" }, { "name": "AZURE_OPENAI_PREVIEW_API_VERSION", - "value": "[parameters('AzureOpenAIApiVersion')]" + "value": "[parameters('azureOpenAIApiVersion')]" }, { "name": "AZURE_OPENAI_STREAM", - "value": "[parameters('AzureOpenAIStream')]" + "value": "[parameters('azureOpenAIStream')]" }, { "name": "AZURE_SEARCH_QUERY_TYPE", - "value": "[parameters('AzureSearchQueryType')]" + "value": "[parameters('azureSearchQueryType')]" }, { "name": "AZURE_SEARCH_VECTOR_COLUMNS", - "value": "[parameters('AzureSearchVectorFields')]" + "value": "[parameters('azureSearchVectorFields')]" }, { "name": "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN", - "value": "[parameters('AzureSearchPermittedGroupsField')]" + "value": "[parameters('azureSearchPermittedGroupsField')]" }, { "name": "AZURE_SEARCH_STRICTNESS", - "value": "[parameters('AzureSearchStrictness')]" + "value": "[parameters('azureSearchStrictness')]" }, { "name": "AZURE_OPENAI_EMBEDDING_NAME", - "value": "[parameters('AzureOpenAIEmbeddingName')]" + "value": "[parameters('azureOpenAIEmbeddingName')]" }, { "name": "AZURE_OPENAI_EMBEDDING_ENDPOINT", - "value": "[parameters('AzureOpenAIEmbeddingEndpoint')]" + "value": "[parameters('azureOpenAIEmbeddingEndpoint')]" }, { "name": "SQLDB_SERVER", @@ -2884,22 +2789,23 @@ }, { "name": "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME", - "value": "[parameters('AzureOpenAIModel')]" + "value": "[parameters('azureOpenAIModel')]" }, { "name": "AZURE_AI_AGENT_API_VERSION", - "value": "[parameters('AzureOpenAIApiVersion')]" + "value": "[parameters('azureOpenAIApiVersion')]" }, { "name": "AZURE_SEARCH_CONNECTION_NAME", "value": "[parameters('aiSearchProjectConnectionName')]" } ], - "linuxFxVersion": "[variables('WebAppImageName')]" + "linuxFxVersion": "[variables('webAppImageName')]" } }, + "tags": "[parameters('tags')]", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('HostingPlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]" ] }, { @@ -2908,18 +2814,18 @@ "apiVersion": "2022-04-01", "name": "[guid(resourceGroup().id, extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]", "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('WebsiteName')), '2020-06-01', 'full').identity.principalId]", + "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]", "roleDefinitionId": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]", "principalType": "ServicePrincipal" }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('WebsiteName'))]" + "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" ] }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('cosmos-sql-user-role-{0}', parameters('WebsiteName'))]", + "name": "[format('cosmos-sql-user-role-{0}', parameters('websiteName'))]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2933,7 +2839,7 @@ "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', split(format('{0}/00000000-0000-0000-0000-000000000002', parameters('AZURE_COSMOSDB_ACCOUNT')), '/')[0], split(format('{0}/00000000-0000-0000-0000-000000000002', parameters('AZURE_COSMOSDB_ACCOUNT')), '/')[1])]" }, "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', parameters('WebsiteName')), '2020-06-01', 'full').identity.principalId]" + "value": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]" } }, "template": { @@ -2942,21 +2848,30 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9287160422728403181" + "version": "0.37.4.10188", + "templateHash": "13822905633352095375" }, "description": "Creates a SQL role assignment under an Azure Cosmos DB account." }, "parameters": { "accountName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the Azure Cosmos DB account." + } }, "roleDefinitionId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. ID of the Cosmos DB SQL role definition." + } }, "principalId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Otional. Principal ID to assign the role to." + } } }, "resources": [ @@ -2974,7 +2889,7 @@ } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('WebsiteName'))]" + "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" ] }, { @@ -2991,17 +2906,20 @@ "mode": "Incremental", "parameters": { "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', parameters('WebsiteName')), '2020-06-01', 'full').identity.principalId]" + "value": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]" }, "roleDefinitionId": { "value": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]" }, "roleAssignmentName": { - "value": "[guid(parameters('WebsiteName'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]" + "value": "[guid(parameters('websiteName'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]" }, "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIServicesName')), createObject('value', parameters('aiFoundryName')))]", "aiProjectName": { "value": "[variables('existingAIProjectName')]" + }, + "tags": { + "value": "[parameters('tags')]" } }, "template": { @@ -3010,32 +2928,57 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9088273662782005929" + "version": "0.37.4.10188", + "templateHash": "14256377996349985323" } }, "parameters": { "principalId": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Principal ID to assign the role to." + } }, "roleDefinitionId": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. ID of the role definition to assign." + } }, "roleAssignmentName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the role assignment." + } }, "aiFoundryName": { - "type": "string" + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry resource." + } }, "aiProjectName": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the AI project." + } }, "aiModelDeployments": { "type": "array", - "defaultValue": [] + "defaultValue": [], + "metadata": { + "description": "Optional. List of AI model deployments." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } } }, "resources": [ @@ -3060,7 +3003,8 @@ "sku": { "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" - } + }, + "tags": "[parameters('tags')]" }, { "type": "Microsoft.Authorization/roleAssignments", @@ -3077,28 +3021,40 @@ "outputs": { "aiServicesPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Services resource." + }, "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), '2025-04-01-preview', 'full').identity.principalId]" }, "aiProjectPrincipalId": { "type": "string", + "metadata": { + "description": "Principal ID of the AI Project resource if defined." + }, "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" } } } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('WebsiteName'))]" + "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" ] } ], "outputs": { "webAppUrl": { "type": "string", - "value": "[format('https://{0}.azurewebsites.net', parameters('WebsiteName'))]" + "metadata": { + "description": "URL of the deployed web application." + }, + "value": "[format('https://{0}.azurewebsites.net', parameters('websiteName'))]" }, "webAppName": { "type": "string", - "value": "[parameters('WebsiteName')]" + "metadata": { + "description": "Name of the deployed web application." + }, + "value": "[parameters('websiteName')]" } } } @@ -3114,238 +3070,415 @@ "outputs": { "WEB_APP_URL": { "type": "string", + "metadata": { + "description": "URL of the deployed web application." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppUrl.value]" }, "STORAGE_ACCOUNT_NAME": { "type": "string", + "metadata": { + "description": "Name of the storage account." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_storage_account'), '2022-09-01').outputs.storageName.value]" }, "STORAGE_CONTAINER_NAME": { "type": "string", + "metadata": { + "description": "Name of the storage container." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_storage_account'), '2022-09-01').outputs.storageContainer.value]" }, "KEY_VAULT_NAME": { "type": "string", + "metadata": { + "description": "Name of the Key Vault." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" }, "COSMOSDB_ACCOUNT_NAME": { "type": "string", + "metadata": { + "description": "Name of the Cosmos DB account." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" }, "RESOURCE_GROUP_NAME": { "type": "string", + "metadata": { + "description": "Name of the resource group." + }, "value": "[resourceGroup().name]" }, "RESOURCE_GROUP_NAME_FOUNDRY": { "type": "string", + "metadata": { + "description": "Name of the resource group used by AI Foundry." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.resourceGroupNameFoundry.value]" }, "SQLDB_SERVER_NAME": { "type": "string", + "metadata": { + "description": "Name of the SQL Database server." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value]" }, "SQLDB_DATABASE": { "type": "string", + "metadata": { + "description": "Name of the SQL Database." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlDbName.value]" }, "MANAGEDIDENTITY_WEBAPP_NAME": { "type": "string", + "metadata": { + "description": "Name of the managed identity used by the web app." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.name]" }, "MANAGEDIDENTITY_WEBAPP_CLIENTID": { "type": "string", + "metadata": { + "description": "Client ID of the managed identity used by the web app." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" }, "AI_FOUNDRY_NAME": { "type": "string", + "metadata": { + "description": "Name of the AI Foundry resource." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" }, "AI_SEARCH_SERVICE_NAME": { "type": "string", + "metadata": { + "description": "Name of the AI Search service." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" }, "WEB_APP_NAME": { "type": "string", + "metadata": { + "description": "Name of the deployed web application." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppName.value]" }, "APP_ENV": { "type": "string", + "metadata": { + "description": "Specifies the current application environment." + }, "value": "[variables('appEnvironment')]" }, "APPINSIGHTS_INSTRUMENTATIONKEY": { "type": "string", + "metadata": { + "description": "The Application Insights instrumentation key." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.instrumentationKey.value]" }, "APPLICATIONINSIGHTS_CONNECTION_STRING": { "type": "string", + "metadata": { + "description": "The Application Insights connection string." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" }, "AZURE_AI_AGENT_API_VERSION": { "type": "string", + "metadata": { + "description": "The API version used for the Azure AI Agent service." + }, "value": "[parameters('azureOpenaiAPIVersion')]" }, "AZURE_AI_AGENT_ENDPOINT": { "type": "string", + "metadata": { + "description": "The endpoint URL of the Azure AI Agent project." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryProjectEndpoint.value]" }, "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": { "type": "string", + "metadata": { + "description": "The deployment name of the GPT model for the Azure AI Agent." + }, "value": "[parameters('gptModelName')]" }, "AZURE_AI_SEARCH_ENDPOINT": { "type": "string", + "metadata": { + "description": "The endpoint URL of the Azure AI Search service." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchTarget.value]" }, "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT": { "type": "string", + "metadata": { + "description": "The system prompt used for call transcript processing in Azure Functions." + }, "value": "[variables('functionAppCallTranscriptSystemPrompt')]" }, "AZURE_COSMOSDB_ACCOUNT": { "type": "string", + "metadata": { + "description": "The name of the Azure Cosmos DB account." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" }, "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { "type": "string", + "metadata": { + "description": "The name of the Azure Cosmos DB container for storing conversations." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosContainerName.value]" }, "AZURE_COSMOSDB_DATABASE": { "type": "string", + "metadata": { + "description": "The name of the Azure Cosmos DB database." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" }, "AZURE_COSMOSDB_ENABLE_FEEDBACK": { "type": "string", + "metadata": { + "description": "Indicates whether feedback is enabled in Azure Cosmos DB." + }, "value": "[variables('azureCosmosDbEnableFeedback')]" }, "AZURE_OPENAI_EMBEDDING_ENDPOINT": { "type": "string", + "metadata": { + "description": "The endpoint URL for the Azure OpenAI Embedding model." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, "AZURE_OPENAI_EMBEDDING_NAME": { "type": "string", + "metadata": { + "description": "The name of the Azure OpenAI Embedding model." + }, "value": "[parameters('embeddingModel')]" }, "AZURE_OPENAI_ENDPOINT": { "type": "string", + "metadata": { + "description": "The endpoint URL for the Azure OpenAI service." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" }, "AZURE_OPENAI_MAX_TOKENS": { "type": "string", + "metadata": { + "description": "The maximum number of tokens for Azure OpenAI responses." + }, "value": "[variables('azureOpenAIMaxTokens')]" }, "AZURE_OPENAI_MODEL": { "type": "string", + "metadata": { + "description": "The name of the Azure OpenAI GPT model." + }, "value": "[parameters('gptModelName')]" }, "AZURE_OPENAI_PREVIEW_API_VERSION": { "type": "string", + "metadata": { + "description": "The preview API version for Azure OpenAI." + }, "value": "[parameters('azureOpenaiAPIVersion')]" }, "AZURE_OPENAI_RESOURCE": { "type": "string", + "metadata": { + "description": "The Azure OpenAI resource name." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" }, "AZURE_OPENAI_STOP_SEQUENCE": { "type": "string", + "metadata": { + "description": "The stop sequence(s) for Azure OpenAI responses." + }, "value": "[variables('azureOpenAIStopSequence')]" }, "AZURE_OPENAI_STREAM": { "type": "string", + "metadata": { + "description": "Indicates whether streaming is enabled for Azure OpenAI responses." + }, "value": "[variables('azureOpenAIStream')]" }, "AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT": { "type": "string", + "metadata": { + "description": "The system prompt for streaming text responses in Azure Functions." + }, "value": "[variables('functionAppStreamTextSystemPrompt')]" }, "AZURE_OPENAI_SYSTEM_MESSAGE": { "type": "string", + "metadata": { + "description": "The system message for Azure OpenAI requests." + }, "value": "[variables('azureOpenAISystemMessage')]" }, "AZURE_OPENAI_TEMPERATURE": { "type": "string", + "metadata": { + "description": "The temperature setting for Azure OpenAI responses." + }, "value": "[variables('azureOpenAITemperature')]" }, "AZURE_OPENAI_TOP_P": { "type": "string", + "metadata": { + "description": "The Top-P setting for Azure OpenAI responses." + }, "value": "[variables('azureOpenAITopP')]" }, "AZURE_SEARCH_CONNECTION_NAME": { "type": "string", + "metadata": { + "description": "The name of the Azure AI Search connection." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" }, "AZURE_SEARCH_CONTENT_COLUMNS": { "type": "string", + "metadata": { + "description": "The columns in Azure AI Search that contain content." + }, "value": "[variables('azureSearchContentColumns')]" }, "AZURE_SEARCH_ENABLE_IN_DOMAIN": { "type": "string", + "metadata": { + "description": "Indicates whether in-domain filtering is enabled for Azure AI Search." + }, "value": "[variables('azureSearchEnableInDomain')]" }, "AZURE_SEARCH_FILENAME_COLUMN": { "type": "string", + "metadata": { + "description": "The filename column used in Azure AI Search." + }, "value": "[variables('azureSearchFilenameColumn')]" }, "AZURE_SEARCH_INDEX": { "type": "string", + "metadata": { + "description": "The name of the Azure AI Search index." + }, "value": "[variables('azureSearchIndex')]" }, "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN": { "type": "string", + "metadata": { + "description": "The permitted groups field used in Azure AI Search." + }, "value": "[variables('azureSearchPermittedGroupsField')]" }, "AZURE_SEARCH_QUERY_TYPE": { "type": "string", + "metadata": { + "description": "The query type for Azure AI Search." + }, "value": "[variables('azureSearchQueryType')]" }, "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG": { "type": "string", + "metadata": { + "description": "The semantic search configuration name in Azure AI Search." + }, "value": "[variables('azureSearchSemanticSearchConfig')]" }, "AZURE_SEARCH_SERVICE": { "type": "string", + "metadata": { + "description": "The name of the Azure AI Search service." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" }, "AZURE_SEARCH_STRICTNESS": { "type": "string", + "metadata": { + "description": "The strictness setting for Azure AI Search semantic ranking." + }, "value": "[variables('azureSearchStrictness')]" }, "AZURE_SEARCH_TITLE_COLUMN": { "type": "string", + "metadata": { + "description": "The title column used in Azure AI Search." + }, "value": "[variables('azureSearchTitleColumn')]" }, "AZURE_SEARCH_TOP_K": { "type": "string", + "metadata": { + "description": "The number of top results (K) to return from Azure AI Search." + }, "value": "[variables('azureSearchTopK')]" }, "AZURE_SEARCH_URL_COLUMN": { "type": "string", + "metadata": { + "description": "The URL column used in Azure AI Search." + }, "value": "[variables('azureSearchUrlColumn')]" }, "AZURE_SEARCH_USE_SEMANTIC_SEARCH": { "type": "string", + "metadata": { + "description": "Indicates whether semantic search is used in Azure AI Search." + }, "value": "[variables('azureSearchUseSemanticSearch')]" }, "AZURE_SEARCH_VECTOR_COLUMNS": { "type": "string", + "metadata": { + "description": "The vector fields used in Azure AI Search." + }, "value": "[variables('azureSearchVectorFields')]" }, "AZURE_SQL_SYSTEM_PROMPT": { "type": "string", + "metadata": { + "description": "The system prompt for SQL queries in Azure Functions." + }, "value": "[variables('functionAppSqlPrompt')]" }, "SQLDB_SERVER": { "type": "string", + "metadata": { + "description": "The fully qualified domain name (FQDN) of the Azure SQL Server." + }, "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" }, "SQLDB_USER_MID": { "type": "string", + "metadata": { + "description": "The client ID of the managed identity for the web application." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" }, "USE_AI_PROJECT_CLIENT": { "type": "string", + "metadata": { + "description": "Indicates whether the AI Project Client should be used." + }, "value": "[variables('useAIProjectClientFlag')]" }, "USE_INTERNAL_STREAM": { "type": "string", + "metadata": { + "description": "Indicates whether the internal stream should be used." + }, "value": "[variables('useInternalStream')]" } } From a911f20712584c747f3d868f9db3cd574e687833 Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Tue, 12 Aug 2025 11:36:25 +0530 Subject: [PATCH 19/84] updated main.json file --- infra/main.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/infra/main.json b/infra/main.json index 01dfc0e18..5ec0bea04 100644 --- a/infra/main.json +++ b/infra/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11128400850485102096" + "version": "0.37.4.10188", + "templateHash": "4697605839482939008" } }, "parameters": { @@ -594,8 +594,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "16824105341593554305" + "version": "0.37.4.10188", + "templateHash": "651349839825117270" } }, "parameters": { @@ -3116,6 +3116,9 @@ }, "AI_FOUNDRY_RESOURCE_ID": { "type": "string", + "metadata": { + "description": "The resource ID of the AI Foundry instance." + }, "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryId.value]" }, "SQLDB_SERVER_NAME": { From d3ed0ee218dd9cc8a9a1a459200f4d84ddbb0974 Mon Sep 17 00:00:00 2001 From: Bangarraju-Microsoft Date: Tue, 12 Aug 2025 11:39:23 +0530 Subject: [PATCH 20/84] updated yml file --- .github/workflows/CAdeploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index b78b0d1c4..c18e7e748 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -148,7 +148,7 @@ jobs: --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT" - if [[ -z "$DEPLOY_OUTPUT" ]]; thenSOLUTION_PREFIX + if [[ -z "$DEPLOY_OUTPUT" ]]; then echo "Error: Deployment output is empty. Please check the deployment logs." exit 1 fi From 074208074095a1952ca8a0e48fd2eb1846f0f34c Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Tue, 12 Aug 2025 12:01:45 +0530 Subject: [PATCH 21/84] test --- .github/workflows/CAdeploy.yml | 1 + infra/scripts/run_create_index_scripts.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index ae964dbed..bf00ce706 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -6,6 +6,7 @@ on: - main - dev - demo + - git-actions schedule: - cron: "0 6,18 * * *" # Runs at 6:00 AM and 6:00 PM GMT diff --git a/infra/scripts/run_create_index_scripts.sh b/infra/scripts/run_create_index_scripts.sh index 2f47638d5..283623aba 100644 --- a/infra/scripts/run_create_index_scripts.sh +++ b/infra/scripts/run_create_index_scripts.sh @@ -72,6 +72,9 @@ fi # Check if the user has the Azure AI User role echo "Checking if user has the Azure AI User role" role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --assignee $signed_user_id --query "[].roleDefinitionId" -o tsv) +echo "role_assignment: $role_assignment" +echo "aif_resource_id: $aif_resource_id" +echo "signed_user_id: $signed_user_id" if [ -z "$role_assignment" ]; then echo "User does not have the Azure AI User role. Assigning the role." MSYS_NO_PATHCONV=1 az role assignment create --assignee $signed_user_id --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --output none From edd0c97b447ea8683324646da314e51aeedb29c1 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Tue, 12 Aug 2025 13:23:30 +0530 Subject: [PATCH 22/84] test --- infra/scripts/run_create_index_scripts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/scripts/run_create_index_scripts.sh b/infra/scripts/run_create_index_scripts.sh index 283623aba..4399ba6d7 100644 --- a/infra/scripts/run_create_index_scripts.sh +++ b/infra/scripts/run_create_index_scripts.sh @@ -68,7 +68,7 @@ fi ### Assign Azure AI User role to the signed in user ### - +echo "Azure AI resource id: $aif_resource_id" # Check if the user has the Azure AI User role echo "Checking if user has the Azure AI User role" role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --assignee $signed_user_id --query "[].roleDefinitionId" -o tsv) From 9b812cc5c701bacfee2befe49bfb03f25a8ff102 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Tue, 12 Aug 2025 14:00:31 +0530 Subject: [PATCH 23/84] update --- infra/scripts/process_sample_data.sh | 4 ---- infra/scripts/run_create_index_scripts.sh | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index d457c963b..aa8b3e291 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -51,10 +51,6 @@ if [ -z "$webAppManagedIdentityDisplayName" ]; then webAppManagedIdentityDisplayName=$(azd env get-value MANAGEDIDENTITY_WEBAPP_NAME) fi -if [ -z "$aiFoundryName" ]; then - aiFoundryName=$(azd env get-value AI_FOUNDRY_NAME) -fi - if [ -z "$aiSearchName" ]; then aiSearchName=$(azd env get-value AI_SEARCH_SERVICE_NAME) fi diff --git a/infra/scripts/run_create_index_scripts.sh b/infra/scripts/run_create_index_scripts.sh index 4399ba6d7..77375b815 100644 --- a/infra/scripts/run_create_index_scripts.sh +++ b/infra/scripts/run_create_index_scripts.sh @@ -10,7 +10,7 @@ aiSearchName="$6" aif_resource_id="$7" echo "Script Started" - +echo "aif_resource_id in run_create: $aif_resource_id" # Authenticate with Azure if az account show &> /dev/null; then echo "Already authenticated with Azure." @@ -72,9 +72,6 @@ echo "Azure AI resource id: $aif_resource_id" # Check if the user has the Azure AI User role echo "Checking if user has the Azure AI User role" role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --assignee $signed_user_id --query "[].roleDefinitionId" -o tsv) -echo "role_assignment: $role_assignment" -echo "aif_resource_id: $aif_resource_id" -echo "signed_user_id: $signed_user_id" if [ -z "$role_assignment" ]; then echo "User does not have the Azure AI User role. Assigning the role." MSYS_NO_PATHCONV=1 az role assignment create --assignee $signed_user_id --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --output none From a2328c0c4f38c6eaacaab3792758cd9fc19c41d9 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Tue, 12 Aug 2025 15:58:48 +0530 Subject: [PATCH 24/84] update --- .github/workflows/CAdeploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index bf00ce706..7283b6140 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -154,7 +154,7 @@ jobs: exit 1 fi - export AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.AI_FOUNDRY_RESOURCE_ID.value') + export AI_FOUNDRY_RESOURCE_ID=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_FOUNDRY_RESOURCE_ID.value') echo "AI_FOUNDRY_RESOURCE_ID=$AI_FOUNDRY_RESOURCE_ID" >> $GITHUB_ENV export SEARCH_SERVICE_NAME=$(echo "$DEPLOY_OUTPUT" | jq -r '.aI_SEARCH_SERVICE_NAME.value') echo "SEARCH_SERVICE_NAME=$SEARCH_SERVICE_NAME" >> $GITHUB_ENV From b84cd26a91979400aca70eca854ceb02740c387d Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Tue, 12 Aug 2025 20:05:57 +0530 Subject: [PATCH 25/84] test --- infra/scripts/run_create_index_scripts.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infra/scripts/run_create_index_scripts.sh b/infra/scripts/run_create_index_scripts.sh index 77375b815..ad4878d43 100644 --- a/infra/scripts/run_create_index_scripts.sh +++ b/infra/scripts/run_create_index_scripts.sh @@ -10,7 +10,7 @@ aiSearchName="$6" aif_resource_id="$7" echo "Script Started" -echo "aif_resource_id in run_create: $aif_resource_id" + # Authenticate with Azure if az account show &> /dev/null; then echo "Already authenticated with Azure." @@ -68,7 +68,6 @@ fi ### Assign Azure AI User role to the signed in user ### -echo "Azure AI resource id: $aif_resource_id" # Check if the user has the Azure AI User role echo "Checking if user has the Azure AI User role" role_assignment=$(MSYS_NO_PATHCONV=1 az role assignment list --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope $aif_resource_id --assignee $signed_user_id --query "[].roleDefinitionId" -o tsv) From f4b5b14f4a7795877b903e6744d2cc68232cd588 Mon Sep 17 00:00:00 2001 From: "Kanchan Nagshetti (Persistent Systems Inc)" Date: Wed, 13 Aug 2025 10:44:00 +0530 Subject: [PATCH 26/84] update CAdeploy.yml --- .github/workflows/CAdeploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 7283b6140..0424010ac 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -6,7 +6,6 @@ on: - main - dev - demo - - git-actions schedule: - cron: "0 6,18 * * *" # Runs at 6:00 AM and 6:00 PM GMT From fe7c161994ed9842547331031ff8f11e58d87a28 Mon Sep 17 00:00:00 2001 From: UtkarshMishra-Microsoft Date: Wed, 13 Aug 2025 12:49:26 +0530 Subject: [PATCH 27/84] CustomizingAzdparamMd --- docs/CustomizingAzdParameters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CustomizingAzdParameters.md b/docs/CustomizingAzdParameters.md index c3ac28645..d37917948 100644 --- a/docs/CustomizingAzdParameters.md +++ b/docs/CustomizingAzdParameters.md @@ -18,7 +18,7 @@ By default this template will use the environment name as the prefix to prevent | `AZURE_ENV_EMBEDDING_MODEL_NAME` | string | `text-embedding-ada-002` | Set the model name used for embeddings. | | `AZURE_ENV_EMBEDDING_MODEL_CAPACITY` | integer | `80` | Set the capacity for embedding model deployment. | | `AZURE_ENV_IMAGETAG` | string | `latest` | Set the image tag (allowed values: `latest`, `dev`, `hotfix`). | -| `AZURE_LOCATION` | string | `japaneast` | Sets the Azure region for resource deployment. | +| `AZURE_LOCATION` | string | `` | Sets the Azure region for resource deployment. | | `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of provisioning a new one. | | `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` | string | `` | Reuses an existing AI Foundry Project Resource Id instead of provisioning a new one. | From c38e7a95d6cefce15ae07d3d3cebc7fd9344ff4d Mon Sep 17 00:00:00 2001 From: UtkarshMishra-Microsoft Date: Thu, 14 Aug 2025 12:01:18 +0530 Subject: [PATCH 28/84] deploymentMdUpdate --- docs/DeploymentGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index b743f27d0..f994405a4 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -116,7 +116,7 @@ When you start the deployment, most parameters will have **default values**, but | **Embedding Model Capacity** | Set the capacity for **embedding models**. Choose based on usage and quota. | `80` | | **Image Tag** | The version of the Docker image to use (e.g., `latest`, `dev`, `hotfix`). | `latest` | | **Azure OpenAI API Version** | Set the API version for OpenAI model deployments. | `2025-04-01-preview` | -| **AZURE\_LOCATION** | Sets the Azure region for resource deployment. | `japaneast` | +| **AZURE\_LOCATION** | Sets the Azure region for resource deployment. | `` | | **Existing Log Analytics Workspace** | To reuse an existing Log Analytics Workspace ID instead of creating a new one. | *(empty)* | | **Existing AI Foundry Project Resource ID** | To reuse an existing AI Foundry Project Resource ID instead of creating a new one. | *(empty)* | From 55aa714fa38574567478d6d2f0828330252f5505 Mon Sep 17 00:00:00 2001 From: UtkarshMishra-Microsoft Date: Thu, 14 Aug 2025 12:09:55 +0530 Subject: [PATCH 29/84] Updated --- docs/DeploymentGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index f994405a4..2af2e32f5 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -116,7 +116,7 @@ When you start the deployment, most parameters will have **default values**, but | **Embedding Model Capacity** | Set the capacity for **embedding models**. Choose based on usage and quota. | `80` | | **Image Tag** | The version of the Docker image to use (e.g., `latest`, `dev`, `hotfix`). | `latest` | | **Azure OpenAI API Version** | Set the API version for OpenAI model deployments. | `2025-04-01-preview` | -| **AZURE\_LOCATION** | Sets the Azure region for resource deployment. | `` | +| **AZURE_LOCATION** | Sets the Azure region for resource deployment. | `` | | **Existing Log Analytics Workspace** | To reuse an existing Log Analytics Workspace ID instead of creating a new one. | *(empty)* | | **Existing AI Foundry Project Resource ID** | To reuse an existing AI Foundry Project Resource ID instead of creating a new one. | *(empty)* | From 39e008534354ce0209c4e04d8588b4c268b3da34 Mon Sep 17 00:00:00 2001 From: Prasanjeet-Microsoft Date: Mon, 18 Aug 2025 12:02:53 +0530 Subject: [PATCH 30/84] Update infra/main.bicep Fixed typo error. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 80ccee8b7..dcfe484e0 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -78,7 +78,7 @@ param imageTag string = 'latest' ] } }) -@description('Rquired. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') +@description('Required. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') param aiDeploymentsLocation string @description('Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') From a1b363d231066ba674c7ea3a9d6b76007a752589 Mon Sep 17 00:00:00 2001 From: Prasanjeet-Microsoft Date: Mon, 18 Aug 2025 12:06:57 +0530 Subject: [PATCH 31/84] Update src/App/backend/plugins/chat_with_data_plugin.py Updated the finally block for function "get_SQL_Response" Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/App/backend/plugins/chat_with_data_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/backend/plugins/chat_with_data_plugin.py b/src/App/backend/plugins/chat_with_data_plugin.py index 605ca5d42..500d8255a 100644 --- a/src/App/backend/plugins/chat_with_data_plugin.py +++ b/src/App/backend/plugins/chat_with_data_plugin.py @@ -107,7 +107,7 @@ async def get_SQL_Response( logging.exception("Error in get_SQL_Response") return f"Error retrieving SQL data: {str(e)}" finally: - if thread: + if thread and project_client: try: logging.info(f"Attempting to delete thread {thread.id}") await project_client.agents.threads.delete(thread.id) From 4f81d2aee5353a56979049753aa5ee108dae8029 Mon Sep 17 00:00:00 2001 From: Prasanjeet-Microsoft Date: Mon, 18 Aug 2025 13:05:03 +0530 Subject: [PATCH 32/84] Update infra/main.bicep Removed Commented-out code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- infra/main.bicep | 1 - 1 file changed, 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index dcfe484e0..c1c4906e8 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -85,7 +85,6 @@ param aiDeploymentsLocation string param AZURE_LOCATION string = '' var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION -//var uniqueId = toLower(uniqueString(solutionName , subscription().id, solutionLocation, resourceGroup().name)) //var solutionSuffix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' @maxLength(5) From 12ca750ccab2734538343d5717683185d989fe65 Mon Sep 17 00:00:00 2001 From: Prasanjeet-Microsoft Date: Mon, 18 Aug 2025 19:38:36 +0530 Subject: [PATCH 33/84] Revert Copilot suggestion: `and project_client` is unnecessary. `project_client` is always initialized before thread creation, so `if thread:` is sufficient. --- src/App/backend/plugins/chat_with_data_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/backend/plugins/chat_with_data_plugin.py b/src/App/backend/plugins/chat_with_data_plugin.py index 500d8255a..605ca5d42 100644 --- a/src/App/backend/plugins/chat_with_data_plugin.py +++ b/src/App/backend/plugins/chat_with_data_plugin.py @@ -107,7 +107,7 @@ async def get_SQL_Response( logging.exception("Error in get_SQL_Response") return f"Error retrieving SQL data: {str(e)}" finally: - if thread and project_client: + if thread: try: logging.info(f"Attempting to delete thread {thread.id}") await project_client.agents.threads.delete(thread.id) From 4fa1c7df5954391af26690f9e68a97510cd8bd2f Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 12:27:46 +0530 Subject: [PATCH 34/84] Avm Waf changes-1 --- infra/main.bicep | 808 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 769 insertions(+), 39 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index c1c4906e8..c89ca151d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -101,6 +101,18 @@ var solutionSuffix= toLower(trim(replace( '' ))) +@description('Optional. Enable private networking for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') +param enablePrivateNetworking bool = false + +@description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') +param enableMonitoring bool = false + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. Enable redundancy for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') +param enableRedundancy bool = false + // Load the abbrevations file required to name the azure resources. //var abbrs = loadJsonContent('./abbreviations.json') @@ -171,11 +183,42 @@ var functionAppStreamTextSystemPrompt = '''The currently selected client's name If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response. Always send clientId as '{client_id}'.''' +// Replica regions list based on article in [Azure regions list](https://learn.microsoft.com/azure/reliability/regions-list) and [Enhance resilience by replicating your Log Analytics workspace across regions](https://learn.microsoft.com/azure/azure-monitor/logs/workspace-replication#supported-regions) for supported regions for Log Analytics Workspace. +var replicaRegionPairs = { + australiaeast: 'australiasoutheast' + centralus: 'westus' + eastasia: 'japaneast' + eastus: 'centralus' + eastus2: 'centralus' + japaneast: 'eastasia' + northeurope: 'westeurope' + southeastasia: 'eastasia' + uksouth: 'westeurope' + westeurope: 'northeurope' +} +var replicaLocation = replicaRegionPairs[resourceGroup().location] + @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' +// Region pairs list based on article in [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions) for supported high availability regions for CosmosDB. +var cosmosDbZoneRedundantHaRegionPairs = { + australiaeast: 'uksouth' //'southeastasia' + centralus: 'eastus2' + eastasia: 'southeastasia' + eastus: 'centralus' + eastus2: 'centralus' + japaneast: 'australiaeast' + northeurope: 'westeurope' + southeastasia: 'eastasia' + uksouth: 'westeurope' + westeurope: 'northeurope' +} +// Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` +var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -187,29 +230,533 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { } } -// ========== Managed Identity ========== // -module managedIdentityModule 'deploy_managed_identity.bicep' = { - name: 'deploy_managed_identity' +// ========== User Assigned Identity ========== // +// WAF best practices for identity and access management: https://learn.microsoft.com/en-us/azure/well-architected/security/identity-access +var userAssignedIdentityResourceName = 'id-${solutionSuffix}' +module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = { + name: take('avm.res.managed-identity.user-assigned-identity.${userAssignedIdentityResourceName}', 64) params: { - solutionName: solutionSuffix - solutionLocation: solutionLocation - miName: 'id-${solutionSuffix}' + name: userAssignedIdentityResourceName + location: solutionLocation tags: tags + // enableTelemetry: enableTelemetry } - scope: resourceGroup(resourceGroup().name) } // ========== Key Vault ========== // -module keyvaultModule 'deploy_keyvault.bicep' = { - name: 'deploy_keyvault' +// module keyvaultModule 'deploy_keyvault.bicep' = { +// name: 'deploy_keyvault' +// params: { +// solutionName: solutionSuffix +// solutionLocation: solutionLocation +// managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId +// kvName: 'kv-${solutionSuffix}' +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + +var networkSecurityGroupAdministrationResourceName = 'nsg-${solutionSuffix}-administration' +module networkSecurityGroupAdministration 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupAdministrationResourceName}', 64) params: { - solutionName: solutionSuffix - solutionLocation: solutionLocation - managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - kvName: 'kv-${solutionSuffix}' + name: networkSecurityGroupAdministrationResourceName + location: solutionLocation tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: [ + '22' + '3389' + ] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } +} + + +// ========== Network Security Groups ========== // +// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network +// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking +var networkSecurityGroupBackendResourceName = 'nsg-${solutionSuffix}-backend' +module networkSecurityGroupBackend 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupBackendResourceName}', 64) + params: { + name: networkSecurityGroupBackendResourceName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: [ + '22' + '3389' + ] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } +} + + +var networkSecurityGroupBastionResourceName = 'nsg-${solutionSuffix}-bastion' +module networkSecurityGroupBastion 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupBastionResourceName}', 64) + params: { + name: networkSecurityGroupBastionResourceName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + securityRules: [ + { + name: 'AllowHttpsInBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'Internet' + destinationPortRange: '443' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'AllowGatewayManagerInBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'GatewayManager' + destinationPortRange: '443' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 110 + direction: 'Inbound' + } + } + { + name: 'AllowLoadBalancerInBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationPortRange: '443' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 120 + direction: 'Inbound' + } + } + { + name: 'AllowBastionHostCommunicationInBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '8080' + '5701' + ] + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 130 + direction: 'Inbound' + } + } + { + name: 'DenyAllInBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationPortRange: '*' + destinationAddressPrefix: '*' + access: 'Deny' + priority: 1000 + direction: 'Inbound' + } + } + { + name: 'AllowSshRdpOutBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationPortRanges: [ + '22' + '3389' + ] + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'AllowAzureCloudCommunicationOutBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationPortRange: '443' + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 110 + direction: 'Outbound' + } + } + { + name: 'AllowBastionHostCommunicationOutBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '8080' + '5701' + ] + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 120 + direction: 'Outbound' + } + } + { + name: 'AllowGetSessionInformationOutBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'Internet' + destinationPortRanges: [ + '80' + '443' + ] + access: 'Allow' + priority: 130 + direction: 'Outbound' + } + } + { + name: 'DenyAllOutBound' + properties: { + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Deny' + priority: 1000 + direction: 'Outbound' + } + } + ] + } +} + +var networkSecurityGroupContainersResourceName = 'nsg-${solutionSuffix}-containers' +module networkSecurityGroupContainers 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupContainersResourceName}', 64) + params: { + name: networkSecurityGroupContainersResourceName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: [ + '22' + '3389' + ] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } +} + +var networkSecurityGroupWebsiteResourceName = 'nsg-${solutionSuffix}-website' +module networkSecurityGroupWebsite 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupWebsiteResourceName}', 64) + params: { + name: networkSecurityGroupWebsiteResourceName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: [ + '22' + '3389' + ] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } +} + +// ========== Virtual Network ========== // +// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network +// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking +var virtualNetworkResourceName = 'vnet-${solutionSuffix}' +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.7.0' = if (enablePrivateNetworking) { + name: take('avm.res.network.virtual-network.${virtualNetworkResourceName}', 64) + params: { + name: virtualNetworkResourceName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + addressPrefixes: ['10.0.0.0/8'] + subnets: [ + { + name: 'backend' + addressPrefix: '10.0.0.0/27' + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + networkSecurityGroupResourceId: networkSecurityGroupBackend!.outputs.resourceId + } + { + name: 'administration' + addressPrefix: '10.0.0.32/27' + networkSecurityGroupResourceId: networkSecurityGroupAdministration!.outputs.resourceId + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + //natGatewayResourceId: natGateway.outputs.resourceId + } + { + // For Azure Bastion resources deployed on or after November 2, 2021, the minimum AzureBastionSubnet size is /26 or larger (/25, /24, etc.). + // https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet + name: 'AzureBastionSubnet' //This exact name is required for Azure Bastion + addressPrefix: '10.0.0.64/26' + networkSecurityGroupResourceId: networkSecurityGroupBastion!.outputs.resourceId + } + { + // If you use your own vnw, you need to provide a subnet that is dedicated exclusively to the Container App environment you deploy. This subnet isn't available to other services + // https://learn.microsoft.com/en-us/azure/container-apps/networking?tabs=workload-profiles-env%2Cazure-cli#custom-vnw-configuration + name: 'containers' + addressPrefix: '10.0.2.0/23' //subnet of size /23 is required for container app + delegation: 'Microsoft.App/environments' + networkSecurityGroupResourceId: networkSecurityGroupContainers!.outputs.resourceId + privateEndpointNetworkPolicies: 'Enabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + { + // If you use your own vnw, you need to provide a subnet that is dedicated exclusively to the App Environment you deploy. This subnet isn't available to other services + // https://learn.microsoft.com/en-us/azure/app-service/overview-vnet-integration#subnet-requirements + name: 'webserverfarm' + addressPrefix: '10.0.4.0/27' //When you're creating subnets in Azure portal as part of integrating with the virtual network, a minimum size of /27 is required + delegation: 'Microsoft.Web/serverfarms' + networkSecurityGroupResourceId: networkSecurityGroupWebsite!.outputs.resourceId + privateEndpointNetworkPolicies: 'Enabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + ] + } +} + +// ========== Private DNS Zones ========== // +var privateDnsZones = [ + 'privatelink.cognitiveservices.azure.com' + 'privatelink.openai.azure.com' + 'privatelink.services.ai.azure.com' + 'privatelink.contentunderstanding.ai.azure.com' + 'privatelink.blob.${environment().suffixes.storage}' + 'privatelink.queue.${environment().suffixes.storage}' + 'privatelink.file.${environment().suffixes.storage}' + 'privatelink.api.azureml.ms' + 'privatelink.notebooks.azure.net' + 'privatelink.mongo.cosmos.azure.com' + 'privatelink.azconfig.io' + 'privatelink.vaultcore.azure.net' + 'privatelink.azurecr.io' + 'privatelink${environment().suffixes.sqlServerHostname}' +] +// DNS Zone Index Constants +var dnsZoneIndex = { + cognitiveServices: 0 + openAI: 1 + aiServices: 2 + contentUnderstanding: 3 + storageBlob: 4 + storageQueue: 5 + storageFile: 6 + aiFoundry: 7 + notebooks: 8 + cosmosDB: 9 + appConfig: 10 + keyVault: 11 + containerRegistry: 12 + sqlServer: 13 +} +@batchSize(5) +module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ + for (zone, i) in privateDnsZones: if (enablePrivateNetworking) { + name: 'dns-zone-${i}' + params: { + name: zone + tags: tags + enableTelemetry: enableTelemetry + virtualNetworkLinks: [{ virtualNetworkResourceId: virtualNetwork!.outputs.resourceId }] + } + } +] + + +// ========== Log Analytics Workspace ========== // +// WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics +// WAF PSRules for Log Analytics: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#azure-monitor-logs +var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}' +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring) { + name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) + params: { + name: logAnalyticsWorkspaceResourceName + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + skuName: 'PerGB2018' + dataRetention: 365 + features: { enableLogAccessUsingOnlyResourcePermissions: true } + diagnosticSettings: [{ useThisWorkspace: true }] + // WAF aligned configuration for Redundancy + dailyQuotaGb: enableRedundancy ? 10 : null //WAF recommendation: 10 GB per day is a good starting point for most workloads + replication: enableRedundancy + ? { + enabled: true + location: replicaLocation + } + : null + // WAF aligned configuration for Private Networking + publicNetworkAccessForIngestion: enablePrivateNetworking ? 'Disabled' : 'Enabled' + publicNetworkAccessForQuery: enablePrivateNetworking ? 'Disabled' : 'Enabled' + dataSources: enablePrivateNetworking + ? [ + { + tags: tags + eventLogName: 'Application' + eventTypes: [ + { + eventType: 'Error' + } + { + eventType: 'Warning' + } + { + eventType: 'Information' + } + ] + kind: 'WindowsEvent' + name: 'applicationEvent' + } + { + counterName: '% Processor Time' + instanceName: '*' + intervalSeconds: 60 + kind: 'WindowsPerformanceCounter' + name: 'windowsPerfCounter1' + objectName: 'Processor' + } + { + kind: 'IISLogs' + name: 'sampleIISLog1' + state: 'OnPremiseEnabled' + } + ] + : null + } +} + +// Key Vault resource +var keyVaultName = 'KV-${solutionSuffix}' +module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { + name: take('avm.res.key-vault.vault.${keyVaultName}', 64) + params: { + name: keyVaultName + location: solutionLocation + tags: tags + sku: 'premium' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + networkAcls: { + defaultAction: 'Allow' + } + enableVaultForDeployment: true + enableVaultForDiskEncryption: true + enableVaultForTemplateDeployment: true + enableRbacAuthorization: true + enableSoftDelete: true + softDeleteRetentionInDays: 7 + diagnosticSettings: enableMonitoring + ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] + : [] + // WAF aligned configuration for Private Networking + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-${keyVaultName}' + customNetworkInterfaceName: 'nic-${keyVaultName}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.keyVault]!.outputs.resourceId}] + } + service: 'vault' + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + } + ] + : [] + // WAF aligned configuration for Role-based Access Control + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Key Vault Administrator' + } + ] + secrets: [ + { + name: 'ExampleSecret' + value: 'YourSecretValue' + } + ] + // enableTelemetry: enableTelemetry } - scope: resourceGroup(resourceGroup().name) } // ==========AI Foundry and related resources ========== // @@ -218,7 +765,7 @@ module aifoundry 'deploy_ai_foundry.bicep' = { params: { solutionName: solutionSuffix solutionLocation: aiDeploymentsLocation - keyVaultName: keyvaultModule.outputs.keyvaultName + keyVaultName: keyvault.outputs.name deploymentType: deploymentType gptModelName: gptModelName azureOpenaiAPIVersion: azureOpenaiAPIVersion @@ -234,26 +781,209 @@ module aifoundry 'deploy_ai_foundry.bicep' = { } // ========== CosmosDB ========== // -module cosmosDBModule 'deploy_cosmos_db.bicep' = { - name: 'deploy_cosmos_db' +// module cosmosDBModule 'deploy_cosmos_db.bicep' = { +// name: 'deploy_cosmos_db' +// params: { +// solutionLocation: cosmosLocation +// cosmosDBName: 'cosmos-${solutionSuffix}' +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + + +//========== AVM WAF ========== // +//========== Cosmos DB module ========== // +var cosmosDbResourceName = 'cosmos-${solutionSuffix}' +var cosmosDbDatabaseName = 'db_conversation_history' +// var cosmosDbDatabaseMemoryContainerName = 'memory' +var collectionName = 'conversations' +//TODO: update to latest version of AVM module +module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { + name: take('avm.res.document-db.database-account.${cosmosDbResourceName}', 64) params: { - solutionLocation: cosmosLocation - cosmosDBName: 'cosmos-${solutionSuffix}' + // Required parameters + name: cosmosDbResourceName + location: solutionLocation tags: tags + enableTelemetry: enableTelemetry + sqlDatabases: [ + { + name: cosmosDbDatabaseName + containers: [ + // { + // name: cosmosDbDatabaseMemoryContainerName + // paths: [ + // '/session_id' + // ] + // kind: 'Hash' + // version: 2 + // } + { + name: collectionName + paths: [ + '/userId' + ] + } + ] + } + ] + dataPlaneRoleDefinitions: [ + { + // Cosmos DB Built-in Data Contributor: https://docs.azure.cn/en-us/cosmos-db/nosql/security/reference-data-plane-roles#cosmos-db-built-in-data-contributor + roleName: 'Cosmos DB SQL Data Contributor' + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + ] + assignments: [{ principalId: userAssignedIdentity.outputs.principalId }] + } + ] + // WAF aligned configuration for Monitoring + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + // WAF aligned configuration for Private Networking + networkRestrictions: { + networkAclBypass: 'None' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + } + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-${cosmosDbResourceName}' + customNetworkInterfaceName: 'nic-${cosmosDbResourceName}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cosmosDB]!.outputs.resourceId } + ] + } + service: 'Sql' + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + } + ] + : [] + // WAF aligned configuration for Redundancy + zoneRedundant: enableRedundancy ? true : false + capabilitiesToAdd: enableRedundancy ? null : ['EnableServerless'] + automaticFailover: enableRedundancy ? true : false + failoverLocations: enableRedundancy + ? [ + { + failoverPriority: 0 + isZoneRedundant: true + locationName: solutionLocation + } + { + failoverPriority: 1 + isZoneRedundant: true + locationName: cosmosDbHaLocation + } + ] + : [ + { + locationName: solutionLocation + failoverPriority: 0 + } + ] } + dependsOn: [keyvault, avmStorageAccount] scope: resourceGroup(resourceGroup().name) } + // ========== Storage Account Module ========== // -module storageAccountModule 'deploy_storage_account.bicep' = { - name: 'deploy_storage_account' +// module storageAccountModule 'deploy_storage_account.bicep' = { +// name: 'deploy_storage_account' +// params: { +// solutionLocation: solutionLocation +// managedIdentityObjectId: userAssignedIdentity.outputs.principalId +// saName: 'st${solutionSuffix}' +// keyVaultName: keyvault.outputs.name +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + +// ========== AVM WAF ========== // +// ========== Storage account module ========== // +var storageAccountName = 'st${solutionSuffix}' +module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { + name: take('avm.res.storage.storage-account.${storageAccountName}', 64) params: { - solutionLocation: solutionLocation - managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - saName: 'st${solutionSuffix}' - keyVaultName: keyvaultModule.outputs.keyvaultName + name: storageAccountName + location: solutionLocation + managedIdentities: { systemAssigned: true } + minimumTlsVersion: 'TLS1_2' + enableTelemetry: enableTelemetry tags: tags + accessTier: 'Hot' + supportsHttpsTrafficOnly: true + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalType: 'ServicePrincipal' + } + ] + // WAF aligned networking + networkAcls: { + bypass: 'AzureServices' + defaultAction: enablePrivateNetworking ? 'Deny' : 'Allow' + } + allowBlobPublicAccess: enablePrivateNetworking ? true : false + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + // Private endpoints for blob and queue + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-blob-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'storage-dns-zone-group-blob' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'blob' + } + { + name: 'pep-queue-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'storage-dns-zone-group-queue' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageQueue]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'queue' + } + ] + : [] + blobServices: { + corsRules: [] + deleteRetentionPolicyEnabled: false + containers: [ + { + name: 'data' + publicAccess: 'None' + } + ] + } + // secretsExportConfiguration: { + // accessKey1Name: 'ADLS-ACCOUNT-NAME' + // connectionString1Name: storageAccountName + // accessKey2Name: 'ADLS-ACCOUNT-CONTAINER' + // connectionString2Name: 'data' + // accessKey3Name: 'ADLS-ACCOUNT-KEY' + // connectionString3Name: listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2021-04-01') + // keyVaultResourceId: keyvault.outputs.resourceId + // } } + dependsOn: [keyvault] scope: resourceGroup(resourceGroup().name) } @@ -262,9 +992,9 @@ module sqlDBModule 'deploy_sql_db.bicep' = { name: 'deploy_sql_db' params: { solutionLocation: solutionLocation - keyVaultName: keyvaultModule.outputs.keyvaultName - managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - managedIdentityName: managedIdentityModule.outputs.managedIdentityOutput.name + keyVaultName: keyvault.outputs.name + managedIdentityObjectId: userAssignedIdentity.outputs.principalId + managedIdentityName: userAssignedIdentity.outputs.name serverName: 'sql-${solutionSuffix}' sqlDBName: 'sqldb-${solutionSuffix}' tags: tags @@ -314,14 +1044,14 @@ module appserviceModule 'deploy_app_service.bicep' = { USE_INTERNAL_STREAM: useInternalStream SQLDB_SERVER: sqlServerFqdn SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName - AZURE_COSMOSDB_ACCOUNT: cosmosDBModule.outputs.cosmosAccountName - AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: cosmosDBModule.outputs.cosmosContainerName - AZURE_COSMOSDB_DATABASE: cosmosDBModule.outputs.cosmosDatabaseName + AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.cosmosAccountName + AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: cosmosDb.outputs.cosmosContainerName + AZURE_COSMOSDB_DATABASE: cosmosDb.outputs.cosmosDatabaseName AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback //VITE_POWERBI_EMBED_URL: 'TBD' imageTag: imageTag - userassignedIdentityClientId: managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId - userassignedIdentityId: managedIdentityModule.outputs.managedIdentityWebAppOutput.id + userassignedIdentityClientId: userAssignedIdentity.outputs.clientId + userassignedIdentityId: userAssignedIdentity.outputs.principalId applicationInsightsId: aifoundry.outputs.applicationInsightsId azureSearchServiceEndpoint: aifoundry.outputs.aiSearchTarget sqlSystemPrompt: functionAppSqlPrompt @@ -342,13 +1072,13 @@ module appserviceModule 'deploy_app_service.bicep' = { output WEB_APP_URL string = appserviceModule.outputs.webAppUrl @description('Name of the storage account.') -output STORAGE_ACCOUNT_NAME string = storageAccountModule.outputs.storageName +output STORAGE_ACCOUNT_NAME string = avmStorageAccount.outputs.name @description('Name of the storage container.') -output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageContainer +output STORAGE_CONTAINER_NAME string = avmStorageAccount.outputs.containerName @description('Name of the Key Vault.') -output KEY_VAULT_NAME string = keyvaultModule.outputs.keyvaultName +output KEY_VAULT_NAME string = keyvault.outputs.name @description('Name of the Cosmos DB account.') output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName @@ -366,10 +1096,10 @@ output SQLDB_SERVER_NAME string = sqlDBModule.outputs.sqlServerName output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName @description('Name of the managed identity used by the web app.') -output MANAGEDIDENTITY_WEBAPP_NAME string = managedIdentityModule.outputs.managedIdentityWebAppOutput.name +output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name @description('Client ID of the managed identity used by the web app.') -output MANAGEDIDENTITY_WEBAPP_CLIENTID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId +output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId @description('Name of the AI Search service.') output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService @@ -502,7 +1232,7 @@ output AZURE_SQL_SYSTEM_PROMPT string = functionAppSqlPrompt output SQLDB_SERVER string = sqlServerFqdn @description('The client ID of the managed identity for the web application.') -output SQLDB_USER_MID string = managedIdentityModule.outputs.managedIdentityWebAppOutput.clientId +output SQLDB_USER_MID string = userAssignedIdentity.outputs.clientId @description('Indicates whether the AI Project Client should be used.') output USE_AI_PROJECT_CLIENT string = useAIProjectClientFlag From e7740116444af0a094c5b18de438f3b43e9897e1 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 13:00:35 +0530 Subject: [PATCH 35/84] Avm Waf- v2 --- infra/main.bicep | 115 +++++++---------------------------------------- 1 file changed, 16 insertions(+), 99 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index c89ca151d..0c9a5c836 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -886,104 +886,21 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { } ] } - dependsOn: [keyvault, avmStorageAccount] + dependsOn: [keyvault, storageAccountModule] scope: resourceGroup(resourceGroup().name) } // ========== Storage Account Module ========== // -// module storageAccountModule 'deploy_storage_account.bicep' = { -// name: 'deploy_storage_account' -// params: { -// solutionLocation: solutionLocation -// managedIdentityObjectId: userAssignedIdentity.outputs.principalId -// saName: 'st${solutionSuffix}' -// keyVaultName: keyvault.outputs.name -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } - -// ========== AVM WAF ========== // -// ========== Storage account module ========== // -var storageAccountName = 'st${solutionSuffix}' -module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { - name: take('avm.res.storage.storage-account.${storageAccountName}', 64) +module storageAccountModule 'deploy_storage_account.bicep' = { + name: 'deploy_storage_account' params: { - name: storageAccountName - location: solutionLocation - managedIdentities: { systemAssigned: true } - minimumTlsVersion: 'TLS1_2' - enableTelemetry: enableTelemetry + solutionLocation: solutionLocation + managedIdentityObjectId: userAssignedIdentity.outputs.principalId + saName: 'st${solutionSuffix}' + keyVaultName: keyvault.outputs.name tags: tags - accessTier: 'Hot' - supportsHttpsTrafficOnly: true - roleAssignments: [ - { - principalId: userAssignedIdentity.outputs.principalId - roleDefinitionIdOrName: 'Storage Blob Data Contributor' - principalType: 'ServicePrincipal' - } - ] - // WAF aligned networking - networkAcls: { - bypass: 'AzureServices' - defaultAction: enablePrivateNetworking ? 'Deny' : 'Allow' - } - allowBlobPublicAccess: enablePrivateNetworking ? true : false - publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' - // Private endpoints for blob and queue - privateEndpoints: enablePrivateNetworking - ? [ - { - name: 'pep-blob-${solutionSuffix}' - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { - name: 'storage-dns-zone-group-blob' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId - } - ] - } - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] - service: 'blob' - } - { - name: 'pep-queue-${solutionSuffix}' - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { - name: 'storage-dns-zone-group-queue' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageQueue]!.outputs.resourceId - } - ] - } - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] - service: 'queue' - } - ] - : [] - blobServices: { - corsRules: [] - deleteRetentionPolicyEnabled: false - containers: [ - { - name: 'data' - publicAccess: 'None' - } - ] - } - // secretsExportConfiguration: { - // accessKey1Name: 'ADLS-ACCOUNT-NAME' - // connectionString1Name: storageAccountName - // accessKey2Name: 'ADLS-ACCOUNT-CONTAINER' - // connectionString2Name: 'data' - // accessKey3Name: 'ADLS-ACCOUNT-KEY' - // connectionString3Name: listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2021-04-01') - // keyVaultResourceId: keyvault.outputs.resourceId - // } } - dependsOn: [keyvault] scope: resourceGroup(resourceGroup().name) } @@ -1044,9 +961,9 @@ module appserviceModule 'deploy_app_service.bicep' = { USE_INTERNAL_STREAM: useInternalStream SQLDB_SERVER: sqlServerFqdn SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName - AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.cosmosAccountName - AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: cosmosDb.outputs.cosmosContainerName - AZURE_COSMOSDB_DATABASE: cosmosDb.outputs.cosmosDatabaseName + AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.name + AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName + AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback //VITE_POWERBI_EMBED_URL: 'TBD' imageTag: imageTag @@ -1072,16 +989,16 @@ module appserviceModule 'deploy_app_service.bicep' = { output WEB_APP_URL string = appserviceModule.outputs.webAppUrl @description('Name of the storage account.') -output STORAGE_ACCOUNT_NAME string = avmStorageAccount.outputs.name +output STORAGE_ACCOUNT_NAME string = storageAccountModule.outputs.storageName @description('Name of the storage container.') -output STORAGE_CONTAINER_NAME string = avmStorageAccount.outputs.containerName +output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageContainer @description('Name of the Key Vault.') output KEY_VAULT_NAME string = keyvault.outputs.name @description('Name of the Cosmos DB account.') -output COSMOSDB_ACCOUNT_NAME string = cosmosDBModule.outputs.cosmosAccountName +output COSMOSDB_ACCOUNT_NAME string = cosmosDb.outputs.name @description('Name of the resource group.') output RESOURCE_GROUP_NAME string = resourceGroup().name @@ -1130,13 +1047,13 @@ output AZURE_AI_SEARCH_ENDPOINT string = aifoundry.outputs.aiSearchTarget output AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT string = functionAppCallTranscriptSystemPrompt @description('The name of the Azure Cosmos DB account.') -output AZURE_COSMOSDB_ACCOUNT string = cosmosDBModule.outputs.cosmosAccountName +output AZURE_COSMOSDB_ACCOUNT string = cosmosDb.outputs.name @description('The name of the Azure Cosmos DB container for storing conversations.') -output AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = cosmosDBModule.outputs.cosmosContainerName +output AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = collectionName @description('The name of the Azure Cosmos DB database.') -output AZURE_COSMOSDB_DATABASE string = cosmosDBModule.outputs.cosmosDatabaseName +output AZURE_COSMOSDB_DATABASE string = cosmosDbDatabaseName @description('Indicates whether feedback is enabled in Azure Cosmos DB.') output AZURE_COSMOSDB_ENABLE_FEEDBACK string = azureCosmosDbEnableFeedback From 38f16a69ca22e9baeb871301ef814e6ec1842478 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 15:52:24 +0530 Subject: [PATCH 36/84] Added Networking --- infra/main.bicep | 347 +++++++++++++++++++-- infra/modules/network.bicep | 251 +++++++++++++++ infra/modules/network/bastionHost.bicep | 104 ++++++ infra/modules/network/jumpbox.bicep | 155 +++++++++ infra/modules/network/main.bicep | 104 ++++++ infra/modules/network/virtualNetwork.bicep | 157 ++++++++++ 6 files changed, 1100 insertions(+), 18 deletions(-) create mode 100644 infra/modules/network.bicep create mode 100644 infra/modules/network/bastionHost.bicep create mode 100644 infra/modules/network/jumpbox.bicep create mode 100644 infra/modules/network/main.bicep create mode 100644 infra/modules/network/virtualNetwork.bicep diff --git a/infra/main.bicep b/infra/main.bicep index 0c9a5c836..e36219aad 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -113,6 +113,9 @@ param enableTelemetry bool = true @description('Optional. Enable redundancy for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableRedundancy bool = false +@description('Optional. Enable purge protection for the Key Vault') +param enablePurgeProtection bool = false + // Load the abbrevations file required to name the azure resources. //var abbrs = loadJsonContent('./abbreviations.json') @@ -147,6 +150,19 @@ var useInternalStream = 'True' var useAIProjectClientFlag = 'False' var sqlServerFqdn = '${sqlDBModule.outputs.sqlServerName}.database.windows.net' +@description('Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true.') +param vmSize string? + +@description('Optional. Admin username for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.') +@secure() +//param vmAdminUsername string = take(newGuid(), 20) +param vmAdminUsername string? + +@description('Optional. Admin password for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.') +@secure() +//param vmAdminPassword string = newGuid() +param vmAdminPassword string? + var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for tables and columns provided below: 1. Table: Clients Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents @@ -216,6 +232,25 @@ var cosmosDbZoneRedundantHaRegionPairs = { uksouth: 'westeurope' westeurope: 'northeurope' } + + +var allTags = union( + { + 'azd-env-name': solutionName + }, + tags +) + +var resourcesName = toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) + // Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] @@ -256,6 +291,21 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id // scope: resourceGroup(resourceGroup().name) // } +module network 'modules/network.bicep' = if (enablePrivateNetworking) { + name: take('network-${resourcesName}-deployment', 64) + params: { + resourcesName: resourcesName + logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId + vmAdminUsername: vmAdminUsername ?? 'JumpboxAdminUser' + vmAdminPassword: vmAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!' + vmSize: vmSize ?? 'Standard_DS2_v2' // Default VM size + location: solutionLocation + tags: allTags + enableTelemetry: enableTelemetry + } +} + + var networkSecurityGroupAdministrationResourceName = 'nsg-${solutionSuffix}-administration' module networkSecurityGroupAdministration 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { name: take('avm.res.network.network-security-group.${networkSecurityGroupAdministrationResourceName}', 64) @@ -886,37 +936,298 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { } ] } - dependsOn: [keyvault, storageAccountModule] + dependsOn: [keyvault, avmStorageAccount] scope: resourceGroup(resourceGroup().name) } // ========== Storage Account Module ========== // -module storageAccountModule 'deploy_storage_account.bicep' = { - name: 'deploy_storage_account' +// module storageAccountModule 'deploy_storage_account.bicep' = { +// name: 'deploy_storage_account' +// params: { +// solutionLocation: solutionLocation +// managedIdentityObjectId: userAssignedIdentity.outputs.principalId +// saName: 'st${solutionSuffix}' +// keyVaultName: keyvault.outputs.name +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + +// ========== AVM WAF ========== // +// ========== Storage account module ========== // +var storageAccountName = 'st${solutionSuffix}' +module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { + name: take('avm.res.storage.storage-account.${storageAccountName}', 64) params: { - solutionLocation: solutionLocation - managedIdentityObjectId: userAssignedIdentity.outputs.principalId - saName: 'st${solutionSuffix}' - keyVaultName: keyvault.outputs.name + name: storageAccountName + location: solutionLocation + managedIdentities: { systemAssigned: true } + minimumTlsVersion: 'TLS1_2' + enableTelemetry: enableTelemetry tags: tags + accessTier: 'Hot' + supportsHttpsTrafficOnly: true + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalType: 'ServicePrincipal' + } + ] + // WAF aligned networking + networkAcls: { + bypass: 'AzureServices' + defaultAction: enablePrivateNetworking ? 'Deny' : 'Allow' + } + allowBlobPublicAccess: enablePrivateNetworking ? true : false + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + // Private endpoints for blob and queue + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-blob-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'storage-dns-zone-group-blob' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'blob' + } + { + name: 'pep-queue-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'storage-dns-zone-group-queue' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageQueue]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'queue' + } + ] + : [] + blobServices: { + corsRules: [] + deleteRetentionPolicyEnabled: false + containers: [ + { + name: 'data' + publicAccess: 'None' + } + ] + } + // secretsExportConfiguration: { + // accessKey1Name: 'ADLS-ACCOUNT-NAME' + // connectionString1Name: storageAccountName + // accessKey2Name: 'ADLS-ACCOUNT-CONTAINER' + // connectionString2Name: 'data' + // accessKey3Name: 'ADLS-ACCOUNT-KEY' + // connectionString3Name: listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2021-04-01') + // keyVaultResourceId: keyvault.outputs.resourceId + // } } + dependsOn: [keyvault] scope: resourceGroup(resourceGroup().name) } +// working version of saving storage account secrets in key vault using AVM module +module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0.12.1' = { + name: take('saveStorageAccountSecretsInKeyVault.${keyVaultName}', 64) + params: { + name: keyVaultName + enablePurgeProtection: enablePurgeProtection + enableVaultForDeployment: true + enableVaultForDiskEncryption: true + enableVaultForTemplateDeployment: true + enableRbacAuthorization: true + enableSoftDelete: true + softDeleteRetentionInDays: 7 + secrets: [ + { + name: 'ADLS-ACCOUNT-NAME' + value: storageAccountName + } + { + name: 'ADLS-ACCOUNT-CONTAINER' + value: 'data' + } + { + name: 'ADLS-ACCOUNT-KEY' + value: avmStorageAccount.outputs.primaryAccessKey + } + ] + } +} + + //========== SQL DB Module ========== // -module sqlDBModule 'deploy_sql_db.bicep' = { - name: 'deploy_sql_db' +// module sqlDBModule 'deploy_sql_db.bicep' = { +// name: 'deploy_sql_db' +// params: { +// solutionLocation: solutionLocation +// keyVaultName: keyvault.outputs.name +// managedIdentityObjectId: userAssignedIdentity.outputs.principalId +// managedIdentityName: userAssignedIdentity.outputs.name +// serverName: 'sql-${solutionSuffix}' +// sqlDBName: 'sqldb-${solutionSuffix}' +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + + +module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { + name: 'serverDeployment' params: { - solutionLocation: solutionLocation - keyVaultName: keyvault.outputs.name - managedIdentityObjectId: userAssignedIdentity.outputs.principalId - managedIdentityName: userAssignedIdentity.outputs.name - serverName: 'sql-${solutionSuffix}' - sqlDBName: 'sqldb-${solutionSuffix}' + // Required parameters + name: 'sql-${solutionSuffix}' + // Non-required parameters + administrators: { + azureADOnlyAuthentication: true + login: userAssignedIdentity.outputs.name + principalType: 'Application' + sid: userAssignedIdentity.outputs.principalId + tenantId: subscription().tenantId + } + connectionPolicy: 'Redirect' + // customerManagedKey: { + // autoRotationEnabled: true + // keyName: keyvault.outputs.name + // keyVaultResourceId: keyvault.outputs.resourceId + // // keyVersion: keyvault.outputs. + // } + databases: [ + { + availabilityZone: 1 + backupLongTermRetentionPolicy: { + monthlyRetention: 'P6M' + } + backupShortTermRetentionPolicy: { + retentionDays: 14 + } + collation: 'SQL_Latin1_General_CP1_CI_AS' + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + elasticPoolResourceId: resourceId('Microsoft.Sql/servers/elasticPools', 'sql-${solutionSuffix}', 'sqlswaf-ep-001') + licenseType: 'LicenseIncluded' + maxSizeBytes: 34359738368 + name: 'sqldb-${solutionSuffix}' + sku: { + capacity: 0 + name: 'ElasticPool' + tier: 'GeneralPurpose' + } + } + ] + elasticPools: [ + { + availabilityZone: -1 + //maintenanceConfigurationId: '' + name: 'sqlswaf-ep-001' + sku: { + capacity: 10 + name: 'GP_Gen5' + tier: 'GeneralPurpose' + } + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'db_datareader' + } + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'db_datawriter' + } + + //Enable if above access is not sufficient for your use case + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'SQL DB Contributor' + // } + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'SQL Server Contributor' + // } + ] + } + ] + firewallRules: [ + { + endIpAddress: '255.255.255.255' + name: 'AllowSpecificRange' + startIpAddress: '0.0.0.0' + } + { + endIpAddress: '0.0.0.0' + name: 'AllowAllWindowsAzureIps' + startIpAddress: '0.0.0.0' + } + ] + location: solutionLocation + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + userAssignedIdentity.outputs.resourceId + ] + } + primaryUserAssignedIdentityResourceId: userAssignedIdentity.outputs.resourceId + privateEndpoints: enablePrivateNetworking + ? [ + { + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.sqlServer]!.outputs.resourceId + } + ] + } + service: 'sqlServer' + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + tags: tags + } + ] + : [] + restrictOutboundNetworkAccess: 'Disabled' + securityAlertPolicies: [ + { + emailAccountAdmins: true + name: 'Default' + state: 'Enabled' + } + ] tags: tags + virtualNetworkRules: enablePrivateNetworking + ? [ + { + ignoreMissingVnetServiceEndpoint: true + name: 'newVnetRule1' + virtualNetworkSubnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + } + ] + : [] + vulnerabilityAssessmentsObj: { + name: 'default' + // recurringScans: { + // emails: [ + // 'test1@contoso.com' + // 'test2@contoso.com' + // ] + // emailSubscriptionAdmins: true + // isEnabled: true + // } + storageAccountResourceId: avmStorageAccount.outputs.resourceId + } } - scope: resourceGroup(resourceGroup().name) } //========== Updates to Key Vault ========== // @@ -989,10 +1300,10 @@ module appserviceModule 'deploy_app_service.bicep' = { output WEB_APP_URL string = appserviceModule.outputs.webAppUrl @description('Name of the storage account.') -output STORAGE_ACCOUNT_NAME string = storageAccountModule.outputs.storageName +output STORAGE_ACCOUNT_NAME string = avmStorageAccount.outputs.name @description('Name of the storage container.') -output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageContainer +output STORAGE_CONTAINER_NAME string = 'data' @description('Name of the Key Vault.') output KEY_VAULT_NAME string = keyvault.outputs.name diff --git a/infra/modules/network.bicep b/infra/modules/network.bicep new file mode 100644 index 000000000..55a161fbe --- /dev/null +++ b/infra/modules/network.bicep @@ -0,0 +1,251 @@ +@description('Required. Named used for all resource naming.') +param resourcesName string + +@description('Required. Resource ID of the Log Analytics Workspace for monitoring and diagnostics.') +param logAnalyticsWorkSpaceResourceId string + +@minLength(3) +@description('Required. Azure region for all services.') +param location string + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Required. Admin username for the VM.') +@secure() +param vmAdminUsername string + +@description('Required. Admin password for the VM.') +@secure() +param vmAdminPassword string + +@description('Required. VM size for the Jumpbox VM.') +param vmSize string + + +// VM Size Notes: +// 1 B-series VMs (like Standard_B2ms) do not support accelerated networking. +// 2 Pick a VM size that does support accelerated networking (the usual jump-box candidates): +// Standard_DS2_v2 (2 vCPU, 7 GiB RAM, Premium SSD) // The most broadly available (it’s a legacy SKU supported in virtually every region). +// Standard_D2s_v3 (2 vCPU, 8 GiB RAM, Premium SSD) // next most common +// Standard_D2s_v4 (2 vCPU, 8 GiB RAM, Premium SSD) // Newest, so fewer regions availabl + + +// Subnet Classless Inter-Doman Routing (CIDR) Sizing Reference Table (Best Practices) +// | CIDR | # of Addresses | # of /24s | Notes | +// |-----------|---------------|-----------|----------------------------------------| +// | /24 | 256 | 1 | Smallest recommended for Azure subnets | +// | /23 | 512 | 2 | Good for 1-2 workloads per subnet | +// | /22 | 1024 | 4 | Good for 2-4 workloads per subnet | +// | /21 | 2048 | 8 | | +// | /20 | 4096 | 16 | Used for default VNet in this solution | +// | /19 | 8192 | 32 | | +// | /18 | 16384 | 64 | | +// | /17 | 32768 | 128 | | +// | /16 | 65536 | 256 | | +// | /15 | 131072 | 512 | | +// | /14 | 262144 | 1024 | | +// | /13 | 524288 | 2048 | | +// | /12 | 1048576 | 4096 | | +// | /11 | 2097152 | 8192 | | +// | /10 | 4194304 | 16384 | | +// | /9 | 8388608 | 32768 | | +// | /8 | 16777216 | 65536 | | +// +// Best Practice Notes: +// - Use /24 as the minimum subnet size for Azure (smaller subnets are not supported for most services). +// - Plan for future growth: allocate larger address spaces (e.g., /20 or /21 for VNets) to allow for new subnets. +// - Avoid overlapping address spaces with on-premises or other VNets. +// - Use contiguous, non-overlapping ranges for subnets. +// - Document subnet usage and purpose in code comments. +// - For AVM modules, ensure only one delegation per subnet and leave delegations empty if not required. + +module network 'network/main.bicep' = { + name: take('network-${resourcesName}-create', 64) + params: { + resourcesName: resourcesName + location: location + logAnalyticsWorkSpaceResourceId: logAnalyticsWorkSpaceResourceId + tags: tags + addressPrefixes: ['10.0.0.0/20'] // 4096 addresses (enough for 8 /23 subnets or 16 /24) + subnets: [ + // Only one delegation per subnet is supported by the AVM module as of June 2025. + // For subnets that do not require delegation, leave the value empty. + { + name: 'web' + addressPrefixes: ['10.0.0.0/23'] // /23 (10.0.0.0 - 10.0.1.255), 512 addresses + networkSecurityGroup: { + name: 'nsg-web' + securityRules: [ + { + name: 'AllowHttpsInbound' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 100 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefixes: ['0.0.0.0/0'] + destinationAddressPrefixes: ['10.0.0.0/23'] + } + } + { + name: 'AllowIntraSubnetTraffic' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 200 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefixes: ['10.0.0.0/23'] // From same subnet + destinationAddressPrefixes: ['10.0.0.0/23'] // To same subnet + } + } + { + name: 'AllowAzureLoadBalancer' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 300 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: '10.0.0.0/23' + } + } + ] + } + delegation: 'Microsoft.App/environments' + } + { + name: 'peps' + addressPrefixes: ['10.0.2.0/23'] // /23 (10.0.2.0 - 10.0.3.255), 512 addresses + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Disabled' + networkSecurityGroup: { + name: 'nsg-peps' + securityRules: [] + } + } + ] + bastionConfiguration: { + name: 'bas-${resourcesName}' + subnet: { + name: 'AzureBastionSubnet' + addressPrefixes: ['10.0.10.0/26'] + networkSecurityGroup: { + name: 'nsg-AzureBastionSubnet' + securityRules: [ + { + name: 'AllowGatewayManager' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 2702 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'GatewayManager' + destinationAddressPrefix: '*' + } + } + { + name: 'AllowHttpsInBound' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 2703 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'Internet' + destinationAddressPrefix: '*' + } + } + { + name: 'AllowSshRdpOutbound' + properties: { + access: 'Allow' + direction: 'Outbound' + priority: 100 + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: ['22', '3389'] + sourceAddressPrefix: '*' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'AllowAzureCloudOutbound' + properties: { + access: 'Allow' + direction: 'Outbound' + priority: 110 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'AzureCloud' + } + } + ] + } + } + } + jumpboxConfiguration: { + name: 'vm-jumpbox-${resourcesName}' + size: vmSize + username: vmAdminUsername + password: vmAdminPassword + subnet: { + name: 'jumpbox' + addressPrefixes: ['10.0.12.0/23'] // /23 (10.0.12.0 - 10.0.13.255), 512 addresses + networkSecurityGroup: { + name: 'nsg-jumbox' + securityRules: [ + { + name: 'AllowRdpFromBastion' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 100 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3389' + sourceAddressPrefixes: [ + '10.0.10.0/26' // Azure Bastion subnet + ] + destinationAddressPrefixes: ['10.0.12.0/23'] + } + } + ] + } + } + } + enableTelemetry: enableTelemetry + } +} + +@description('Name of the Virtual Network resource.') +output vnetName string = network.outputs.vnetName + +@description('Resource ID of the Virtual Network.') +output vnetResourceId string = network.outputs.vnetResourceId + +@description('Resource ID of the "web" subnet.') +output subnetWebResourceId string = first(filter(network.outputs.subnets, s => s.name == 'web')).?resourceId ?? '' + +@description('Resource ID of the "peps" subnet for Private Endpoints.') +output subnetPrivateEndpointsResourceId string = first(filter(network.outputs.subnets, s => s.name == 'peps')).?resourceId ?? '' + +@description('Resource ID of the Bastion Host.') +output bastionResourceId string = network.outputs.bastionHostId + +@description('Resource ID of the Jumpbox VM.') +output jumpboxResourceId string = network.outputs.jumpboxResourceId diff --git a/infra/modules/network/bastionHost.bicep b/infra/modules/network/bastionHost.bicep new file mode 100644 index 000000000..cc1987e5f --- /dev/null +++ b/infra/modules/network/bastionHost.bicep @@ -0,0 +1,104 @@ +// /****************************************************************************************************************************/ +// Create Azure Bastion Subnet and Azure Bastion Host +// /****************************************************************************************************************************/ + +@description('Name of the Azure Bastion Host resource.') +param name string + +@description('Azure region to deploy resources.') +param location string = resourceGroup().location + +@description('Resource ID of the Virtual Network where the Azure Bastion Host will be deployed.') +param vnetId string + +@description('Name of the Virtual Network where the Azure Bastion Host will be deployed.') +param vnetName string + +@description('Resource ID of the Log Analytics Workspace for monitoring and diagnostics.') +param logAnalyticsWorkspaceId string + +@description('Optional. Tags to apply to the resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +import { subnetType } from 'virtualNetwork.bicep' +@description('Optional. Subnet configuration for the Jumpbox VM.') +param subnet subnetType? + +// 1. Create AzureBastionSubnet NSG +// using AVM Network Security Group module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group +module nsg 'br/public:avm/res/network/network-security-group:0.5.1' = if (!empty(subnet)) { + name: '${vnetName}-${subnet.?networkSecurityGroup.name}' + params: { + name: '${subnet.?networkSecurityGroup.name}-${vnetName}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } +} + +// 2. Create Azure Bastion Host using AVM Subnet Module with special config for Azure Bastion Subnet +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network/subnet +module bastionSubnet 'br/public:avm/res/network/virtual-network/subnet:0.1.2' = if (!empty(subnet)) { + name: take('bastionSubnet-${vnetName}', 64) + params: { + virtualNetworkName: vnetName + name: 'AzureBastionSubnet' // this name required as is for Azure Bastion Host subnet + addressPrefixes: subnet.?addressPrefixes + networkSecurityGroupResourceId: nsg.outputs.resourceId + enableTelemetry: enableTelemetry + } +} + +// 3. Create Azure Bastion Host in AzureBastionsubnetSubnet using AVM Bastion Host module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/bastion-host + +module bastionHost 'br/public:avm/res/network/bastion-host:0.6.1' = { + name: take('bastionHost-${vnetName}-${name}', 64) + params: { + name: name + skuName: 'Standard' + location: location + virtualNetworkResourceId: vnetId + diagnosticSettings: [ + { + name: 'bastionDiagnostics' + workspaceResourceId: logAnalyticsWorkspaceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + } + ] + tags: tags + enableTelemetry: enableTelemetry + publicIPAddressObject: { + name: 'pip-${name}' + zones: [] + } + } + dependsOn: [ + bastionSubnet + ] +} + +output resourceId string = bastionHost.outputs.resourceId +output name string = bastionHost.outputs.name +output subnetId string = bastionSubnet.outputs.resourceId +output subnetName string = bastionSubnet.outputs.name + +@export() +@description('Custom type definition for establishing Bastion Host for remote connection.') +type bastionHostConfigurationType = { + @description('The name of the Bastion Host resource.') + name: string + + @description('Optional. Subnet configuration for the Jumpbox VM.') + subnet: subnetType? +} diff --git a/infra/modules/network/jumpbox.bicep b/infra/modules/network/jumpbox.bicep new file mode 100644 index 000000000..29f7d3e2f --- /dev/null +++ b/infra/modules/network/jumpbox.bicep @@ -0,0 +1,155 @@ +// /****************************************************************************************************************************/ +// Create Jumpbox NSG and Jumpbox Subnet, then create Jumpbox VM +// /****************************************************************************************************************************/ + +@description('Name of the Jumpbox Virtual Machine.') +param name string + +@description('Azure region to deploy resources.') +param location string = resourceGroup().location + +@description('Name of the Virtual Network where the Jumpbox VM will be deployed.') +param vnetName string + +@description('Size of the Jumpbox Virtual Machine.') +param size string + +import { subnetType } from 'virtualNetwork.bicep' +@description('Optional. Subnet configuration for the Jumpbox VM.') +param subnet subnetType? + +@description('Username to access the Jumpbox VM.') +param username string + +@secure() +@description('Password to access the Jumpbox VM.') +param password string + +@description('Optional. Tags to apply to the resources.') +param tags object = {} + +@description('Log Analytics Workspace Resource ID for VM diagnostics.') +param logAnalyticsWorkspaceId string + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// 1. Create Jumpbox NSG +// using AVM Network Security Group module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group +module nsg 'br/public:avm/res/network/network-security-group:0.5.1' = if (!empty(subnet)) { + name: '${vnetName}-${subnet.?networkSecurityGroup.name}' + params: { + name: '${subnet.?networkSecurityGroup.name}-${vnetName}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } +} + +// 2. Create Jumpbox subnet as part of the existing VNet +// using AVM Virtual Network Subnet module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network/subnet +module subnetResource 'br/public:avm/res/network/virtual-network/subnet:0.1.2' = if (!empty(subnet)) { + name: subnet.?name ?? '${vnetName}-jumpbox-subnet' + params: { + virtualNetworkName: vnetName + name: subnet.?name ?? '' + addressPrefixes: subnet.?addressPrefixes + networkSecurityGroupResourceId: nsg.outputs.resourceId + enableTelemetry: enableTelemetry + } +} + +// 3. Create Jumpbox VM +// using AVM Virtual Machine module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/compute/virtual-machine +var vmName = take(name, 15) // Shorten VM name to 15 characters to avoid Azure limits + +module vm 'br/public:avm/res/compute/virtual-machine:0.15.0' = { + name: take('${vmName}-jumpbox', 64) + params: { + name: vmName + vmSize: size + location: location + adminUsername: username + adminPassword: password + tags: tags + zone: 0 + imageReference: { + offer: 'WindowsServer' + publisher: 'MicrosoftWindowsServer' + sku: '2019-datacenter' + version: 'latest' + } + osType: 'Windows' + osDisk: { + name: 'osdisk-${vmName}' + managedDisk: { + storageAccountType: 'Standard_LRS' + } + } + encryptionAtHost: false // Some Azure subscriptions do not support encryption at host + nicConfigurations: [ + { + name: 'nic-${vmName}' + ipConfigurations: [ + { + name: 'ipconfig1' + subnetResourceId: subnetResource.outputs.resourceId + } + ] + networkSecurityGroupResourceId: nsg.outputs.resourceId + diagnosticSettings: [ + { + name: 'jumpboxDiagnostics' + workspaceResourceId: logAnalyticsWorkspaceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } + ] + } + ] + enableTelemetry: enableTelemetry + } +} + +output resourceId string = vm.outputs.resourceId +output name string = vm.outputs.name +output location string = vm.outputs.location + +output subnetId string = subnetResource.outputs.resourceId +output subnetName string = subnetResource.outputs.name +output nsgId string = nsg.outputs.resourceId +output nsgName string = nsg.outputs.name + +@export() +@description('Custom type definition for establishing Jumpbox Virtual Machine and its associated resources.') +type jumpBoxConfigurationType = { + @description('The name of the Virtual Machine.') + name: string + + @description('The size of the VM.') + size: string? + + @description('Username to access VM.') + username: string + + @secure() + @description('Password to access VM.') + password: string + + @description('Optional. Subnet configuration for the Jumpbox VM.') + subnet: subnetType? +} diff --git a/infra/modules/network/main.bicep b/infra/modules/network/main.bicep new file mode 100644 index 000000000..7e2e86587 --- /dev/null +++ b/infra/modules/network/main.bicep @@ -0,0 +1,104 @@ +@minLength(6) +@maxLength(25) +@description('Name used for naming all network resources.') +param resourcesName string + +@minLength(3) +@description('Azure region for all services.') +param location string + +@description('Resource ID of the Log Analytics Workspace for monitoring and diagnostics.') +param logAnalyticsWorkSpaceResourceId string + +@description('Networking address prefix for the VNET.') +param addressPrefixes array + +import { subnetType } from 'virtualNetwork.bicep' +@description('Array of subnets to be created within the VNET.') +param subnets subnetType[] + +import { jumpBoxConfigurationType } from 'jumpbox.bicep' +@description('Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation.') +param jumpboxConfiguration jumpBoxConfigurationType? + +import { bastionHostConfigurationType } from 'bastionHost.bicep' +@description('Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation.') +param bastionConfiguration bastionHostConfigurationType? + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// /****************************************************************************************************************************/ +// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG +// /****************************************************************************************************************************/ + +module virtualNetwork 'virtualNetwork.bicep' = { + name: '${resourcesName}-virtualNetwork' + params: { + name: 'vnet-${resourcesName}' + addressPrefixes: addressPrefixes + subnets: subnets + location: location + tags: tags + logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId + enableTelemetry: enableTelemetry + } +} + +// /****************************************************************************************************************************/ +// // Create Azure Bastion Subnet and Azure Bastion Host +// /****************************************************************************************************************************/ + +module bastionHost 'bastionHost.bicep' = if (!empty(bastionConfiguration)) { + name: '${resourcesName}-bastionHost' + params: { + name: bastionConfiguration.?name ?? 'bas-${resourcesName}' + vnetId: virtualNetwork.outputs.resourceId + vnetName: virtualNetwork.outputs.name + location: location + logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId + subnet: bastionConfiguration.?subnet + tags: tags + enableTelemetry: enableTelemetry + } +} + +// /****************************************************************************************************************************/ +// // create Jumpbox NSG and Jumpbox Subnet, then create Jumpbox VM +// /****************************************************************************************************************************/ + +module jumpbox 'jumpbox.bicep' = if (!empty(jumpboxConfiguration)) { + name: '${resourcesName}-jumpbox' + params: { + name: jumpboxConfiguration.?name ?? 'vm-jumpbox-${resourcesName}' + vnetName: virtualNetwork.outputs.name + size: jumpboxConfiguration.?size ?? 'Standard_D2s_v3' + logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId + location: location + subnet: jumpboxConfiguration.?subnet + username: jumpboxConfiguration.?username ?? '' // required + password: jumpboxConfiguration.?password ?? '' // required + enableTelemetry: enableTelemetry + tags: tags + } +} + +output vnetName string = virtualNetwork.outputs.name +output vnetResourceId string = virtualNetwork.outputs.resourceId + +import { subnetOutputType } from 'virtualNetwork.bicep' +output subnets subnetOutputType[] = virtualNetwork.outputs.subnets // This one holds critical info for subnets, including NSGs + +output bastionSubnetId string = bastionHost.outputs.subnetId +output bastionSubnetName string = bastionHost.outputs.subnetName +output bastionHostId string = bastionHost.outputs.resourceId +output bastionHostName string = bastionHost.outputs.name + +output jumpboxSubnetName string = jumpbox.outputs.subnetName +output jumpboxSubnetId string = jumpbox.outputs.subnetId +output jumpboxName string = jumpbox.outputs.name +output jumpboxResourceId string = jumpbox.outputs.resourceId + diff --git a/infra/modules/network/virtualNetwork.bicep b/infra/modules/network/virtualNetwork.bicep new file mode 100644 index 000000000..6b5029740 --- /dev/null +++ b/infra/modules/network/virtualNetwork.bicep @@ -0,0 +1,157 @@ +/****************************************************************************************************************************/ +// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG +/****************************************************************************************************************************/ +@description('Name of the virtual network.') +param name string + +@description('Azure region to deploy resources.') +param location string = resourceGroup().location + +@description('Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`.') +param addressPrefixes array + +@description('An array of subnets to be created within the virtual network. Each subnet can have its own configuration and associated Network Security Group (NSG).') +param subnets subnetType[] + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to.') +param logAnalyticsWorkspaceId string + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// 1. Create NSGs for subnets +// using AVM Network Security Group module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group + +@batchSize(1) +module nsgs 'br/public:avm/res/network/network-security-group:0.5.1' = [ + for (subnet, i) in subnets: if (!empty(subnet.?networkSecurityGroup)) { + name: take('${name}-${subnet.?networkSecurityGroup.name}-networksecuritygroup', 64) + params: { + name: '${subnet.?networkSecurityGroup.name}-${name}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } + } +] + +// 2. Create VNet and subnets, with subnets associated with corresponding NSGs +// using AVM Virtual Network module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network + +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.7.0' = { + name: take('${name}-virtualNetwork', 64) + params: { + name: name + location: location + addressPrefixes: addressPrefixes + subnets: [ + for (subnet, i) in subnets: { + name: subnet.name + addressPrefixes: subnet.?addressPrefixes + networkSecurityGroupResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i].outputs.resourceId : null + privateEndpointNetworkPolicies: subnet.?privateEndpointNetworkPolicies + privateLinkServiceNetworkPolicies: subnet.?privateLinkServiceNetworkPolicies + delegation: subnet.?delegation + } + ] + diagnosticSettings: [ + { + name: 'vnetDiagnostics' + workspaceResourceId: logAnalyticsWorkspaceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } + ] + tags: tags + enableTelemetry: enableTelemetry + } +} + +output name string = virtualNetwork.outputs.name +output resourceId string = virtualNetwork.outputs.resourceId + +// combined output array that holds subnet details along with NSG information +output subnets subnetOutputType[] = [ + for (subnet, i) in subnets: { + name: subnet.name + resourceId: virtualNetwork.outputs.subnetResourceIds[i] + nsgName: !empty(subnet.?networkSecurityGroup) ? subnet.?networkSecurityGroup.name : null + nsgResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i].outputs.resourceId : null + } +] + +@export() +@description('Custom type definition for subnet resource information as output') +type subnetOutputType = { + @description('The name of the subnet.') + name: string + + @description('The resource ID of the subnet.') + resourceId: string + + @description('The name of the associated network security group, if any.') + nsgName: string? + + @description('The resource ID of the associated network security group, if any.') + nsgResourceId: string? +} + +@export() +@description('Custom type definition for subnet configuration') +type subnetType = { + @description('Required. The Name of the subnet resource.') + name: string + + @description('Required. Prefixes for the subnet.') // Required to ensure at least one prefix is provided + addressPrefixes: string[] + + @description('Optional. The delegation to enable on the subnet.') + delegation: string? + + @description('Optional. enable or disable apply network policies on private endpoint in the subnet.') + privateEndpointNetworkPolicies: ('Disabled' | 'Enabled' | 'NetworkSecurityGroupEnabled' | 'RouteTableEnabled')? + + @description('Optional. Enable or disable apply network policies on private link service in the subnet.') + privateLinkServiceNetworkPolicies: ('Disabled' | 'Enabled')? + + @description('Optional. Network Security Group configuration for the subnet.') + networkSecurityGroup: networkSecurityGroupType? + + @description('Optional. The resource ID of the route table to assign to the subnet.') + routeTableResourceId: string? + + @description('Optional. An array of service endpoint policies.') + serviceEndpointPolicies: object[]? + + @description('Optional. The service endpoints to enable on the subnet.') + serviceEndpoints: string[]? + + @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.') + defaultOutboundAccess: bool? +} + +@export() +@description('Custom type definition for network security group configuration') +type networkSecurityGroupType = { + @description('Required. The name of the network security group.') + name: string + + @description('Required. The security rules for the network security group.') + securityRules: object[] +} From a3f03f7889427a84611b654dcb8a542369cfc30a Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 15:58:51 +0530 Subject: [PATCH 37/84] Added Networking and Sql --- infra/main.bicep | 347 +++++++++++++++++++-- infra/modules/network.bicep | 251 +++++++++++++++ infra/modules/network/bastionHost.bicep | 104 ++++++ infra/modules/network/jumpbox.bicep | 155 +++++++++ infra/modules/network/main.bicep | 104 ++++++ infra/modules/network/virtualNetwork.bicep | 157 ++++++++++ 6 files changed, 1100 insertions(+), 18 deletions(-) create mode 100644 infra/modules/network.bicep create mode 100644 infra/modules/network/bastionHost.bicep create mode 100644 infra/modules/network/jumpbox.bicep create mode 100644 infra/modules/network/main.bicep create mode 100644 infra/modules/network/virtualNetwork.bicep diff --git a/infra/main.bicep b/infra/main.bicep index 0c9a5c836..e36219aad 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -113,6 +113,9 @@ param enableTelemetry bool = true @description('Optional. Enable redundancy for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableRedundancy bool = false +@description('Optional. Enable purge protection for the Key Vault') +param enablePurgeProtection bool = false + // Load the abbrevations file required to name the azure resources. //var abbrs = loadJsonContent('./abbreviations.json') @@ -147,6 +150,19 @@ var useInternalStream = 'True' var useAIProjectClientFlag = 'False' var sqlServerFqdn = '${sqlDBModule.outputs.sqlServerName}.database.windows.net' +@description('Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true.') +param vmSize string? + +@description('Optional. Admin username for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.') +@secure() +//param vmAdminUsername string = take(newGuid(), 20) +param vmAdminUsername string? + +@description('Optional. Admin password for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.') +@secure() +//param vmAdminPassword string = newGuid() +param vmAdminPassword string? + var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for tables and columns provided below: 1. Table: Clients Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents @@ -216,6 +232,25 @@ var cosmosDbZoneRedundantHaRegionPairs = { uksouth: 'westeurope' westeurope: 'northeurope' } + + +var allTags = union( + { + 'azd-env-name': solutionName + }, + tags +) + +var resourcesName = toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) + // Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] @@ -256,6 +291,21 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id // scope: resourceGroup(resourceGroup().name) // } +module network 'modules/network.bicep' = if (enablePrivateNetworking) { + name: take('network-${resourcesName}-deployment', 64) + params: { + resourcesName: resourcesName + logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId + vmAdminUsername: vmAdminUsername ?? 'JumpboxAdminUser' + vmAdminPassword: vmAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!' + vmSize: vmSize ?? 'Standard_DS2_v2' // Default VM size + location: solutionLocation + tags: allTags + enableTelemetry: enableTelemetry + } +} + + var networkSecurityGroupAdministrationResourceName = 'nsg-${solutionSuffix}-administration' module networkSecurityGroupAdministration 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { name: take('avm.res.network.network-security-group.${networkSecurityGroupAdministrationResourceName}', 64) @@ -886,37 +936,298 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { } ] } - dependsOn: [keyvault, storageAccountModule] + dependsOn: [keyvault, avmStorageAccount] scope: resourceGroup(resourceGroup().name) } // ========== Storage Account Module ========== // -module storageAccountModule 'deploy_storage_account.bicep' = { - name: 'deploy_storage_account' +// module storageAccountModule 'deploy_storage_account.bicep' = { +// name: 'deploy_storage_account' +// params: { +// solutionLocation: solutionLocation +// managedIdentityObjectId: userAssignedIdentity.outputs.principalId +// saName: 'st${solutionSuffix}' +// keyVaultName: keyvault.outputs.name +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + +// ========== AVM WAF ========== // +// ========== Storage account module ========== // +var storageAccountName = 'st${solutionSuffix}' +module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { + name: take('avm.res.storage.storage-account.${storageAccountName}', 64) params: { - solutionLocation: solutionLocation - managedIdentityObjectId: userAssignedIdentity.outputs.principalId - saName: 'st${solutionSuffix}' - keyVaultName: keyvault.outputs.name + name: storageAccountName + location: solutionLocation + managedIdentities: { systemAssigned: true } + minimumTlsVersion: 'TLS1_2' + enableTelemetry: enableTelemetry tags: tags + accessTier: 'Hot' + supportsHttpsTrafficOnly: true + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + principalType: 'ServicePrincipal' + } + ] + // WAF aligned networking + networkAcls: { + bypass: 'AzureServices' + defaultAction: enablePrivateNetworking ? 'Deny' : 'Allow' + } + allowBlobPublicAccess: enablePrivateNetworking ? true : false + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + // Private endpoints for blob and queue + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-blob-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'storage-dns-zone-group-blob' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'blob' + } + { + name: 'pep-queue-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'storage-dns-zone-group-queue' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageQueue]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'queue' + } + ] + : [] + blobServices: { + corsRules: [] + deleteRetentionPolicyEnabled: false + containers: [ + { + name: 'data' + publicAccess: 'None' + } + ] + } + // secretsExportConfiguration: { + // accessKey1Name: 'ADLS-ACCOUNT-NAME' + // connectionString1Name: storageAccountName + // accessKey2Name: 'ADLS-ACCOUNT-CONTAINER' + // connectionString2Name: 'data' + // accessKey3Name: 'ADLS-ACCOUNT-KEY' + // connectionString3Name: listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2021-04-01') + // keyVaultResourceId: keyvault.outputs.resourceId + // } } + dependsOn: [keyvault] scope: resourceGroup(resourceGroup().name) } +// working version of saving storage account secrets in key vault using AVM module +module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0.12.1' = { + name: take('saveStorageAccountSecretsInKeyVault.${keyVaultName}', 64) + params: { + name: keyVaultName + enablePurgeProtection: enablePurgeProtection + enableVaultForDeployment: true + enableVaultForDiskEncryption: true + enableVaultForTemplateDeployment: true + enableRbacAuthorization: true + enableSoftDelete: true + softDeleteRetentionInDays: 7 + secrets: [ + { + name: 'ADLS-ACCOUNT-NAME' + value: storageAccountName + } + { + name: 'ADLS-ACCOUNT-CONTAINER' + value: 'data' + } + { + name: 'ADLS-ACCOUNT-KEY' + value: avmStorageAccount.outputs.primaryAccessKey + } + ] + } +} + + //========== SQL DB Module ========== // -module sqlDBModule 'deploy_sql_db.bicep' = { - name: 'deploy_sql_db' +// module sqlDBModule 'deploy_sql_db.bicep' = { +// name: 'deploy_sql_db' +// params: { +// solutionLocation: solutionLocation +// keyVaultName: keyvault.outputs.name +// managedIdentityObjectId: userAssignedIdentity.outputs.principalId +// managedIdentityName: userAssignedIdentity.outputs.name +// serverName: 'sql-${solutionSuffix}' +// sqlDBName: 'sqldb-${solutionSuffix}' +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + + +module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { + name: 'serverDeployment' params: { - solutionLocation: solutionLocation - keyVaultName: keyvault.outputs.name - managedIdentityObjectId: userAssignedIdentity.outputs.principalId - managedIdentityName: userAssignedIdentity.outputs.name - serverName: 'sql-${solutionSuffix}' - sqlDBName: 'sqldb-${solutionSuffix}' + // Required parameters + name: 'sql-${solutionSuffix}' + // Non-required parameters + administrators: { + azureADOnlyAuthentication: true + login: userAssignedIdentity.outputs.name + principalType: 'Application' + sid: userAssignedIdentity.outputs.principalId + tenantId: subscription().tenantId + } + connectionPolicy: 'Redirect' + // customerManagedKey: { + // autoRotationEnabled: true + // keyName: keyvault.outputs.name + // keyVaultResourceId: keyvault.outputs.resourceId + // // keyVersion: keyvault.outputs. + // } + databases: [ + { + availabilityZone: 1 + backupLongTermRetentionPolicy: { + monthlyRetention: 'P6M' + } + backupShortTermRetentionPolicy: { + retentionDays: 14 + } + collation: 'SQL_Latin1_General_CP1_CI_AS' + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + elasticPoolResourceId: resourceId('Microsoft.Sql/servers/elasticPools', 'sql-${solutionSuffix}', 'sqlswaf-ep-001') + licenseType: 'LicenseIncluded' + maxSizeBytes: 34359738368 + name: 'sqldb-${solutionSuffix}' + sku: { + capacity: 0 + name: 'ElasticPool' + tier: 'GeneralPurpose' + } + } + ] + elasticPools: [ + { + availabilityZone: -1 + //maintenanceConfigurationId: '' + name: 'sqlswaf-ep-001' + sku: { + capacity: 10 + name: 'GP_Gen5' + tier: 'GeneralPurpose' + } + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'db_datareader' + } + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'db_datawriter' + } + + //Enable if above access is not sufficient for your use case + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'SQL DB Contributor' + // } + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'SQL Server Contributor' + // } + ] + } + ] + firewallRules: [ + { + endIpAddress: '255.255.255.255' + name: 'AllowSpecificRange' + startIpAddress: '0.0.0.0' + } + { + endIpAddress: '0.0.0.0' + name: 'AllowAllWindowsAzureIps' + startIpAddress: '0.0.0.0' + } + ] + location: solutionLocation + managedIdentities: { + systemAssigned: true + userAssignedResourceIds: [ + userAssignedIdentity.outputs.resourceId + ] + } + primaryUserAssignedIdentityResourceId: userAssignedIdentity.outputs.resourceId + privateEndpoints: enablePrivateNetworking + ? [ + { + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.sqlServer]!.outputs.resourceId + } + ] + } + service: 'sqlServer' + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + tags: tags + } + ] + : [] + restrictOutboundNetworkAccess: 'Disabled' + securityAlertPolicies: [ + { + emailAccountAdmins: true + name: 'Default' + state: 'Enabled' + } + ] tags: tags + virtualNetworkRules: enablePrivateNetworking + ? [ + { + ignoreMissingVnetServiceEndpoint: true + name: 'newVnetRule1' + virtualNetworkSubnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + } + ] + : [] + vulnerabilityAssessmentsObj: { + name: 'default' + // recurringScans: { + // emails: [ + // 'test1@contoso.com' + // 'test2@contoso.com' + // ] + // emailSubscriptionAdmins: true + // isEnabled: true + // } + storageAccountResourceId: avmStorageAccount.outputs.resourceId + } } - scope: resourceGroup(resourceGroup().name) } //========== Updates to Key Vault ========== // @@ -989,10 +1300,10 @@ module appserviceModule 'deploy_app_service.bicep' = { output WEB_APP_URL string = appserviceModule.outputs.webAppUrl @description('Name of the storage account.') -output STORAGE_ACCOUNT_NAME string = storageAccountModule.outputs.storageName +output STORAGE_ACCOUNT_NAME string = avmStorageAccount.outputs.name @description('Name of the storage container.') -output STORAGE_CONTAINER_NAME string = storageAccountModule.outputs.storageContainer +output STORAGE_CONTAINER_NAME string = 'data' @description('Name of the Key Vault.') output KEY_VAULT_NAME string = keyvault.outputs.name diff --git a/infra/modules/network.bicep b/infra/modules/network.bicep new file mode 100644 index 000000000..55a161fbe --- /dev/null +++ b/infra/modules/network.bicep @@ -0,0 +1,251 @@ +@description('Required. Named used for all resource naming.') +param resourcesName string + +@description('Required. Resource ID of the Log Analytics Workspace for monitoring and diagnostics.') +param logAnalyticsWorkSpaceResourceId string + +@minLength(3) +@description('Required. Azure region for all services.') +param location string + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Required. Admin username for the VM.') +@secure() +param vmAdminUsername string + +@description('Required. Admin password for the VM.') +@secure() +param vmAdminPassword string + +@description('Required. VM size for the Jumpbox VM.') +param vmSize string + + +// VM Size Notes: +// 1 B-series VMs (like Standard_B2ms) do not support accelerated networking. +// 2 Pick a VM size that does support accelerated networking (the usual jump-box candidates): +// Standard_DS2_v2 (2 vCPU, 7 GiB RAM, Premium SSD) // The most broadly available (it’s a legacy SKU supported in virtually every region). +// Standard_D2s_v3 (2 vCPU, 8 GiB RAM, Premium SSD) // next most common +// Standard_D2s_v4 (2 vCPU, 8 GiB RAM, Premium SSD) // Newest, so fewer regions availabl + + +// Subnet Classless Inter-Doman Routing (CIDR) Sizing Reference Table (Best Practices) +// | CIDR | # of Addresses | # of /24s | Notes | +// |-----------|---------------|-----------|----------------------------------------| +// | /24 | 256 | 1 | Smallest recommended for Azure subnets | +// | /23 | 512 | 2 | Good for 1-2 workloads per subnet | +// | /22 | 1024 | 4 | Good for 2-4 workloads per subnet | +// | /21 | 2048 | 8 | | +// | /20 | 4096 | 16 | Used for default VNet in this solution | +// | /19 | 8192 | 32 | | +// | /18 | 16384 | 64 | | +// | /17 | 32768 | 128 | | +// | /16 | 65536 | 256 | | +// | /15 | 131072 | 512 | | +// | /14 | 262144 | 1024 | | +// | /13 | 524288 | 2048 | | +// | /12 | 1048576 | 4096 | | +// | /11 | 2097152 | 8192 | | +// | /10 | 4194304 | 16384 | | +// | /9 | 8388608 | 32768 | | +// | /8 | 16777216 | 65536 | | +// +// Best Practice Notes: +// - Use /24 as the minimum subnet size for Azure (smaller subnets are not supported for most services). +// - Plan for future growth: allocate larger address spaces (e.g., /20 or /21 for VNets) to allow for new subnets. +// - Avoid overlapping address spaces with on-premises or other VNets. +// - Use contiguous, non-overlapping ranges for subnets. +// - Document subnet usage and purpose in code comments. +// - For AVM modules, ensure only one delegation per subnet and leave delegations empty if not required. + +module network 'network/main.bicep' = { + name: take('network-${resourcesName}-create', 64) + params: { + resourcesName: resourcesName + location: location + logAnalyticsWorkSpaceResourceId: logAnalyticsWorkSpaceResourceId + tags: tags + addressPrefixes: ['10.0.0.0/20'] // 4096 addresses (enough for 8 /23 subnets or 16 /24) + subnets: [ + // Only one delegation per subnet is supported by the AVM module as of June 2025. + // For subnets that do not require delegation, leave the value empty. + { + name: 'web' + addressPrefixes: ['10.0.0.0/23'] // /23 (10.0.0.0 - 10.0.1.255), 512 addresses + networkSecurityGroup: { + name: 'nsg-web' + securityRules: [ + { + name: 'AllowHttpsInbound' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 100 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefixes: ['0.0.0.0/0'] + destinationAddressPrefixes: ['10.0.0.0/23'] + } + } + { + name: 'AllowIntraSubnetTraffic' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 200 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefixes: ['10.0.0.0/23'] // From same subnet + destinationAddressPrefixes: ['10.0.0.0/23'] // To same subnet + } + } + { + name: 'AllowAzureLoadBalancer' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 300 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationAddressPrefix: '10.0.0.0/23' + } + } + ] + } + delegation: 'Microsoft.App/environments' + } + { + name: 'peps' + addressPrefixes: ['10.0.2.0/23'] // /23 (10.0.2.0 - 10.0.3.255), 512 addresses + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Disabled' + networkSecurityGroup: { + name: 'nsg-peps' + securityRules: [] + } + } + ] + bastionConfiguration: { + name: 'bas-${resourcesName}' + subnet: { + name: 'AzureBastionSubnet' + addressPrefixes: ['10.0.10.0/26'] + networkSecurityGroup: { + name: 'nsg-AzureBastionSubnet' + securityRules: [ + { + name: 'AllowGatewayManager' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 2702 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'GatewayManager' + destinationAddressPrefix: '*' + } + } + { + name: 'AllowHttpsInBound' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 2703 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'Internet' + destinationAddressPrefix: '*' + } + } + { + name: 'AllowSshRdpOutbound' + properties: { + access: 'Allow' + direction: 'Outbound' + priority: 100 + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: ['22', '3389'] + sourceAddressPrefix: '*' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'AllowAzureCloudOutbound' + properties: { + access: 'Allow' + direction: 'Outbound' + priority: 110 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'AzureCloud' + } + } + ] + } + } + } + jumpboxConfiguration: { + name: 'vm-jumpbox-${resourcesName}' + size: vmSize + username: vmAdminUsername + password: vmAdminPassword + subnet: { + name: 'jumpbox' + addressPrefixes: ['10.0.12.0/23'] // /23 (10.0.12.0 - 10.0.13.255), 512 addresses + networkSecurityGroup: { + name: 'nsg-jumbox' + securityRules: [ + { + name: 'AllowRdpFromBastion' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 100 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3389' + sourceAddressPrefixes: [ + '10.0.10.0/26' // Azure Bastion subnet + ] + destinationAddressPrefixes: ['10.0.12.0/23'] + } + } + ] + } + } + } + enableTelemetry: enableTelemetry + } +} + +@description('Name of the Virtual Network resource.') +output vnetName string = network.outputs.vnetName + +@description('Resource ID of the Virtual Network.') +output vnetResourceId string = network.outputs.vnetResourceId + +@description('Resource ID of the "web" subnet.') +output subnetWebResourceId string = first(filter(network.outputs.subnets, s => s.name == 'web')).?resourceId ?? '' + +@description('Resource ID of the "peps" subnet for Private Endpoints.') +output subnetPrivateEndpointsResourceId string = first(filter(network.outputs.subnets, s => s.name == 'peps')).?resourceId ?? '' + +@description('Resource ID of the Bastion Host.') +output bastionResourceId string = network.outputs.bastionHostId + +@description('Resource ID of the Jumpbox VM.') +output jumpboxResourceId string = network.outputs.jumpboxResourceId diff --git a/infra/modules/network/bastionHost.bicep b/infra/modules/network/bastionHost.bicep new file mode 100644 index 000000000..cc1987e5f --- /dev/null +++ b/infra/modules/network/bastionHost.bicep @@ -0,0 +1,104 @@ +// /****************************************************************************************************************************/ +// Create Azure Bastion Subnet and Azure Bastion Host +// /****************************************************************************************************************************/ + +@description('Name of the Azure Bastion Host resource.') +param name string + +@description('Azure region to deploy resources.') +param location string = resourceGroup().location + +@description('Resource ID of the Virtual Network where the Azure Bastion Host will be deployed.') +param vnetId string + +@description('Name of the Virtual Network where the Azure Bastion Host will be deployed.') +param vnetName string + +@description('Resource ID of the Log Analytics Workspace for monitoring and diagnostics.') +param logAnalyticsWorkspaceId string + +@description('Optional. Tags to apply to the resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +import { subnetType } from 'virtualNetwork.bicep' +@description('Optional. Subnet configuration for the Jumpbox VM.') +param subnet subnetType? + +// 1. Create AzureBastionSubnet NSG +// using AVM Network Security Group module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group +module nsg 'br/public:avm/res/network/network-security-group:0.5.1' = if (!empty(subnet)) { + name: '${vnetName}-${subnet.?networkSecurityGroup.name}' + params: { + name: '${subnet.?networkSecurityGroup.name}-${vnetName}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } +} + +// 2. Create Azure Bastion Host using AVM Subnet Module with special config for Azure Bastion Subnet +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network/subnet +module bastionSubnet 'br/public:avm/res/network/virtual-network/subnet:0.1.2' = if (!empty(subnet)) { + name: take('bastionSubnet-${vnetName}', 64) + params: { + virtualNetworkName: vnetName + name: 'AzureBastionSubnet' // this name required as is for Azure Bastion Host subnet + addressPrefixes: subnet.?addressPrefixes + networkSecurityGroupResourceId: nsg.outputs.resourceId + enableTelemetry: enableTelemetry + } +} + +// 3. Create Azure Bastion Host in AzureBastionsubnetSubnet using AVM Bastion Host module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/bastion-host + +module bastionHost 'br/public:avm/res/network/bastion-host:0.6.1' = { + name: take('bastionHost-${vnetName}-${name}', 64) + params: { + name: name + skuName: 'Standard' + location: location + virtualNetworkResourceId: vnetId + diagnosticSettings: [ + { + name: 'bastionDiagnostics' + workspaceResourceId: logAnalyticsWorkspaceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + } + ] + tags: tags + enableTelemetry: enableTelemetry + publicIPAddressObject: { + name: 'pip-${name}' + zones: [] + } + } + dependsOn: [ + bastionSubnet + ] +} + +output resourceId string = bastionHost.outputs.resourceId +output name string = bastionHost.outputs.name +output subnetId string = bastionSubnet.outputs.resourceId +output subnetName string = bastionSubnet.outputs.name + +@export() +@description('Custom type definition for establishing Bastion Host for remote connection.') +type bastionHostConfigurationType = { + @description('The name of the Bastion Host resource.') + name: string + + @description('Optional. Subnet configuration for the Jumpbox VM.') + subnet: subnetType? +} diff --git a/infra/modules/network/jumpbox.bicep b/infra/modules/network/jumpbox.bicep new file mode 100644 index 000000000..29f7d3e2f --- /dev/null +++ b/infra/modules/network/jumpbox.bicep @@ -0,0 +1,155 @@ +// /****************************************************************************************************************************/ +// Create Jumpbox NSG and Jumpbox Subnet, then create Jumpbox VM +// /****************************************************************************************************************************/ + +@description('Name of the Jumpbox Virtual Machine.') +param name string + +@description('Azure region to deploy resources.') +param location string = resourceGroup().location + +@description('Name of the Virtual Network where the Jumpbox VM will be deployed.') +param vnetName string + +@description('Size of the Jumpbox Virtual Machine.') +param size string + +import { subnetType } from 'virtualNetwork.bicep' +@description('Optional. Subnet configuration for the Jumpbox VM.') +param subnet subnetType? + +@description('Username to access the Jumpbox VM.') +param username string + +@secure() +@description('Password to access the Jumpbox VM.') +param password string + +@description('Optional. Tags to apply to the resources.') +param tags object = {} + +@description('Log Analytics Workspace Resource ID for VM diagnostics.') +param logAnalyticsWorkspaceId string + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// 1. Create Jumpbox NSG +// using AVM Network Security Group module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group +module nsg 'br/public:avm/res/network/network-security-group:0.5.1' = if (!empty(subnet)) { + name: '${vnetName}-${subnet.?networkSecurityGroup.name}' + params: { + name: '${subnet.?networkSecurityGroup.name}-${vnetName}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } +} + +// 2. Create Jumpbox subnet as part of the existing VNet +// using AVM Virtual Network Subnet module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network/subnet +module subnetResource 'br/public:avm/res/network/virtual-network/subnet:0.1.2' = if (!empty(subnet)) { + name: subnet.?name ?? '${vnetName}-jumpbox-subnet' + params: { + virtualNetworkName: vnetName + name: subnet.?name ?? '' + addressPrefixes: subnet.?addressPrefixes + networkSecurityGroupResourceId: nsg.outputs.resourceId + enableTelemetry: enableTelemetry + } +} + +// 3. Create Jumpbox VM +// using AVM Virtual Machine module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/compute/virtual-machine +var vmName = take(name, 15) // Shorten VM name to 15 characters to avoid Azure limits + +module vm 'br/public:avm/res/compute/virtual-machine:0.15.0' = { + name: take('${vmName}-jumpbox', 64) + params: { + name: vmName + vmSize: size + location: location + adminUsername: username + adminPassword: password + tags: tags + zone: 0 + imageReference: { + offer: 'WindowsServer' + publisher: 'MicrosoftWindowsServer' + sku: '2019-datacenter' + version: 'latest' + } + osType: 'Windows' + osDisk: { + name: 'osdisk-${vmName}' + managedDisk: { + storageAccountType: 'Standard_LRS' + } + } + encryptionAtHost: false // Some Azure subscriptions do not support encryption at host + nicConfigurations: [ + { + name: 'nic-${vmName}' + ipConfigurations: [ + { + name: 'ipconfig1' + subnetResourceId: subnetResource.outputs.resourceId + } + ] + networkSecurityGroupResourceId: nsg.outputs.resourceId + diagnosticSettings: [ + { + name: 'jumpboxDiagnostics' + workspaceResourceId: logAnalyticsWorkspaceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } + ] + } + ] + enableTelemetry: enableTelemetry + } +} + +output resourceId string = vm.outputs.resourceId +output name string = vm.outputs.name +output location string = vm.outputs.location + +output subnetId string = subnetResource.outputs.resourceId +output subnetName string = subnetResource.outputs.name +output nsgId string = nsg.outputs.resourceId +output nsgName string = nsg.outputs.name + +@export() +@description('Custom type definition for establishing Jumpbox Virtual Machine and its associated resources.') +type jumpBoxConfigurationType = { + @description('The name of the Virtual Machine.') + name: string + + @description('The size of the VM.') + size: string? + + @description('Username to access VM.') + username: string + + @secure() + @description('Password to access VM.') + password: string + + @description('Optional. Subnet configuration for the Jumpbox VM.') + subnet: subnetType? +} diff --git a/infra/modules/network/main.bicep b/infra/modules/network/main.bicep new file mode 100644 index 000000000..7e2e86587 --- /dev/null +++ b/infra/modules/network/main.bicep @@ -0,0 +1,104 @@ +@minLength(6) +@maxLength(25) +@description('Name used for naming all network resources.') +param resourcesName string + +@minLength(3) +@description('Azure region for all services.') +param location string + +@description('Resource ID of the Log Analytics Workspace for monitoring and diagnostics.') +param logAnalyticsWorkSpaceResourceId string + +@description('Networking address prefix for the VNET.') +param addressPrefixes array + +import { subnetType } from 'virtualNetwork.bicep' +@description('Array of subnets to be created within the VNET.') +param subnets subnetType[] + +import { jumpBoxConfigurationType } from 'jumpbox.bicep' +@description('Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation.') +param jumpboxConfiguration jumpBoxConfigurationType? + +import { bastionHostConfigurationType } from 'bastionHost.bicep' +@description('Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation.') +param bastionConfiguration bastionHostConfigurationType? + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// /****************************************************************************************************************************/ +// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG +// /****************************************************************************************************************************/ + +module virtualNetwork 'virtualNetwork.bicep' = { + name: '${resourcesName}-virtualNetwork' + params: { + name: 'vnet-${resourcesName}' + addressPrefixes: addressPrefixes + subnets: subnets + location: location + tags: tags + logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId + enableTelemetry: enableTelemetry + } +} + +// /****************************************************************************************************************************/ +// // Create Azure Bastion Subnet and Azure Bastion Host +// /****************************************************************************************************************************/ + +module bastionHost 'bastionHost.bicep' = if (!empty(bastionConfiguration)) { + name: '${resourcesName}-bastionHost' + params: { + name: bastionConfiguration.?name ?? 'bas-${resourcesName}' + vnetId: virtualNetwork.outputs.resourceId + vnetName: virtualNetwork.outputs.name + location: location + logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId + subnet: bastionConfiguration.?subnet + tags: tags + enableTelemetry: enableTelemetry + } +} + +// /****************************************************************************************************************************/ +// // create Jumpbox NSG and Jumpbox Subnet, then create Jumpbox VM +// /****************************************************************************************************************************/ + +module jumpbox 'jumpbox.bicep' = if (!empty(jumpboxConfiguration)) { + name: '${resourcesName}-jumpbox' + params: { + name: jumpboxConfiguration.?name ?? 'vm-jumpbox-${resourcesName}' + vnetName: virtualNetwork.outputs.name + size: jumpboxConfiguration.?size ?? 'Standard_D2s_v3' + logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId + location: location + subnet: jumpboxConfiguration.?subnet + username: jumpboxConfiguration.?username ?? '' // required + password: jumpboxConfiguration.?password ?? '' // required + enableTelemetry: enableTelemetry + tags: tags + } +} + +output vnetName string = virtualNetwork.outputs.name +output vnetResourceId string = virtualNetwork.outputs.resourceId + +import { subnetOutputType } from 'virtualNetwork.bicep' +output subnets subnetOutputType[] = virtualNetwork.outputs.subnets // This one holds critical info for subnets, including NSGs + +output bastionSubnetId string = bastionHost.outputs.subnetId +output bastionSubnetName string = bastionHost.outputs.subnetName +output bastionHostId string = bastionHost.outputs.resourceId +output bastionHostName string = bastionHost.outputs.name + +output jumpboxSubnetName string = jumpbox.outputs.subnetName +output jumpboxSubnetId string = jumpbox.outputs.subnetId +output jumpboxName string = jumpbox.outputs.name +output jumpboxResourceId string = jumpbox.outputs.resourceId + diff --git a/infra/modules/network/virtualNetwork.bicep b/infra/modules/network/virtualNetwork.bicep new file mode 100644 index 000000000..6b5029740 --- /dev/null +++ b/infra/modules/network/virtualNetwork.bicep @@ -0,0 +1,157 @@ +/****************************************************************************************************************************/ +// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG +/****************************************************************************************************************************/ +@description('Name of the virtual network.') +param name string + +@description('Azure region to deploy resources.') +param location string = resourceGroup().location + +@description('Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`.') +param addressPrefixes array + +@description('An array of subnets to be created within the virtual network. Each subnet can have its own configuration and associated Network Security Group (NSG).') +param subnets subnetType[] + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to.') +param logAnalyticsWorkspaceId string + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// 1. Create NSGs for subnets +// using AVM Network Security Group module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group + +@batchSize(1) +module nsgs 'br/public:avm/res/network/network-security-group:0.5.1' = [ + for (subnet, i) in subnets: if (!empty(subnet.?networkSecurityGroup)) { + name: take('${name}-${subnet.?networkSecurityGroup.name}-networksecuritygroup', 64) + params: { + name: '${subnet.?networkSecurityGroup.name}-${name}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } + } +] + +// 2. Create VNet and subnets, with subnets associated with corresponding NSGs +// using AVM Virtual Network module +// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network + +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.7.0' = { + name: take('${name}-virtualNetwork', 64) + params: { + name: name + location: location + addressPrefixes: addressPrefixes + subnets: [ + for (subnet, i) in subnets: { + name: subnet.name + addressPrefixes: subnet.?addressPrefixes + networkSecurityGroupResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i].outputs.resourceId : null + privateEndpointNetworkPolicies: subnet.?privateEndpointNetworkPolicies + privateLinkServiceNetworkPolicies: subnet.?privateLinkServiceNetworkPolicies + delegation: subnet.?delegation + } + ] + diagnosticSettings: [ + { + name: 'vnetDiagnostics' + workspaceResourceId: logAnalyticsWorkspaceId + logCategoriesAndGroups: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metricCategories: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } + ] + tags: tags + enableTelemetry: enableTelemetry + } +} + +output name string = virtualNetwork.outputs.name +output resourceId string = virtualNetwork.outputs.resourceId + +// combined output array that holds subnet details along with NSG information +output subnets subnetOutputType[] = [ + for (subnet, i) in subnets: { + name: subnet.name + resourceId: virtualNetwork.outputs.subnetResourceIds[i] + nsgName: !empty(subnet.?networkSecurityGroup) ? subnet.?networkSecurityGroup.name : null + nsgResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i].outputs.resourceId : null + } +] + +@export() +@description('Custom type definition for subnet resource information as output') +type subnetOutputType = { + @description('The name of the subnet.') + name: string + + @description('The resource ID of the subnet.') + resourceId: string + + @description('The name of the associated network security group, if any.') + nsgName: string? + + @description('The resource ID of the associated network security group, if any.') + nsgResourceId: string? +} + +@export() +@description('Custom type definition for subnet configuration') +type subnetType = { + @description('Required. The Name of the subnet resource.') + name: string + + @description('Required. Prefixes for the subnet.') // Required to ensure at least one prefix is provided + addressPrefixes: string[] + + @description('Optional. The delegation to enable on the subnet.') + delegation: string? + + @description('Optional. enable or disable apply network policies on private endpoint in the subnet.') + privateEndpointNetworkPolicies: ('Disabled' | 'Enabled' | 'NetworkSecurityGroupEnabled' | 'RouteTableEnabled')? + + @description('Optional. Enable or disable apply network policies on private link service in the subnet.') + privateLinkServiceNetworkPolicies: ('Disabled' | 'Enabled')? + + @description('Optional. Network Security Group configuration for the subnet.') + networkSecurityGroup: networkSecurityGroupType? + + @description('Optional. The resource ID of the route table to assign to the subnet.') + routeTableResourceId: string? + + @description('Optional. An array of service endpoint policies.') + serviceEndpointPolicies: object[]? + + @description('Optional. The service endpoints to enable on the subnet.') + serviceEndpoints: string[]? + + @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.') + defaultOutboundAccess: bool? +} + +@export() +@description('Custom type definition for network security group configuration') +type networkSecurityGroupType = { + @description('Required. The name of the network security group.') + name: string + + @description('Required. The security rules for the network security group.') + securityRules: object[] +} From 5c8b710acf1c2489d35efc75ee576b52889cddc2 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 16:46:24 +0530 Subject: [PATCH 38/84] Avm v3 --- infra/main.bicep | 87 +++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index e36219aad..2f0759850 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -148,7 +148,7 @@ var azureSearchEnableInDomain = 'False' // Set to 'True' if you want to enable i var azureCosmosDbEnableFeedback = 'True' var useInternalStream = 'True' var useAIProjectClientFlag = 'False' -var sqlServerFqdn = '${sqlDBModule.outputs.sqlServerName}.database.windows.net' +var sqlServerFqdn = '${sqlDBModule.outputs.name}.database.windows.net' @description('Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true.') param vmSize string? @@ -200,19 +200,19 @@ var functionAppStreamTextSystemPrompt = '''The currently selected client's name Always send clientId as '{client_id}'.''' // Replica regions list based on article in [Azure regions list](https://learn.microsoft.com/azure/reliability/regions-list) and [Enhance resilience by replicating your Log Analytics workspace across regions](https://learn.microsoft.com/azure/azure-monitor/logs/workspace-replication#supported-regions) for supported regions for Log Analytics Workspace. -var replicaRegionPairs = { - australiaeast: 'australiasoutheast' - centralus: 'westus' - eastasia: 'japaneast' - eastus: 'centralus' - eastus2: 'centralus' - japaneast: 'eastasia' - northeurope: 'westeurope' - southeastasia: 'eastasia' - uksouth: 'westeurope' - westeurope: 'northeurope' -} -var replicaLocation = replicaRegionPairs[resourceGroup().location] +// var replicaRegionPairs = { +// australiaeast: 'australiasoutheast' +// centralus: 'westus' +// eastasia: 'japaneast' +// eastus: 'centralus' +// eastus2: 'centralus' +// japaneast: 'eastasia' +// northeurope: 'westeurope' +// southeastasia: 'eastasia' +// uksouth: 'westeurope' +// westeurope: 'northeurope' +// } +// var replicaLocation = replicaRegionPairs[resourceGroup().location] @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} @@ -220,18 +220,18 @@ param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' // Region pairs list based on article in [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions) for supported high availability regions for CosmosDB. -var cosmosDbZoneRedundantHaRegionPairs = { - australiaeast: 'uksouth' //'southeastasia' - centralus: 'eastus2' - eastasia: 'southeastasia' - eastus: 'centralus' - eastus2: 'centralus' - japaneast: 'australiaeast' - northeurope: 'westeurope' - southeastasia: 'eastasia' - uksouth: 'westeurope' - westeurope: 'northeurope' -} +// var cosmosDbZoneRedundantHaRegionPairs = { +// australiaeast: 'uksouth' //'southeastasia' +// centralus: 'eastus2' +// eastasia: 'southeastasia' +// eastus: 'centralus' +// eastus2: 'centralus' +// japaneast: 'australiaeast' +// northeurope: 'westeurope' +// southeastasia: 'eastasia' +// uksouth: 'westeurope' +// westeurope: 'northeurope' +// } var allTags = union( @@ -252,7 +252,7 @@ var resourcesName = toLower(trim(replace( ))) // Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` -var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] +// var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { @@ -709,12 +709,6 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 diagnosticSettings: [{ useThisWorkspace: true }] // WAF aligned configuration for Redundancy dailyQuotaGb: enableRedundancy ? 10 : null //WAF recommendation: 10 GB per day is a good starting point for most workloads - replication: enableRedundancy - ? { - enabled: true - location: replicaLocation - } - : null // WAF aligned configuration for Private Networking publicNetworkAccessForIngestion: enablePrivateNetworking ? 'Disabled' : 'Enabled' publicNetworkAccessForQuery: enablePrivateNetworking ? 'Disabled' : 'Enabled' @@ -916,25 +910,6 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { zoneRedundant: enableRedundancy ? true : false capabilitiesToAdd: enableRedundancy ? null : ['EnableServerless'] automaticFailover: enableRedundancy ? true : false - failoverLocations: enableRedundancy - ? [ - { - failoverPriority: 0 - isZoneRedundant: true - locationName: solutionLocation - } - { - failoverPriority: 1 - isZoneRedundant: true - locationName: cosmosDbHaLocation - } - ] - : [ - { - locationName: solutionLocation - failoverPriority: 0 - } - ] } dependsOn: [keyvault, avmStorageAccount] scope: resourceGroup(resourceGroup().name) @@ -1082,7 +1057,7 @@ module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0. // scope: resourceGroup(resourceGroup().name) // } - +var sqlDbName = 'sqldb-${solutionSuffix}' module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { name: 'serverDeployment' params: { @@ -1271,7 +1246,7 @@ module appserviceModule 'deploy_app_service.bicep' = { azureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint USE_INTERNAL_STREAM: useInternalStream SQLDB_SERVER: sqlServerFqdn - SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName + SQLDB_DATABASE: sqlDbName AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.name AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName @@ -1318,10 +1293,10 @@ output RESOURCE_GROUP_NAME string = resourceGroup().name output AI_FOUNDRY_RESOURCE_ID string = aifoundry.outputs.aiFoundryId @description('Name of the SQL Database server.') -output SQLDB_SERVER_NAME string = sqlDBModule.outputs.sqlServerName +output SQLDB_SERVER_NAME string = sqlDBModule.outputs.name @description('Name of the SQL Database.') -output SQLDB_DATABASE string = sqlDBModule.outputs.sqlDbName +output SQLDB_DATABASE string = sqlDbName @description('Name of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name From 28016fea116b84c8c2d4d3aaf13e44f63c942e3b Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 16:53:39 +0530 Subject: [PATCH 39/84] Resolved Key valut issue --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 2f0759850..b6833ac99 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1207,7 +1207,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { //========== Updates to Key Vault ========== // resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: aifoundry.outputs.keyvaultName + name: keyVaultName scope: resourceGroup(resourceGroup().name) } From f37bbd940a6e08bd07afc50ae9f489f8f1e1c348 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 25 Aug 2025 17:23:04 +0530 Subject: [PATCH 40/84] Changed ResourceId --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index b6833ac99..ad58f04bc 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1254,7 +1254,7 @@ module appserviceModule 'deploy_app_service.bicep' = { //VITE_POWERBI_EMBED_URL: 'TBD' imageTag: imageTag userassignedIdentityClientId: userAssignedIdentity.outputs.clientId - userassignedIdentityId: userAssignedIdentity.outputs.principalId + userassignedIdentityId: userAssignedIdentity.outputs.resourceId applicationInsightsId: aifoundry.outputs.applicationInsightsId azureSearchServiceEndpoint: aifoundry.outputs.aiSearchTarget sqlSystemPrompt: functionAppSqlPrompt From ff16543571310f28a49abba4855780a432fab21a Mon Sep 17 00:00:00 2001 From: Abdul-Microsoft Date: Mon, 25 Aug 2025 18:34:04 +0530 Subject: [PATCH 41/84] feat: Add Bicep modules for Cognitive Services, Key Vault export, AI Projects, and Web Apps - Created `dependencies.bicep` to manage Cognitive Services account configurations including deployments, private endpoints, and role assignments. - Introduced `keyVaultExport.bicep` for exporting secrets to Azure Key Vault. - Added `project.bicep` for creating AI Foundry projects linked to Cognitive Services. - Developed `web-sites.bicep` for deploying various types of web applications with extensive configuration options. - Implemented `web-sites.config.bicep` to manage app settings and configurations for deployed web apps. --- infra/main.bicep | 771 ++++++++++----------------- infra/modules/ai-services.bicep | 410 ++++++++++++++ infra/modules/dependencies.bicep | 479 +++++++++++++++++ infra/modules/keyVaultExport.bicep | 43 ++ infra/modules/project.bicep | 62 +++ infra/modules/web-sites.bicep | 368 +++++++++++++ infra/modules/web-sites.config.bicep | 91 ++++ 7 files changed, 1726 insertions(+), 498 deletions(-) create mode 100644 infra/modules/ai-services.bicep create mode 100644 infra/modules/dependencies.bicep create mode 100644 infra/modules/keyVaultExport.bicep create mode 100644 infra/modules/project.bicep create mode 100644 infra/modules/web-sites.bicep create mode 100644 infra/modules/web-sites.config.bicep diff --git a/infra/main.bicep b/infra/main.bicep index ad58f04bc..267867c1a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -107,12 +107,27 @@ param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') param enableMonitoring bool = false +@description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') +param enableScalability bool = false + @description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true @description('Optional. Enable redundancy for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableRedundancy bool = false +@description('Optional. The Container Registry hostname where the docker images for the frontend are located.') +param containerRegistryHostname string = 'biabcontainerreg.azurecr.io' + +@description('Optional. The Container Image Name to deploy on the webapp.') +param containerImageName string = 'byc-wa-app' + +@description('Optional. The Container Image Tag to deploy on the webapp.') +param containerImageTag string = 'latest' + +@description('Optional. Resource ID of an existing Foundry project') +param existingFoundryProjectResourceId string = '' + @description('Optional. Enable purge protection for the Key Vault') param enablePurgeProtection bool = false @@ -200,19 +215,19 @@ var functionAppStreamTextSystemPrompt = '''The currently selected client's name Always send clientId as '{client_id}'.''' // Replica regions list based on article in [Azure regions list](https://learn.microsoft.com/azure/reliability/regions-list) and [Enhance resilience by replicating your Log Analytics workspace across regions](https://learn.microsoft.com/azure/azure-monitor/logs/workspace-replication#supported-regions) for supported regions for Log Analytics Workspace. -// var replicaRegionPairs = { -// australiaeast: 'australiasoutheast' -// centralus: 'westus' -// eastasia: 'japaneast' -// eastus: 'centralus' -// eastus2: 'centralus' -// japaneast: 'eastasia' -// northeurope: 'westeurope' -// southeastasia: 'eastasia' -// uksouth: 'westeurope' -// westeurope: 'northeurope' -// } -// var replicaLocation = replicaRegionPairs[resourceGroup().location] +var replicaRegionPairs = { + australiaeast: 'australiasoutheast' + centralus: 'westus' + eastasia: 'japaneast' + eastus: 'centralus' + eastus2: 'centralus' + japaneast: 'eastasia' + northeurope: 'westeurope' + southeastasia: 'eastasia' + uksouth: 'westeurope' + westeurope: 'northeurope' +} +var replicaLocation = replicaRegionPairs[resourceGroup().location] @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} @@ -220,18 +235,18 @@ param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' // Region pairs list based on article in [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions) for supported high availability regions for CosmosDB. -// var cosmosDbZoneRedundantHaRegionPairs = { -// australiaeast: 'uksouth' //'southeastasia' -// centralus: 'eastus2' -// eastasia: 'southeastasia' -// eastus: 'centralus' -// eastus2: 'centralus' -// japaneast: 'australiaeast' -// northeurope: 'westeurope' -// southeastasia: 'eastasia' -// uksouth: 'westeurope' -// westeurope: 'northeurope' -// } +var cosmosDbZoneRedundantHaRegionPairs = { + australiaeast: 'uksouth' //'southeastasia' + centralus: 'eastus2' + eastasia: 'southeastasia' + eastus: 'centralus' + eastus2: 'centralus' + japaneast: 'australiaeast' + northeurope: 'westeurope' + southeastasia: 'eastasia' + uksouth: 'westeurope' + westeurope: 'northeurope' +} var allTags = union( @@ -252,7 +267,7 @@ var resourcesName = toLower(trim(replace( ))) // Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` -// var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] +var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { @@ -306,391 +321,76 @@ module network 'modules/network.bicep' = if (enablePrivateNetworking) { } -var networkSecurityGroupAdministrationResourceName = 'nsg-${solutionSuffix}-administration' -module networkSecurityGroupAdministration 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { - name: take('avm.res.network.network-security-group.${networkSecurityGroupAdministrationResourceName}', 64) - params: { - name: networkSecurityGroupAdministrationResourceName - location: solutionLocation - tags: tags - enableTelemetry: enableTelemetry - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - securityRules: [ - { - name: 'deny-hop-outbound' - properties: { - access: 'Deny' - destinationAddressPrefix: '*' - destinationPortRanges: [ - '22' - '3389' - ] - direction: 'Outbound' - priority: 200 - protocol: 'Tcp' - sourceAddressPrefix: 'VirtualNetwork' - sourcePortRange: '*' - } - } - ] - } -} - - -// ========== Network Security Groups ========== // -// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network -// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking -var networkSecurityGroupBackendResourceName = 'nsg-${solutionSuffix}-backend' -module networkSecurityGroupBackend 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { - name: take('avm.res.network.network-security-group.${networkSecurityGroupBackendResourceName}', 64) - params: { - name: networkSecurityGroupBackendResourceName - location: solutionLocation - tags: tags - enableTelemetry: enableTelemetry - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - securityRules: [ - { - name: 'deny-hop-outbound' - properties: { - access: 'Deny' - destinationAddressPrefix: '*' - destinationPortRanges: [ - '22' - '3389' - ] - direction: 'Outbound' - priority: 200 - protocol: 'Tcp' - sourceAddressPrefix: 'VirtualNetwork' - sourcePortRange: '*' - } - } - ] - } -} - - -var networkSecurityGroupBastionResourceName = 'nsg-${solutionSuffix}-bastion' -module networkSecurityGroupBastion 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { - name: take('avm.res.network.network-security-group.${networkSecurityGroupBastionResourceName}', 64) - params: { - name: networkSecurityGroupBastionResourceName - location: solutionLocation - tags: tags - enableTelemetry: enableTelemetry - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - securityRules: [ - { - name: 'AllowHttpsInBound' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: 'Internet' - destinationPortRange: '443' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 100 - direction: 'Inbound' - } - } - { - name: 'AllowGatewayManagerInBound' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: 'GatewayManager' - destinationPortRange: '443' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 110 - direction: 'Inbound' - } - } - { - name: 'AllowLoadBalancerInBound' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: 'AzureLoadBalancer' - destinationPortRange: '443' - destinationAddressPrefix: '*' - access: 'Allow' - priority: 120 - direction: 'Inbound' - } - } - { - name: 'AllowBastionHostCommunicationInBound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: 'VirtualNetwork' - destinationPortRanges: [ - '8080' - '5701' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 130 - direction: 'Inbound' - } - } - { - name: 'DenyAllInBound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Inbound' - } - } - { - name: 'AllowSshRdpOutBound' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRanges: [ - '22' - '3389' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 100 - direction: 'Outbound' - } - } - { - name: 'AllowAzureCloudCommunicationOutBound' - properties: { - protocol: 'Tcp' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationPortRange: '443' - destinationAddressPrefix: 'AzureCloud' - access: 'Allow' - priority: 110 - direction: 'Outbound' - } - } - { - name: 'AllowBastionHostCommunicationOutBound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: 'VirtualNetwork' - destinationPortRanges: [ - '8080' - '5701' - ] - destinationAddressPrefix: 'VirtualNetwork' - access: 'Allow' - priority: 120 - direction: 'Outbound' - } - } - { - name: 'AllowGetSessionInformationOutBound' - properties: { - protocol: '*' - sourcePortRange: '*' - sourceAddressPrefix: '*' - destinationAddressPrefix: 'Internet' - destinationPortRanges: [ - '80' - '443' - ] - access: 'Allow' - priority: 130 - direction: 'Outbound' - } - } - { - name: 'DenyAllOutBound' - properties: { - protocol: '*' - sourcePortRange: '*' - destinationPortRange: '*' - sourceAddressPrefix: '*' - destinationAddressPrefix: '*' - access: 'Deny' - priority: 1000 - direction: 'Outbound' - } - } - ] - } -} - -var networkSecurityGroupContainersResourceName = 'nsg-${solutionSuffix}-containers' -module networkSecurityGroupContainers 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { - name: take('avm.res.network.network-security-group.${networkSecurityGroupContainersResourceName}', 64) - params: { - name: networkSecurityGroupContainersResourceName - location: solutionLocation - tags: tags - enableTelemetry: enableTelemetry - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - securityRules: [ - { - name: 'deny-hop-outbound' - properties: { - access: 'Deny' - destinationAddressPrefix: '*' - destinationPortRanges: [ - '22' - '3389' - ] - direction: 'Outbound' - priority: 200 - protocol: 'Tcp' - sourceAddressPrefix: 'VirtualNetwork' - sourcePortRange: '*' - } - } - ] - } -} - -var networkSecurityGroupWebsiteResourceName = 'nsg-${solutionSuffix}-website' -module networkSecurityGroupWebsite 'br/public:avm/res/network/network-security-group:0.5.1' = if (enablePrivateNetworking) { - name: take('avm.res.network.network-security-group.${networkSecurityGroupWebsiteResourceName}', 64) - params: { - name: networkSecurityGroupWebsiteResourceName - location: solutionLocation - tags: tags - enableTelemetry: enableTelemetry - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - securityRules: [ - { - name: 'deny-hop-outbound' - properties: { - access: 'Deny' - destinationAddressPrefix: '*' - destinationPortRanges: [ - '22' - '3389' - ] - direction: 'Outbound' - priority: 200 - protocol: 'Tcp' - sourceAddressPrefix: 'VirtualNetwork' - sourcePortRange: '*' - } - } - ] - } -} - -// ========== Virtual Network ========== // -// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network -// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking -var virtualNetworkResourceName = 'vnet-${solutionSuffix}' -module virtualNetwork 'br/public:avm/res/network/virtual-network:0.7.0' = if (enablePrivateNetworking) { - name: take('avm.res.network.virtual-network.${virtualNetworkResourceName}', 64) - params: { - name: virtualNetworkResourceName - location: solutionLocation - tags: tags - enableTelemetry: enableTelemetry - addressPrefixes: ['10.0.0.0/8'] - subnets: [ - { - name: 'backend' - addressPrefix: '10.0.0.0/27' - //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access - networkSecurityGroupResourceId: networkSecurityGroupBackend!.outputs.resourceId - } - { - name: 'administration' - addressPrefix: '10.0.0.32/27' - networkSecurityGroupResourceId: networkSecurityGroupAdministration!.outputs.resourceId - //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access - //natGatewayResourceId: natGateway.outputs.resourceId - } - { - // For Azure Bastion resources deployed on or after November 2, 2021, the minimum AzureBastionSubnet size is /26 or larger (/25, /24, etc.). - // https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet - name: 'AzureBastionSubnet' //This exact name is required for Azure Bastion - addressPrefix: '10.0.0.64/26' - networkSecurityGroupResourceId: networkSecurityGroupBastion!.outputs.resourceId - } - { - // If you use your own vnw, you need to provide a subnet that is dedicated exclusively to the Container App environment you deploy. This subnet isn't available to other services - // https://learn.microsoft.com/en-us/azure/container-apps/networking?tabs=workload-profiles-env%2Cazure-cli#custom-vnw-configuration - name: 'containers' - addressPrefix: '10.0.2.0/23' //subnet of size /23 is required for container app - delegation: 'Microsoft.App/environments' - networkSecurityGroupResourceId: networkSecurityGroupContainers!.outputs.resourceId - privateEndpointNetworkPolicies: 'Enabled' - privateLinkServiceNetworkPolicies: 'Enabled' - } - { - // If you use your own vnw, you need to provide a subnet that is dedicated exclusively to the App Environment you deploy. This subnet isn't available to other services - // https://learn.microsoft.com/en-us/azure/app-service/overview-vnet-integration#subnet-requirements - name: 'webserverfarm' - addressPrefix: '10.0.4.0/27' //When you're creating subnets in Azure portal as part of integrating with the virtual network, a minimum size of /27 is required - delegation: 'Microsoft.Web/serverfarms' - networkSecurityGroupResourceId: networkSecurityGroupWebsite!.outputs.resourceId - privateEndpointNetworkPolicies: 'Enabled' - privateLinkServiceNetworkPolicies: 'Enabled' - } - ] - } -} - // ========== Private DNS Zones ========== // var privateDnsZones = [ 'privatelink.cognitiveservices.azure.com' 'privatelink.openai.azure.com' 'privatelink.services.ai.azure.com' - 'privatelink.contentunderstanding.ai.azure.com' + 'privatelink.azurewebsites.net' 'privatelink.blob.${environment().suffixes.storage}' 'privatelink.queue.${environment().suffixes.storage}' 'privatelink.file.${environment().suffixes.storage}' - 'privatelink.api.azureml.ms' - 'privatelink.notebooks.azure.net' - 'privatelink.mongo.cosmos.azure.com' - 'privatelink.azconfig.io' + 'privatelink.documents.azure.com' 'privatelink.vaultcore.azure.net' - 'privatelink.azurecr.io' - 'privatelink${environment().suffixes.sqlServerHostname}' + 'privatelink.${environment().suffixes.sqlServerHostname}' ] + // DNS Zone Index Constants var dnsZoneIndex = { cognitiveServices: 0 openAI: 1 aiServices: 2 - contentUnderstanding: 3 + appService: 3 storageBlob: 4 storageQueue: 5 storageFile: 6 - aiFoundry: 7 - notebooks: 8 - cosmosDB: 9 - appConfig: 10 - keyVault: 11 - containerRegistry: 12 - sqlServer: 13 + cosmosDB: 7 + keyVault: 8 + sqlServer: 9 } + +// List of DNS zone indices that correspond to AI-related services. +var aiRelatedDnsZoneIndices = [ + dnsZoneIndex.cognitiveServices + dnsZoneIndex.openAI + dnsZoneIndex.aiServices +] + +// =================================================== +// DEPLOY PRIVATE DNS ZONES +// - Deploys all zones if no existing Foundry project is used +// - Excludes AI-related zones when using with an existing Foundry project +// =================================================== @batchSize(5) module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ - for (zone, i) in privateDnsZones: if (enablePrivateNetworking) { - name: 'dns-zone-${i}' + for (zone, i) in privateDnsZones: if (enablePrivateNetworking && (empty(existingFoundryProjectResourceId) || !contains(aiRelatedDnsZoneIndices, i))) { + name: 'avm.res.network.private-dns-zone.${contains(zone, 'azurecontainerapps.io') ? 'containerappenv' : split(zone, '.')[1]}' params: { name: zone tags: tags enableTelemetry: enableTelemetry - virtualNetworkLinks: [{ virtualNetworkResourceId: virtualNetwork!.outputs.resourceId }] + virtualNetworkLinks: [ + { + name: take('vnetlink-${network!.outputs.vnetName}-${split(zone, '.')[1]}', 80) + virtualNetworkResourceId: network!.outputs.subnetPrivateEndpointsResourceId + } + ] } } ] +// Extracts subscription, resource group, and workspace name from the resource ID when using an existing Log Analytics workspace +var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) + +var existingLawSubscription = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[2] : '' +var existingLawResourceGroup = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[4] : '' +var existingLawName = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[8] : '' + +resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = if (useExistingLogAnalytics) { + name: existingLawName + scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) +} + // ========== Log Analytics Workspace ========== // // WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics @@ -709,6 +409,12 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 diagnosticSettings: [{ useThisWorkspace: true }] // WAF aligned configuration for Redundancy dailyQuotaGb: enableRedundancy ? 10 : null //WAF recommendation: 10 GB per day is a good starting point for most workloads + replication: enableRedundancy + ? { + enabled: true + location: replicaLocation + } + : null // WAF aligned configuration for Private Networking publicNetworkAccessForIngestion: enablePrivateNetworking ? 'Disabled' : 'Enabled' publicNetworkAccessForQuery: enablePrivateNetworking ? 'Disabled' : 'Enabled' @@ -749,6 +455,33 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 } } +// Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) +var logAnalyticsWorkspaceName = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.name : logAnalyticsWorkspace!.outputs.name +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace!.outputs.resourceId +var logAnalyticsPrimarySharedKey = useExistingLogAnalytics? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey +var logAnalyticsWorkspaceId = useExistingLogAnalytics? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId + +// ========== Application Insights ========== // +// WAF best practices for Application Insights: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/application-insights +// WAF PSRules for Application Insights: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#application-insights +var applicationInsightsResourceName = 'appi-${solutionSuffix}' +module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (enableMonitoring) { + name: take('avm.res.insights.component.${applicationInsightsResourceName}', 64) + params: { + name: applicationInsightsResourceName + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + retentionInDays: 365 + kind: 'web' + disableIpMasking: false + flowType: 'Bluefield' + // WAF aligned configuration for Monitoring + workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : '' + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + } +} + // Key Vault resource var keyVaultName = 'KV-${solutionSuffix}' module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { @@ -781,7 +514,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.keyVault]!.outputs.resourceId}] } service: 'vault' - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId } ] : [] @@ -902,7 +635,7 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { ] } service: 'Sql' - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId } ] : [] @@ -910,6 +643,25 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { zoneRedundant: enableRedundancy ? true : false capabilitiesToAdd: enableRedundancy ? null : ['EnableServerless'] automaticFailover: enableRedundancy ? true : false + failoverLocations: enableRedundancy + ? [ + { + failoverPriority: 0 + isZoneRedundant: true + locationName: solutionLocation + } + { + failoverPriority: 1 + isZoneRedundant: true + locationName: cosmosDbHaLocation + } + ] + : [ + { + locationName: solutionLocation + failoverPriority: 0 + } + ] } dependsOn: [keyvault, avmStorageAccount] scope: resourceGroup(resourceGroup().name) @@ -970,7 +722,7 @@ module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { } ] } - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId service: 'blob' } { @@ -983,7 +735,7 @@ module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { } ] } - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId service: 'queue' } ] @@ -1042,21 +794,6 @@ module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0. } -//========== SQL DB Module ========== // -// module sqlDBModule 'deploy_sql_db.bicep' = { -// name: 'deploy_sql_db' -// params: { -// solutionLocation: solutionLocation -// keyVaultName: keyvault.outputs.name -// managedIdentityObjectId: userAssignedIdentity.outputs.principalId -// managedIdentityName: userAssignedIdentity.outputs.name -// serverName: 'sql-${solutionSuffix}' -// sqlDBName: 'sqldb-${solutionSuffix}' -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } - var sqlDbName = 'sqldb-${solutionSuffix}' module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { name: 'serverDeployment' @@ -1100,42 +837,6 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { } } ] - elasticPools: [ - { - availabilityZone: -1 - //maintenanceConfigurationId: '' - name: 'sqlswaf-ep-001' - sku: { - capacity: 10 - name: 'GP_Gen5' - tier: 'GeneralPurpose' - } - roleAssignments: [ - { - principalId: userAssignedIdentity.outputs.principalId - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'db_datareader' - } - { - principalId: userAssignedIdentity.outputs.principalId - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'db_datawriter' - } - - //Enable if above access is not sufficient for your use case - // { - // principalId: userAssignedIdentity.outputs.principalId - // principalType: 'ServicePrincipal' - // roleDefinitionIdOrName: 'SQL DB Contributor' - // } - // { - // principalId: userAssignedIdentity.outputs.principalId - // principalType: 'ServicePrincipal' - // roleDefinitionIdOrName: 'SQL Server Contributor' - // } - ] - } - ] firewallRules: [ { endIpAddress: '255.255.255.255' @@ -1167,7 +868,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { ] } service: 'sqlServer' - subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId tags: tags } ] @@ -1186,7 +887,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { { ignoreMissingVnetServiceEndpoint: true name: 'newVnetRule1' - virtualNetworkSubnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + virtualNetworkSubnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId } ] : [] @@ -1207,72 +908,146 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { //========== Updates to Key Vault ========== // resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: keyVaultName + name: aifoundry.outputs.keyvaultName scope: resourceGroup(resourceGroup().name) } -// ========== App Service Module ========== // -module appserviceModule 'deploy_app_service.bicep' = { - name: 'deploy_app_service' +// // ========== App Service Module ========== // +// module appserviceModule 'deploy_app_service.bicep' = { +// name: 'deploy_app_service' +// params: { +// solutionLocation: solutionLocation +// hostingPlanName: hostingPlanName +// websiteName: websiteName +// appEnvironment: appEnvironment +// azureSearchService: aifoundry.outputs.aiSearchService +// azureSearchIndex: azureSearchIndex +// azureSearchUseSemanticSearch: azureSearchUseSemanticSearch +// azureSearchSemanticSearchConfig: azureSearchSemanticSearchConfig +// azureSearchTopK: azureSearchTopK +// azureSearchContentColumns: azureSearchContentColumns +// azureSearchFilenameColumn: azureSearchFilenameColumn +// azureSearchTitleColumn: azureSearchTitleColumn +// azureSearchUrlColumn: azureSearchUrlColumn +// azureOpenAIResource: aifoundry.outputs.aiFoundryName +// azureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint +// azureOpenAIModel: gptModelName +// azureOpenAITemperature: azureOpenAITemperature +// azureOpenAITopP: azureOpenAITopP +// azureOpenAIMaxTokens: azureOpenAIMaxTokens +// azureOpenAIStopSequence: azureOpenAIStopSequence +// azureOpenAISystemMessage: azureOpenAISystemMessage +// azureOpenAIApiVersion: azureOpenaiAPIVersion +// azureOpenAIStream: azureOpenAIStream +// azureSearchQueryType: azureSearchQueryType +// azureSearchVectorFields: azureSearchVectorFields +// azureSearchPermittedGroupsField: azureSearchPermittedGroupsField +// azureSearchStrictness: azureSearchStrictness +// azureOpenAIEmbeddingName: embeddingModel +// azureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint +// USE_INTERNAL_STREAM: useInternalStream +// SQLDB_SERVER: sqlServerFqdn +// SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName +// AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.name +// AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName +// AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName +// AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback +// //VITE_POWERBI_EMBED_URL: 'TBD' +// imageTag: imageTag +// userassignedIdentityClientId: userAssignedIdentity.outputs.clientId +// userassignedIdentityId: userAssignedIdentity.outputs.principalId +// applicationInsightsId: aifoundry.outputs.applicationInsightsId +// azureSearchServiceEndpoint: aifoundry.outputs.aiSearchTarget +// sqlSystemPrompt: functionAppSqlPrompt +// callTranscriptSystemPrompt: functionAppCallTranscriptSystemPrompt +// streamTextSystemPrompt: functionAppStreamTextSystemPrompt +// //aiFoundryProjectName:aifoundry.outputs.aiFoundryProjectName +// aiFoundryProjectEndpoint: aifoundry.outputs.aiFoundryProjectEndpoint +// aiFoundryName: aifoundry.outputs.aiFoundryName +// applicationInsightsConnectionString: aifoundry.outputs.applicationInsightsConnectionString +// azureExistingAIProjectResourceId: azureExistingAIProjectResourceId +// aiSearchProjectConnectionName: aifoundry.outputs.aiSearchFoundryConnectionName +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + +// ========== Frontend server farm ========== // +// WAF best practices for Web Application Services: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps +// PSRule for Web Server Farm: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#app-service +var webServerFarmResourceName = 'asp-${solutionSuffix}' +module webServerFarm 'br/public:avm/res/web/serverfarm:0.5.0' = { + name: take('avm.res.web.serverfarm.${webServerFarmResourceName}', 64) params: { - solutionLocation: solutionLocation - hostingPlanName: hostingPlanName - websiteName: websiteName - appEnvironment: appEnvironment - azureSearchService: aifoundry.outputs.aiSearchService - azureSearchIndex: azureSearchIndex - azureSearchUseSemanticSearch: azureSearchUseSemanticSearch - azureSearchSemanticSearchConfig: azureSearchSemanticSearchConfig - azureSearchTopK: azureSearchTopK - azureSearchContentColumns: azureSearchContentColumns - azureSearchFilenameColumn: azureSearchFilenameColumn - azureSearchTitleColumn: azureSearchTitleColumn - azureSearchUrlColumn: azureSearchUrlColumn - azureOpenAIResource: aifoundry.outputs.aiFoundryName - azureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint - azureOpenAIModel: gptModelName - azureOpenAITemperature: azureOpenAITemperature - azureOpenAITopP: azureOpenAITopP - azureOpenAIMaxTokens: azureOpenAIMaxTokens - azureOpenAIStopSequence: azureOpenAIStopSequence - azureOpenAISystemMessage: azureOpenAISystemMessage - azureOpenAIApiVersion: azureOpenaiAPIVersion - azureOpenAIStream: azureOpenAIStream - azureSearchQueryType: azureSearchQueryType - azureSearchVectorFields: azureSearchVectorFields - azureSearchPermittedGroupsField: azureSearchPermittedGroupsField - azureSearchStrictness: azureSearchStrictness - azureOpenAIEmbeddingName: embeddingModel - azureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint - USE_INTERNAL_STREAM: useInternalStream - SQLDB_SERVER: sqlServerFqdn - SQLDB_DATABASE: sqlDbName - AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.name - AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName - AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName - AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback - //VITE_POWERBI_EMBED_URL: 'TBD' - imageTag: imageTag - userassignedIdentityClientId: userAssignedIdentity.outputs.clientId - userassignedIdentityId: userAssignedIdentity.outputs.resourceId - applicationInsightsId: aifoundry.outputs.applicationInsightsId - azureSearchServiceEndpoint: aifoundry.outputs.aiSearchTarget - sqlSystemPrompt: functionAppSqlPrompt - callTranscriptSystemPrompt: functionAppCallTranscriptSystemPrompt - streamTextSystemPrompt: functionAppStreamTextSystemPrompt - //aiFoundryProjectName:aifoundry.outputs.aiFoundryProjectName - aiFoundryProjectEndpoint: aifoundry.outputs.aiFoundryProjectEndpoint - aiFoundryName: aifoundry.outputs.aiFoundryName - applicationInsightsConnectionString: aifoundry.outputs.applicationInsightsConnectionString - azureExistingAIProjectResourceId: azureExistingAIProjectResourceId - aiSearchProjectConnectionName: aifoundry.outputs.aiSearchFoundryConnectionName - tags: tags + name: webServerFarmResourceName + tags: tags + enableTelemetry: enableTelemetry + location: solutionLocation + reserved: true + kind: 'linux' + // WAF aligned configuration for Monitoring + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + // WAF aligned configuration for Scalability + skuName: enableScalability || enableRedundancy ? 'P1v3' : 'B3' + skuCapacity: enableScalability ? 3 : 1 + // WAF aligned configuration for Redundancy + zoneRedundant: enableRedundancy ? true : false + } +} + +// ========== Frontend web site ========== // +// WAF best practices for web app service: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps +// PSRule for Web Server Farm: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#app-service + +//NOTE: AVM module adds 1 MB of overhead to the template. Keeping vanilla resource to save template size. +var webSiteResourceName = 'app-${solutionSuffix}' +module webSite 'modules/web-sites.bicep' = { + name: take('module.web-sites.${webSiteResourceName}', 64) + params: { + name: webSiteResourceName + tags: tags + location: solutionLocation + kind: 'app,linux,container' + serverFarmResourceId: webServerFarm.?outputs.resourceId + siteConfig: { + linuxFxVersion: 'DOCKER|${containerRegistryHostname}/${containerImageName}:${containerImageTag}' + minTlsVersion: '1.2' + } + configs: [ + { + name: 'appsettings' + properties: { + + } + // WAF aligned configuration for Monitoring + applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null + } + ] + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + // WAF aligned configuration for Private Networking + vnetRouteAllEnabled: enablePrivateNetworking ? true : false + vnetImagePullEnabled: enablePrivateNetworking ? true : false + virtualNetworkSubnetId: enablePrivateNetworking ? network!.outputs.subnetWebResourceId : null + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-${webSiteResourceName}' + customNetworkInterfaceName: 'nic-${webSiteResourceName}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.appService]!.outputs.resourceId }] + } + service: 'sites' + subnetResourceId: network!.outputs.subnetWebResourceId + } + ] + : null } - scope: resourceGroup(resourceGroup().name) } + @description('URL of the deployed web application.') -output WEB_APP_URL string = appserviceModule.outputs.webAppUrl +output WEB_APP_URL string = 'https://${webSite.outputs.name}.azurewebsites.net' @description('Name of the storage account.') output STORAGE_ACCOUNT_NAME string = avmStorageAccount.outputs.name @@ -1307,7 +1082,7 @@ output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.cli output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService @description('Name of the deployed web application.') -output WEB_APP_NAME string = appserviceModule.outputs.webAppName +output WEB_APP_NAME string = webSite.outputs.name @description('Specifies the current application environment.') output APP_ENV string = appEnvironment diff --git a/infra/modules/ai-services.bicep b/infra/modules/ai-services.bicep new file mode 100644 index 000000000..94b70795b --- /dev/null +++ b/infra/modules/ai-services.bicep @@ -0,0 +1,410 @@ +metadata name = 'Cognitive Services' +metadata description = 'This module deploys a Cognitive Service.' + +@description('Required. The name of Cognitive Services account.') +param name string + +@description('Required. Kind of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.') +@allowed([ + 'AIServices' + 'AnomalyDetector' + 'CognitiveServices' + 'ComputerVision' + 'ContentModerator' + 'ContentSafety' + 'ConversationalLanguageUnderstanding' + 'CustomVision.Prediction' + 'CustomVision.Training' + 'Face' + 'FormRecognizer' + 'HealthInsights' + 'ImmersiveReader' + 'Internal.AllInOne' + 'LUIS' + 'LUIS.Authoring' + 'LanguageAuthoring' + 'MetricsAdvisor' + 'OpenAI' + 'Personalizer' + 'QnAMaker.v2' + 'SpeechServices' + 'TextAnalytics' + 'TextTranslation' +]) +param kind string + +@description('Required. The name of the AI Foundry project to create.') +param projectName string + +@description('Required. The description of the AI Foundry project to create.') +param projectDescription string + +@description('Optional. SKU of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.') +@allowed([ + 'C2' + 'C3' + 'C4' + 'F0' + 'F1' + 'S' + 'S0' + 'S1' + 'S10' + 'S2' + 'S3' + 'S4' + 'S5' + 'S6' + 'S7' + 'S8' + 'S9' +]) +param sku string = 'S0' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingFullType[]? + +@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.') +@allowed([ + 'Enabled' + 'Disabled' +]) +param publicNetworkAccess string? + +@description('Conditional. Subdomain name used for token-based authentication. Required if \'networkAcls\' or \'privateEndpoints\' are set.') +param customSubDomainName string? + +@description('Optional. A collection of rules governing the accessibility from specific network locations.') +param networkAcls object? + +@description('Optional. The network injection subnet resource Id for the Cognitive Services account. This allows to use the AI Services account with a virtual network.') +param networkInjectionSubnetResourceId string? + +import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointSingleServiceType[]? + +import { lockType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The lock settings of the service.') +param lock lockType? + +import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType[]? + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. List of allowed FQDN.') +param allowedFqdnList array? + +@description('Optional. The API properties for special APIs.') +param apiProperties object? + +@description('Optional. Allow only Azure AD authentication. Should be enabled for security reasons.') +param disableLocalAuth bool = true + +import { customerManagedKeyType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The customer managed key definition.') +param customerManagedKey customerManagedKeyType? + +@description('Optional. The flag to enable dynamic throttling.') +param dynamicThrottlingEnabled bool = false + +@secure() +@description('Optional. Resource migration token.') +param migrationToken string? + +@description('Optional. Restore a soft-deleted cognitive service at deployment time. Will fail if no such soft-deleted resource exists.') +param restore bool = false + +@description('Optional. Restrict outbound network access.') +param restrictOutboundNetworkAccess bool = true + +@description('Optional. The storage accounts for this resource.') +param userOwnedStorage array? + +import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentityAllType? + +@description('Optional. Array of deployments about cognitive service accounts to create.') +param deployments deploymentType[]? + +@description('Optional. The resource ID of an existing Foundry project to use.') +param existingFoundryProjectResourceId string = '' + +@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}': {} }), + {}, + (cur, next) => union(cur, next) +) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } +var identity = !empty(managedIdentities) + ? { + type: (managedIdentities.?systemAssigned ?? false) + ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned') + : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null) + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null + } + : null + +resource cMKKeyVault 'Microsoft.KeyVault/vaults@2024-11-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId)) { + name: last(split(customerManagedKey.?keyVaultResourceId!, '/')) + scope: resourceGroup( + split(customerManagedKey.?keyVaultResourceId!, '/')[2], + split(customerManagedKey.?keyVaultResourceId!, '/')[4] + ) + + resource cMKKey 'keys@2024-11-01' existing = if (!empty(customerManagedKey.?keyVaultResourceId) && !empty(customerManagedKey.?keyName)) { + name: customerManagedKey.?keyName! + } +} + +resource cMKUserAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = if (!empty(customerManagedKey.?userAssignedIdentityResourceId)) { + name: last(split(customerManagedKey.?userAssignedIdentityResourceId!, '/')) + scope: resourceGroup( + split(customerManagedKey.?userAssignedIdentityResourceId!, '/')[2], + split(customerManagedKey.?userAssignedIdentityResourceId!, '/')[4] + ) +} + +var useExistingService = !empty(existingFoundryProjectResourceId) + +resource cognitiveServiceNew 'Microsoft.CognitiveServices/accounts@2025-06-01' = if(!useExistingService) { + name: name + kind: kind + identity: identity + location: location + tags: tags + sku: { + name: sku + } + properties: { + customSubDomainName: customSubDomainName + allowProjectManagement: true + networkAcls: !empty(networkAcls ?? {}) + ? { + defaultAction: networkAcls.?defaultAction + virtualNetworkRules: networkAcls.?virtualNetworkRules ?? [] + ipRules: networkAcls.?ipRules ?? [] + } + : null + publicNetworkAccess: publicNetworkAccess != null + ? publicNetworkAccess + : (!empty(networkAcls) ? 'Enabled' : 'Disabled') + allowedFqdnList: allowedFqdnList + apiProperties: apiProperties + disableLocalAuth: disableLocalAuth + #disable-next-line BCP036 + networkInjections: networkInjectionSubnetResourceId != null + ? [ + { + scenario: 'agent' + subnetArmId: networkInjectionSubnetResourceId + useMicrosoftManagedNetwork: false + } + ] + : null + // true is not supported today + encryption: !empty(customerManagedKey) + ? { + keySource: 'Microsoft.KeyVault' + keyVaultProperties: { + identityClientId: !empty(customerManagedKey.?userAssignedIdentityResourceId ?? '') + ? cMKUserAssignedIdentity!.properties.clientId + : null + keyVaultUri: cMKKeyVault!.properties.vaultUri + keyName: customerManagedKey!.keyName + keyVersion: !empty(customerManagedKey.?keyVersion ?? '') + ? customerManagedKey!.?keyVersion + : last(split(cMKKeyVault::cMKKey!.properties.keyUriWithVersion, '/')) + } + } + : null + migrationToken: migrationToken + restore: restore + restrictOutboundNetworkAccess: restrictOutboundNetworkAccess + userOwnedStorage: userOwnedStorage + dynamicThrottlingEnabled: dynamicThrottlingEnabled + } +} + +var existingCognitiveServiceDetails = split(existingFoundryProjectResourceId, '/') + +resource cognitiveServiceExisting 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = if(useExistingService) { + name: existingCognitiveServiceDetails[8] + scope: resourceGroup(existingCognitiveServiceDetails[2], existingCognitiveServiceDetails[4]) +} + +module cognitive_service_dependencies './dependencies.bicep' = if(!useExistingService) { + params: { + projectName: projectName + projectDescription: projectDescription + name: cognitiveServiceNew.name + location: location + deployments: deployments + diagnosticSettings: diagnosticSettings + lock: lock + privateEndpoints: privateEndpoints + roleAssignments: roleAssignments + secretsExportConfiguration: secretsExportConfiguration + sku: sku + tags: tags + } +} + +module existing_cognitive_service_dependencies './dependencies.bicep' = if(useExistingService) { + params: { + name: cognitiveServiceExisting.name + projectName: projectName + projectDescription: projectDescription + existingFoundryProjectResourceId: existingFoundryProjectResourceId + location: location + deployments: deployments + diagnosticSettings: diagnosticSettings + lock: lock + privateEndpoints: privateEndpoints + roleAssignments: roleAssignments + secretsExportConfiguration: secretsExportConfiguration + sku: sku + tags: tags + } + scope: resourceGroup(existingCognitiveServiceDetails[2], existingCognitiveServiceDetails[4]) +} + +var cognitiveService = useExistingService ? cognitiveServiceExisting : cognitiveServiceNew + +@description('The name of the cognitive services account.') +output name string = useExistingService ? cognitiveServiceExisting.name : cognitiveServiceNew.name + +@description('The resource ID of the cognitive services account.') +output resourceId string = useExistingService ? cognitiveServiceExisting.id : cognitiveServiceNew.id + +@description('The resource group the cognitive services account was deployed into.') +output subscriptionId string = useExistingService ? existingCognitiveServiceDetails[2] : subscription().subscriptionId + +@description('The resource group the cognitive services account was deployed into.') +output resourceGroupName string = useExistingService ? existingCognitiveServiceDetails[4] : resourceGroup().name + +@description('The service endpoint of the cognitive services account.') +output endpoint string = useExistingService ? cognitiveServiceExisting!.properties.endpoint : cognitiveService.properties.endpoint + +@description('All endpoints available for the cognitive services account, types depends on the cognitive service kind.') +output endpoints endpointType = useExistingService ? cognitiveServiceExisting!.properties.endpoints : cognitiveService.properties.endpoints + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string? = useExistingService ? cognitiveServiceExisting!.identity.principalId : cognitiveService.?identity.?principalId + +@description('The location the resource was deployed into.') +output location string = useExistingService ? cognitiveServiceExisting!.location : cognitiveService.location + +import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@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 = useExistingService ? existing_cognitive_service_dependencies!.outputs.exportedSecrets : cognitive_service_dependencies!.outputs.exportedSecrets + +@description('The private endpoints of the congitive services account.') +output privateEndpoints privateEndpointOutputType[] = useExistingService ? existing_cognitive_service_dependencies!.outputs.privateEndpoints : cognitive_service_dependencies!.outputs.privateEndpoints + +import { aiProjectOutputType } from './project.bicep' +output aiProjectInfo aiProjectOutputType = useExistingService ? existing_cognitive_service_dependencies!.outputs.aiProjectInfo : cognitive_service_dependencies!.outputs.aiProjectInfo + +// ================ // +// Definitions // +// ================ // + +@export() +@description('The type for the private endpoint output.') +type privateEndpointOutputType = { + @description('The name of the private endpoint.') + name: string + + @description('The resource ID of the private endpoint.') + resourceId: string + + @description('The group Id for the private endpoint Group.') + groupId: string? + + @description('The custom DNS configurations of the private endpoint.') + customDnsConfigs: { + @description('FQDN that resolves to private endpoint IP address.') + fqdn: string? + + @description('A list of private IP addresses of the private endpoint.') + ipAddresses: string[] + }[] + + @description('The IDs of the network interfaces associated with the private endpoint.') + networkInterfaceResourceIds: string[] +} + +@export() +@description('The type for a cognitive services account deployment.') +type deploymentType = { + @description('Optional. Specify the name of cognitive service account deployment.') + name: string? + + @description('Required. Properties of Cognitive Services account deployment model.') + model: { + @description('Required. The name of Cognitive Services account deployment model.') + name: string + + @description('Required. The format of Cognitive Services account deployment model.') + format: string + + @description('Required. The version of Cognitive Services account deployment model.') + version: string + } + + @description('Optional. The resource model definition representing SKU.') + sku: { + @description('Required. The name of the resource model definition representing SKU.') + name: string + + @description('Optional. The capacity of the resource model definition representing SKU.') + capacity: int? + + @description('Optional. The tier of the resource model definition representing SKU.') + tier: string? + + @description('Optional. The size of the resource model definition representing SKU.') + size: string? + + @description('Optional. The family of the resource model definition representing SKU.') + family: string? + }? + + @description('Optional. The name of RAI policy.') + raiPolicyName: string? + + @description('Optional. The version upgrade option.') + versionUpgradeOption: string? +} + +@export() +@description('The type for a cognitive services account endpoint.') +type endpointType = { + @description('Type of the endpoint.') + name: string? + @description('The endpoint URI.') + endpoint: string? +} + +@export() +@description('The type of the secrets exported to the provided Key Vault.') +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? +} diff --git a/infra/modules/dependencies.bicep b/infra/modules/dependencies.bicep new file mode 100644 index 000000000..9c9efb278 --- /dev/null +++ b/infra/modules/dependencies.bicep @@ -0,0 +1,479 @@ +@description('Required. The name of Cognitive Services account.') +param name string + +@description('Optional. SKU of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.') +@allowed([ + 'C2' + 'C3' + 'C4' + 'F0' + 'F1' + 'S' + 'S0' + 'S1' + 'S10' + 'S2' + 'S3' + 'S4' + 'S5' + 'S6' + 'S7' + 'S8' + 'S9' +]) +param sku string = 'S0' + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. Array of deployments about cognitive service accounts to create.') +param deployments deploymentType[]? + +@description('Optional. Key vault reference and secret settings for the module\'s secrets export.') +param secretsExportConfiguration secretsExportConfigurationType? + +import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointSingleServiceType[]? + +import { lockType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The lock settings of the service.') +param lock lockType? + +import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType[]? + +import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingFullType[]? + +@description('Optional. Name for the project which needs to be created.') +param projectName string + +@description('Optional. Description for the project which needs to be created.') +param projectDescription string + +@description('Optional. Provide the existing project resource id in case if it needs to be reused') +param existingFoundryProjectResourceId string = '' + +var builtInRoleNames = { + 'Cognitive Services Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' + ) + 'Cognitive Services Custom Vision Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3' + ) + 'Cognitive Services Custom Vision Deployment': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '5c4089e1-6d96-4d2f-b296-c1bc7137275f' + ) + 'Cognitive Services Custom Vision Labeler': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '88424f51-ebe7-446f-bc41-7fa16989e96c' + ) + 'Cognitive Services Custom Vision Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '93586559-c37d-4a6b-ba08-b9f0940c2d73' + ) + 'Cognitive Services Custom Vision Trainer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '0a5ae4ab-0d65-4eeb-be61-29fc9b54394b' + ) + 'Cognitive Services Data Reader (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'b59867f0-fa02-499b-be73-45a86b5b3e1c' + ) + 'Cognitive Services Face Recognizer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '9894cab4-e18a-44aa-828b-cb588cd6f2d7' + ) + 'Cognitive Services Immersive Reader User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'b2de6794-95db-4659-8781-7e080d3f2b9d' + ) + 'Cognitive Services Language Owner': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f07febfe-79bc-46b1-8b37-790e26e6e498' + ) + 'Cognitive Services Language Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7628b7b8-a8b2-4cdc-b46f-e9b35248918e' + ) + 'Cognitive Services Language Writer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8' + ) + 'Cognitive Services LUIS Owner': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f72c8140-2111-481c-87ff-72b910f6e3f8' + ) + 'Cognitive Services LUIS Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18e81cdc-4e98-4e29-a639-e7d10c5a6226' + ) + 'Cognitive Services LUIS Writer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '6322a993-d5c9-4bed-b113-e49bbea25b27' + ) + 'Cognitive Services Metrics Advisor Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'cb43c632-a144-4ec5-977c-e80c4affc34a' + ) + 'Cognitive Services Metrics Advisor User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '3b20f47b-3825-43cb-8114-4bd2201156a8' + ) + 'Cognitive Services OpenAI Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'a001fd3d-188f-4b5d-821b-7da978bf7442' + ) + 'Cognitive Services OpenAI User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + ) + 'Cognitive Services QnA Maker Editor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f4cc2bf9-21be-47a1-bdf1-5c5804381025' + ) + 'Cognitive Services QnA Maker Reader': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '466ccd10-b268-4a11-b098-b4849f024126' + ) + 'Cognitive Services Speech Contributor': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '0e75ca1e-0464-4b4d-8b93-68208a576181' + ) + 'Cognitive Services Speech User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f2dc8367-1007-4938-bd23-fe263f013447' + ) + 'Cognitive Services User': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'a97b65f3-24c7-4388-baec-2e87135dc908' + ) + 'Azure AI Developer': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '64702f94-c441-49e6-a78b-ef80e0188fee' + ) + 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' + ) +} + +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 enableReferencedModulesTelemetry = false + +resource cognitiveService 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = { + name: name +} + +@batchSize(1) +resource cognitiveService_deployments 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = [ + for (deployment, index) in (deployments ?? []): { + parent: cognitiveService + name: deployment.?name ?? '${name}-deployments' + properties: { + model: deployment.model + raiPolicyName: deployment.?raiPolicyName + versionUpgradeOption: deployment.?versionUpgradeOption + } + sku: deployment.?sku ?? { + name: sku + capacity: sku.?capacity + tier: sku.?tier + size: sku.?size + family: sku.?family + } + } +] + +resource cognitiveService_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: cognitiveService +} + +resource cognitiveService_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: cognitiveService + } +] + +module cognitiveService_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.0' = [ + for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-cognitiveService-PrivateEndpoint-${index}' + scope: resourceGroup( + split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2], + split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4] + ) + params: { + name: privateEndpoint.?name ?? 'pep-${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}' + privateLinkServiceConnections: privateEndpoint.?isManualConnection != true + ? [ + { + name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}' + properties: { + privateLinkServiceId: cognitiveService.id + groupIds: [ + privateEndpoint.?service ?? 'account' + ] + } + } + ] + : null + manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true + ? [ + { + name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}' + properties: { + privateLinkServiceId: cognitiveService.id + groupIds: [ + privateEndpoint.?service ?? 'account' + ] + requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.' + } + } + ] + : null + subnetResourceId: privateEndpoint.subnetResourceId + enableTelemetry: enableReferencedModulesTelemetry + location: privateEndpoint.?location ?? reference( + split(privateEndpoint.subnetResourceId, '/subnets/')[0], + '2020-06-01', + 'Full' + ).location + lock: privateEndpoint.?lock ?? lock + privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + } + } +] + +resource cognitiveService_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (formattedRoleAssignments ?? []): { + name: roleAssignment.?name ?? guid(cognitiveService.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: cognitiveService + } +] + +module secretsExport './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 + } + ] + : [] + ) + } +} + +module aiProject 'project.bicep' = if(!empty(projectName) || !empty(existingFoundryProjectResourceId)) { + name: take('${name}-ai-project-${projectName}-deployment', 64) + params: { + name: projectName + desc: projectDescription + aiServicesName: cognitiveService.name + location: location + tags: tags + existingFoundryProjectResourceId: existingFoundryProjectResourceId + } +} + +import { secretsOutputType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@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) + : {} + +@description('The private endpoints of the congitive services account.') +output privateEndpoints privateEndpointOutputType[] = [ + for (pe, index) in (privateEndpoints ?? []): { + name: cognitiveService_privateEndpoints[index].outputs.name + resourceId: cognitiveService_privateEndpoints[index].outputs.resourceId + groupId: cognitiveService_privateEndpoints[index].outputs.?groupId! + customDnsConfigs: cognitiveService_privateEndpoints[index].outputs.customDnsConfigs + networkInterfaceResourceIds: cognitiveService_privateEndpoints[index].outputs.networkInterfaceResourceIds + } +] + +import { aiProjectOutputType } from 'project.bicep' +output aiProjectInfo aiProjectOutputType = aiProject!.outputs.aiProjectInfo + +// ================ // +// Definitions // +// ================ // + +@export() +@description('The type for the private endpoint output.') +type privateEndpointOutputType = { + @description('The name of the private endpoint.') + name: string + + @description('The resource ID of the private endpoint.') + resourceId: string + + @description('The group Id for the private endpoint Group.') + groupId: string? + + @description('The custom DNS configurations of the private endpoint.') + customDnsConfigs: { + @description('FQDN that resolves to private endpoint IP address.') + fqdn: string? + + @description('A list of private IP addresses of the private endpoint.') + ipAddresses: string[] + }[] + + @description('The IDs of the network interfaces associated with the private endpoint.') + networkInterfaceResourceIds: string[] +} + +@export() +@description('The type for a cognitive services account deployment.') +type deploymentType = { + @description('Optional. Specify the name of cognitive service account deployment.') + name: string? + + @description('Required. Properties of Cognitive Services account deployment model.') + model: { + @description('Required. The name of Cognitive Services account deployment model.') + name: string + + @description('Required. The format of Cognitive Services account deployment model.') + format: string + + @description('Required. The version of Cognitive Services account deployment model.') + version: string + } + + @description('Optional. The resource model definition representing SKU.') + sku: { + @description('Required. The name of the resource model definition representing SKU.') + name: string + + @description('Optional. The capacity of the resource model definition representing SKU.') + capacity: int? + + @description('Optional. The tier of the resource model definition representing SKU.') + tier: string? + + @description('Optional. The size of the resource model definition representing SKU.') + size: string? + + @description('Optional. The family of the resource model definition representing SKU.') + family: string? + }? + + @description('Optional. The name of RAI policy.') + raiPolicyName: string? + + @description('Optional. The version upgrade option.') + versionUpgradeOption: string? +} + +@export() +@description('The type for a cognitive services account endpoint.') +type endpointType = { + @description('Type of the endpoint.') + name: string? + @description('The endpoint URI.') + endpoint: string? +} + +@export() +@description('The type of the secrets exported to the provided Key Vault.') +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? +} diff --git a/infra/modules/keyVaultExport.bicep b/infra/modules/keyVaultExport.bicep new file mode 100644 index 000000000..a54cc5576 --- /dev/null +++ b/infra/modules/keyVaultExport.bicep @@ -0,0 +1,43 @@ +// ============== // +// Parameters // +// ============== // + +@description('Required. The name of the Key Vault to set the ecrets in.') +param keyVaultName string + +import { secretToSetType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Required. The secrets to set in the Key Vault.') +param secretsToSet secretToSetType[] + +// ============= // +// Resources // +// ============= // + +resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { + name: keyVaultName +} + +resource secrets 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = [ + for secret in secretsToSet: { + name: secret.name + parent: keyVault + properties: { + value: secret.value + } + } +] + +// =========== // +// Outputs // +// =========== // + +import { secretSetOutputType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('The references to the secrets exported to the provided Key Vault.') +output secretsSet secretSetOutputType[] = [ + #disable-next-line outputs-should-not-contain-secrets // Only returning the references, not a secret value + for index in range(0, length(secretsToSet ?? [])): { + secretResourceId: secrets[index].id + secretUri: secrets[index].properties.secretUri + secretUriWithVersion: secrets[index].properties.secretUriWithVersion + } +] diff --git a/infra/modules/project.bicep b/infra/modules/project.bicep new file mode 100644 index 000000000..f2964c875 --- /dev/null +++ b/infra/modules/project.bicep @@ -0,0 +1,62 @@ +@description('Required. Name of the AI Services project.') +param name string + +@description('Required. The location of the Project resource.') +param location string = resourceGroup().location + +@description('Optional. The description of the AI Foundry project to create. Defaults to the project name.') +param desc string = name + +@description('Required. Name of the existing Cognitive Services resource to create the AI Foundry project in.') +param aiServicesName string + +@description('Optional. Tags to be applied to the resources.') +param tags object = {} + +@description('Optional. Use this parameter to use an existing AI project resource ID from different resource group') +param existingFoundryProjectResourceId string = '' + +// // Extract components from existing AI Project Resource ID if provided +var useExistingProject = !empty(existingFoundryProjectResourceId) +var existingProjName = useExistingProject ? last(split(existingFoundryProjectResourceId, '/')) : '' +var existingProjEndpoint = useExistingProject ? format('https://{0}.services.ai.azure.com/api/projects/{1}', aiServicesName, existingProjName) : '' + +// Reference to cognitive service in current resource group for new projects +resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = { + name: aiServicesName +} + +// Create new AI project only if not reusing existing one +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' = if(!useExistingProject) { + parent: cogServiceReference + name: name + tags: tags + location: location + identity: { + type: 'SystemAssigned' + } + properties: { + description: desc + displayName: name + } +} + +@description('AI Project metadata including name, resource ID, and API endpoint.') +output aiProjectInfo aiProjectOutputType = { + name: useExistingProject ? existingProjName : aiProject.name + resourceId: useExistingProject ? existingFoundryProjectResourceId : aiProject.id + apiEndpoint: useExistingProject ? existingProjEndpoint : aiProject!.properties.endpoints['AI Foundry API'] +} + +@export() +@description('Output type representing AI project information.') +type aiProjectOutputType = { + @description('Required. Name of the AI project.') + name: string + + @description('Required. Resource ID of the AI project.') + resourceId: string + + @description('Required. API endpoint for the AI project.') + apiEndpoint: string +} diff --git a/infra/modules/web-sites.bicep b/infra/modules/web-sites.bicep new file mode 100644 index 000000000..520f33669 --- /dev/null +++ b/infra/modules/web-sites.bicep @@ -0,0 +1,368 @@ +@description('Required. Name of the site.') +param name string + +@description('Optional. Location for all Resources.') +param location string = resourceGroup().location + +@description('Required. Type of site to deploy.') +@allowed([ + 'functionapp' // function app windows os + 'functionapp,linux' // function app linux os + 'functionapp,workflowapp' // logic app workflow + 'functionapp,workflowapp,linux' // logic app docker container + 'functionapp,linux,container' // function app linux container + 'functionapp,linux,container,azurecontainerapps' // function app linux container azure container apps + 'app,linux' // linux web app + 'app' // windows web app + 'linux,api' // linux api app + 'api' // windows api app + 'app,linux,container' // linux container app + 'app,container,windows' // windows container app +]) +param kind string + +@description('Required. The resource ID of the app service plan to use for the site.') +param serverFarmResourceId string + +@description('Optional. Azure Resource Manager ID of the customers selected Managed Environment on which to host this app.') +param managedEnvironmentId string? + +@description('Optional. Configures a site to accept only HTTPS requests. Issues redirect for HTTP requests.') +param httpsOnly bool = true + +@description('Optional. If client affinity is enabled.') +param clientAffinityEnabled bool = true + +@description('Optional. The resource ID of the app service environment to use for this resource.') +param appServiceEnvironmentResourceId string? + +import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The managed identity definition for this resource.') +param managedIdentities managedIdentityAllType? + +@description('Optional. The resource ID of the assigned identity to be used to access a key vault with.') +param keyVaultAccessIdentityResourceId string? + +@description('Optional. Checks if Customer provided storage account is required.') +param storageAccountRequired bool = false + +@description('Optional. Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration. This must be of the form /subscriptions/{subscriptionName}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}.') +param virtualNetworkSubnetId string? + +@description('Optional. To enable accessing content over virtual network.') +param vnetContentShareEnabled bool = false + +@description('Optional. To enable pulling image over Virtual Network.') +param vnetImagePullEnabled bool = false + +@description('Optional. Virtual Network Route All enabled. This causes all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied.') +param vnetRouteAllEnabled bool = false + +@description('Optional. Stop SCM (KUDU) site when the app is stopped.') +param scmSiteAlsoStopped bool = false + +@description('Optional. The site config object. The defaults are set to the following values: alwaysOn: true, minTlsVersion: \'1.2\', ftpsState: \'FtpsOnly\'.') +param siteConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.siteConfig = { + alwaysOn: true + minTlsVersion: '1.2' + ftpsState: 'FtpsOnly' +} + +@description('Optional. The web site config.') +param configs appSettingsConfigType[]? + +@description('Optional. The Function App configuration object.') +param functionAppConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.functionAppConfig? + +import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.') +param privateEndpoints privateEndpointSingleServiceType[]? + +@description('Optional. Tags of the resource.') +param tags object? + +import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingFullType[]? + +@description('Optional. To enable client certificate authentication (TLS mutual authentication).') +param clientCertEnabled bool = false + +@description('Optional. Client certificate authentication comma-separated exclusion paths.') +param clientCertExclusionPaths string? + +@description(''' +Optional. This composes with ClientCertEnabled setting. +- ClientCertEnabled=false means ClientCert is ignored. +- ClientCertEnabled=true and ClientCertMode=Required means ClientCert is required. +- ClientCertEnabled=true and ClientCertMode=Optional means ClientCert is optional or accepted. +''') +@allowed([ + 'Optional' + 'OptionalInteractiveUser' + 'Required' +]) +param clientCertMode string = 'Optional' + +@description('Optional. If specified during app creation, the app is cloned from a source app.') +param cloningInfo resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.cloningInfo? + +@description('Optional. Size of the function container.') +param containerSize int? + +@description('Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only).') +param dailyMemoryTimeQuota int? + +@description('Optional. Setting this value to false disables the app (takes the app offline).') +param enabled bool = true + +@description('Optional. Hostname SSL states are used to manage the SSL bindings for app\'s hostnames.') +param hostNameSslStates resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.hostNameSslStates? + +@description('Optional. Hyper-V sandbox.') +param hyperV bool = false + +@description('Optional. Site redundancy mode.') +@allowed([ + 'ActiveActive' + 'Failover' + 'GeoRedundant' + 'Manual' + 'None' +]) +param redundancyMode string = 'None' + +@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.') +@allowed([ + 'Enabled' + 'Disabled' +]) +param publicNetworkAccess string? + +@description('Optional. End to End Encryption Setting.') +param e2eEncryptionEnabled bool? + +@description('Optional. Property to configure various DNS related settings for a site.') +param dnsConfiguration resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.dnsConfiguration? + +@description('Optional. Specifies the scope of uniqueness for the default hostname during resource creation.') +@allowed([ + 'NoReuse' + 'ResourceGroupReuse' + 'SubscriptionReuse' + 'TenantReuse' +]) +param autoGeneratedDomainNameLabelScope string? + +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 + +resource app 'Microsoft.Web/sites@2024-04-01' = { + name: name + location: location + kind: kind + tags: tags + identity: identity + properties: { + managedEnvironmentId: !empty(managedEnvironmentId) ? managedEnvironmentId : null + serverFarmId: serverFarmResourceId + clientAffinityEnabled: clientAffinityEnabled + httpsOnly: httpsOnly + hostingEnvironmentProfile: !empty(appServiceEnvironmentResourceId) + ? { + id: appServiceEnvironmentResourceId + } + : null + storageAccountRequired: storageAccountRequired + keyVaultReferenceIdentity: keyVaultAccessIdentityResourceId + virtualNetworkSubnetId: virtualNetworkSubnetId + siteConfig: siteConfig + functionAppConfig: functionAppConfig + clientCertEnabled: clientCertEnabled + clientCertExclusionPaths: clientCertExclusionPaths + clientCertMode: clientCertMode + cloningInfo: cloningInfo + containerSize: containerSize + dailyMemoryTimeQuota: dailyMemoryTimeQuota + enabled: enabled + hostNameSslStates: hostNameSslStates + hyperV: hyperV + redundancyMode: redundancyMode + publicNetworkAccess: !empty(publicNetworkAccess) + ? any(publicNetworkAccess) + : (!empty(privateEndpoints) ? 'Disabled' : 'Enabled') + vnetContentShareEnabled: vnetContentShareEnabled + vnetImagePullEnabled: vnetImagePullEnabled + vnetRouteAllEnabled: vnetRouteAllEnabled + scmSiteAlsoStopped: scmSiteAlsoStopped + endToEndEncryptionEnabled: e2eEncryptionEnabled + dnsConfiguration: dnsConfiguration + autoGeneratedDomainNameLabelScope: autoGeneratedDomainNameLabelScope + } +} + +module app_config 'web-sites.config.bicep' = [ + for (config, index) in (configs ?? []): { + name: '${uniqueString(deployment().name, location)}-Site-Config-${index}' + params: { + appName: app.name + name: config.name + applicationInsightResourceId: config.?applicationInsightResourceId + storageAccountResourceId: config.?storageAccountResourceId + storageAccountUseIdentityAuthentication: config.?storageAccountUseIdentityAuthentication + properties: config.?properties + currentAppSettings: config.?retainCurrentAppSettings ?? true && config.name == 'appsettings' + ? list('${app.id}/config/appsettings', '2023-12-01').properties + : {} + } + } +] + +#disable-next-line use-recent-api-versions +resource app_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: app + } +] + +module app_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.0' = [ + for (privateEndpoint, index) in (privateEndpoints ?? []): { + name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' + scope: resourceGroup( + split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2], + split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4] + ) + params: { + name: privateEndpoint.?name ?? 'pep-${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' + privateLinkServiceConnections: privateEndpoint.?isManualConnection != true + ? [ + { + name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' + properties: { + privateLinkServiceId: app.id + groupIds: [ + privateEndpoint.?service ?? 'sites' + ] + } + } + ] + : null + manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true + ? [ + { + name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(app.id, '/'))}-${privateEndpoint.?service ?? 'sites'}-${index}' + properties: { + privateLinkServiceId: app.id + groupIds: [ + privateEndpoint.?service ?? 'sites' + ] + requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.' + } + } + ] + : null + subnetResourceId: privateEndpoint.subnetResourceId + enableTelemetry: false //As per https://azure.github.io/Azure-Verified-Modules/spec/BCPFR7/ + location: privateEndpoint.?location ?? reference( + split(privateEndpoint.subnetResourceId, '/subnets/')[0], + '2020-06-01', + 'Full' + ).location + lock: privateEndpoint.?lock ?? null + privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup + roleAssignments: privateEndpoint.?roleAssignments + tags: privateEndpoint.?tags ?? tags + customDnsConfigs: privateEndpoint.?customDnsConfigs + ipConfigurations: privateEndpoint.?ipConfigurations + applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds + customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName + } + } +] + +@description('The name of the site.') +output name string = app.name + +@description('The resource ID of the site.') +output resourceId string = app.id + +@description('The resource group the site was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string? = app.?identity.?principalId + +@description('The location the resource was deployed into.') +output location string = app.location + +@description('Default hostname of the app.') +output defaultHostname string = app.properties.defaultHostName + +@description('Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification.') +output customDomainVerificationId string = app.properties.customDomainVerificationId + +@description('The outbound IP addresses of the app.') +output outboundIpAddresses string = app.properties.outboundIpAddresses + +// ================ // +// Definitions // +// ================ // +@export() +@description('The type of an app settings configuration.') +type appSettingsConfigType = { + @description('Required. The type of config.') + name: 'appsettings' + + @description('Optional. 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\'.') + storageAccountUseIdentityAuthentication: bool? + + @description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the application insight to leverage for this resource.') + applicationInsightResourceId: string? + + @description('Optional. The retain the current app settings. Defaults to true.') + retainCurrentAppSettings: bool? + + @description('Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING.') + properties: { + @description('Required. An app settings key-value pair.') + *: string + }? +} diff --git a/infra/modules/web-sites.config.bicep b/infra/modules/web-sites.config.bicep new file mode 100644 index 000000000..130a9806b --- /dev/null +++ b/infra/modules/web-sites.config.bicep @@ -0,0 +1,91 @@ +metadata name = 'Site App Settings' +metadata description = 'This module deploys a Site App Setting.' + +@description('Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment.') +param appName string + +@description('Required. The name of the config.') +@allowed([ + 'appsettings' + 'authsettings' + 'authsettingsV2' + 'azurestorageaccounts' + 'backup' + 'connectionstrings' + 'logs' + 'metadata' + 'pushsettings' + 'slotConfigNames' + 'web' +]) +param name string + +@description('Optional. The properties of the config. Note: This parameter is highly dependent on the config type, defined by its name.') +param properties object = {} + +// Parameters only relevant for the config type 'appsettings' +@description('Optional. 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\'.') +param storageAccountUseIdentityAuthentication bool = false + +@description('Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions.') +param storageAccountResourceId string? + +@description('Optional. Resource ID of the application insight to leverage for this resource.') +param applicationInsightResourceId string? + +@description('Optional. The current app settings.') +param currentAppSettings { + @description('Required. The key-values pairs of the current app settings.') + *: string +} = {} + +var azureWebJobsValues = !empty(storageAccountResourceId) && !storageAccountUseIdentityAuthentication + ? { + AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount!.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + } + : !empty(storageAccountResourceId) && storageAccountUseIdentityAuthentication + ? { + AzureWebJobsStorage__accountName: storageAccount.name + AzureWebJobsStorage__blobServiceUri: storageAccount!.properties.primaryEndpoints.blob + AzureWebJobsStorage__queueServiceUri: storageAccount!.properties.primaryEndpoints.queue + AzureWebJobsStorage__tableServiceUri: storageAccount!.properties.primaryEndpoints.table + } + : {} + +var appInsightsValues = !empty(applicationInsightResourceId) + ? { + APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights!.properties.ConnectionString + } + : {} + +var expandedProperties = union(currentAppSettings, properties, azureWebJobsValues, appInsightsValues) + +resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightResourceId)) { + name: last(split(applicationInsightResourceId!, '/')) + scope: resourceGroup(split(applicationInsightResourceId!, '/')[2], split(applicationInsightResourceId!, '/')[4]) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = if (!empty(storageAccountResourceId)) { + name: last(split(storageAccountResourceId!, '/')) + scope: resourceGroup(split(storageAccountResourceId!, '/')[2], split(storageAccountResourceId!, '/')[4]) +} + +resource app 'Microsoft.Web/sites@2023-12-01' existing = { + name: appName +} + +resource config 'Microsoft.Web/sites/config@2024-04-01' = { + parent: app + #disable-next-line BCP225 + name: name + properties: expandedProperties +} + +@description('The name of the site config.') +output name string = config.name + +@description('The resource ID of the site config.') +output resourceId string = config.id + +@description('The resource group the site config was deployed into.') +output resourceGroupName string = resourceGroup().name From 9c0b3da613085697bef272fcb6ef6fc2337c95bc Mon Sep 17 00:00:00 2001 From: VishalS-Microsoft Date: Tue, 26 Aug 2025 12:39:01 +0530 Subject: [PATCH 42/84] docs: Added Troubleshoot.md files for BYOC-Client Advisor (#650) * Added Troubleshoot.md files for BYOC-Client Advisor * fixed Check Markdown Broken Links * updated troubleshotingsteps file * Changed MACAE Github issues link with BYOC issue link --------- Co-authored-by: NirajC-Microsoft --- docs/DeploymentGuide.md | 3 + docs/TroubleShootingSteps.md | 355 +++++++++++++++++++++++++++++++++ docs/images/AzureHomePage.png | Bin 0 -> 68688 bytes docs/images/resourcegroup1.png | Bin 0 -> 55920 bytes 4 files changed, 358 insertions(+) create mode 100644 docs/TroubleShootingSteps.md create mode 100644 docs/images/AzureHomePage.png create mode 100644 docs/images/resourcegroup1.png diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index 2af2e32f5..f8196592a 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -217,6 +217,9 @@ If you need to rebuild the source code and push the updated container to the dep This will rebuild the source code, package it into a container, and push it to the Azure Container Registry associated with your deployment. +### 🛠️ Troubleshooting + If you encounter any issues during the deployment process, please refer [troubleshooting](../docs/TroubleShootingSteps.md) document for detailed steps and solutions + ## Post Deployment Steps 1. **Import Sample Data** diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md new file mode 100644 index 000000000..af1c2e346 --- /dev/null +++ b/docs/TroubleShootingSteps.md @@ -0,0 +1,355 @@ + +# 🛠️ Troubleshooting + When deploying Azure resources, you may come across different error codes that stop or delay the deployment process. This section lists some of the most common errors along with possible causes and step-by-step resolutions. + +Use these as quick reference guides to unblock your deployments. + +## Error Codes + +
+ReadOnlyDisabledSubscription + +- Check if you have an active subscription before starting the deployment. + +
+ +
+ MissingSubscriptionRegistration/ AllowBringYourOwnPublicIpAddress/ InvalidAuthenticationToken + + +Enable `AllowBringYourOwnPublicIpAddress` Feature + +Before deploying the resources, you may need to enable the **Bring Your Own Public IP Address** feature in Azure. This is required only once per subscription. + +### Steps + +1. **Run the following command to register the feature:** + + ```bash + az feature register --namespace Microsoft.Network --name AllowBringYourOwnPublicIpAddress + ``` + +2. **Wait for the registration to complete.** + You can check the status using: + + ```bash + az feature show --namespace Microsoft.Network --name AllowBringYourOwnPublicIpAddress --query properties.state + ``` + +3. **The output should show:** + "Registered" + +4. **Once the feature is registered, refresh the provider:** + + ```bash + az provider register --namespace Microsoft.Network + ``` + + 💡 Note: Feature registration may take several minutes to complete. This needs to be done only once per Azure subscription. + +
+ +
+ResourceGroupNotFound + +## Option 1 +### Steps + +1. Go to [Azure Portal](https:/portal.azure.com/#home). + +2. Click on the **"Resource groups"** option available on the Azure portal home page. +![alt text](../docs/images/AzureHomePage.png) + +3. In the Resource Groups search bar, search for the resource group you intend to target for deployment. If it exists, you can proceed with using it. +![alt text](../docs/images/resourcegroup1.png) + + ## Option 2 + +- This error can occur if you deploy the template using the same .env file - from a previous deployment. +- To avoid this issue, create a new environment before redeploying. +- You can use the following command to create a new environment: + ``` + azd env new + ``` +
+
+ResourceGroupBeingDeleted + +To prevent this issue, please ensure that the resource group you are targeting for deployment is not currently being deleted. You can follow steps to verify resource group is being deleted or not. +### Steps: +1. Go to [Azure Portal](https://portal.azure.com/#home) +2. Go to resource group option and search for targeted resource group +3. If Targeted resource group is there and deletion for this is in progress, it means u cannot use this, you can create new or use any other resource group + +
+ +
+InternalSubscriptionIsOverQuotaForSku/ManagedEnvironmentProvisioningError + +Quotas are applied per resource group, subscriptions, accounts, and other scopes. For example, your subscription might be configured to limit the number of vCPUs for a region. If you attempt to deploy a virtual machine with more vCPUs than the permitted amount, you receive an error that the quota was exceeded. +For PowerShell, use the `Get-AzVMUsage` cmdlet to find virtual machine quotas. +```ps +Get-AzVMUsage -Location "West US" +``` +based on available quota you can deploy application otherwise, you can request for more quota +
+ +
+InsufficientQuota + +- Check if you have sufficient quota available in your subscription before deployment. +- To verify, refer to the [quota_check](../docs/QuotaCheck.md) file for details. + +
+ +
+DeploymentModelNotSupported + + - The updated model may not be supported in the selected region. Please verify its availability in the [Azure AI Foundry models](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/models?tabs=global-standard%2Cstandard-chat-completions) document. + +
+
+LinkedInvalidPropertyId/ ResourceNotFound/DeploymentOutputEvaluationFailed/ CanNotRestoreANonExistingResource + +- Before using any resource ID, ensure it follows the correct format. +- Verify that the resource ID you are passing actually exists. +- Make sure there are no typos in the resource ID. +- Verify that the provisioning state of the existing resource is `Succeeded` by running the following command to avoid this error while deployment or restoring the resource. + + ``` + az resource show --ids --query "properties.provisioningState" + ``` +- Sample Resource IDs format + - Log Analytics Workspace Resource ID + ``` + /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.OperationalInsights/workspaces/{workspaceName} + ``` + - Azure AI Foundry Project Resource ID + ``` + /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/workspaces/{name} + ``` +- For more information refer [Resource Not Found errors solutions](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/error-not-found?tabs=bicep) + +
+
+ResourceNameInvalid + +- Ensure the resource name is within the allowed length and naming rules defined for that specific resource type, you can refer [Resource Naming Convention](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules) document. + +
+
+ServiceUnavailable/ResourceNotFound + + - Regions are restricted to guarantee compatibility with paired regions and replica locations for data redundancy and failover scenarios based on articles [Azure regions list](https://learn.microsoft.com/en-us/azure/reliability/regions-list) and [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions). + + - You can request more quota, refer [Quota Request](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/create-support-request-quota-increase) Documentation + + +
+
+Workspace Name - InvalidParameter + + To avoid this errors in workspace ID follow below rules. +1. Must start and end with an alphanumeric character (letter or number). +2. Allowed characters: + `a–z` + `0–9` + `- (hyphen)` +3. Cannot start or end with a hyphen -. +4. No spaces, underscores (_), periods (.), or special characters. +5. Must be unique within the Azure region & subscription. +6. Length: 3–33 characters (for AML workspaces). +
+
+BadRequest: Dns record under zone Document is already taken + +This error can occur only when user hardcoding the CosmosDB Service name. To avoid this you can try few below suggestions. +- Verify resource names are globally unique. +- If you already created an account/resource with same name in another subscription or resource group, check and delete it before reusing the name. +- By default in this template we are using unique prefix with every resource/account name to avoid this kind for errors. +
+
+NetcfgSubnetRangeOutsideVnet + +- Ensure the subnet’s IP address range falls within the virtual network’s address space. +- Always validate that the subnet CIDR block is a subset of the VNet range. +- For Azure Bastion, the AzureBastionSubnet must be at least /27. +- Confirm that the AzureBastionSubnet is deployed inside the VNet. +
+
+DisableExport_PublicNetworkAccessMustBeDisabled + +- Check container source: Confirm whether the deployment is using a Docker image or Azure Container Registry (ACR). +- Verify ACR configuration: If ACR is included, review its settings to ensure they comply with Azure requirements. +- Check export settings: If export is disabled in ACR, make sure public network access is also disabled. +- Dedeploy after fix: Correct the configuration and redeploy. This will prevent the Conflict error during deployment. +- For more information refer [ACR Data Loss Prevention](https://learn.microsoft.com/en-us/azure/container-registry/data-loss-prevention) document. +
+
+AccountProvisioningStateInvalid + +- The AccountProvisioningStateInvalid error occurs when you try to use resources while they are still in the Accepted provisioning state. +- This means the deployment has not yet fully completed. +- To avoid this error, wait until the provisioning state changes to Succeeded. +- Only use the resources once the deployment is fully completed. +
+
+VaultNameNotValid + + In this template Vault name will be unique everytime, but if you trying to hard code the name then please make sure below points. + 1. Check name length + - Ensure the Key Vault name is between 3 and 24 characters. + 2. Validate allowed characters + - The name can only contain letters (a–z, A–Z) and numbers (0–9). + - Hyphens are allowed, but not at the beginning or end, and not consecutive (--). +3. Ensure proper start and end + - The name must start with a letter. + - The name must end with a letter or digit (not a hyphen). +4. Test with a new name + - Example of a valid vault name: + ✅ `cartersaikeyvault1` + ✅ `securevaultdemo` + ✅ `kv-project123` +
+
+DeploymentCanceled + + There might be multiple reasons for this error you can follow below steps to troubleshoot. + 1. Check deployment history + - Go to Azure Portal → Resource Group → Deployments. + - Look at the detailed error message for the deployment that was canceled — this will show which resource failed and why. + 2. Identify the root cause + - A DeploymentCanceled usually means: + - A dependent resource failed to deploy. + - A validation error occurred earlier. + - A manual cancellation was triggered. + - Expand the failed deployment logs for inner error messages. +3. Validate your template (ARM/Bicep) + Run: + ``` + az deployment group validate --resource-group --template-file main.bicep + ``` +4. Check resource limits/quotas + - Ensure you have not exceeded quotas (vCPUs, IPs, storage accounts, etc.), which can silently cause cancellation. +5. Fix the failed dependency + - If a specific resource shows BadRequest, Conflict, or ValidationError, resolve that first. + - Re-run the deployment after fixing the root cause. +6. Retry deployment + Once corrected, redeploy with: + ``` + az deployment group create --resource-group --template-file main.bicep + ``` +Essentially: DeploymentCanceled itself is just a wrapper error — you need to check inner errors in the deployment logs to find the actual failure. +
+
+LocationNotAvailableForResourceType + +- You may encounter a LocationNotAvailableForResourceType error if you set the secondary location to 'Australia Central' in the main.bicep file. +- This happens because 'Australia Central' is not a supported region for that resource type. +- Always refer to the README file or Azure documentation to check the list of supported regions. +- Update the deployment with a valid supported region to resolve the issue. + +
+ +
+InvalidResourceLocation + +- You may encounter an InvalidResourceLocation error if you change the region for Cosmos DB or the Storage Account (secondary location) multiple times in the main.bicep file and redeploy. +- Azure resources like Cosmos DB and Storage Accounts do not support changing regions after deployment. +- If you need to change the region again, first delete the existing deployment. +- Then redeploy the resources with the updated region configuration. + +
+ +
+ +DeploymentActive + +- This issue occurs when a deployment is already in progress and another deployment is triggered in the same resource group, causing a DeploymentActive error. +- Cancel the ongoing deployment before starting a new one. +- Do not initiate a new deployment in the same resource group until the previous one is completed. +
+ +
+ResourceOperationFailure/ProvisioningDisabled + + - This error occurs when provisioning of a resource is restricted in the selected region. + It usually happens because the service is not available in that region or provisioning has been temporarily disabled. + + - Regions are restricted to guarantee compatibility with paired regions and replica locations for data redundancy and failover scenarios based on articles [Azure regions list](https://learn.microsoft.com/en-us/azure/reliability/regions-list) and [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions). + +- If you need to use the same region, you can request a quota or provisioning exception. + Refer [Quota Request](https://docs.microsoft.com/en-us/azure/sql-database/quota-increase-request) for more details. + +
+ +
+MaxNumberOfRegionalEnvironmentsInSubExceeded + +- This error occurs when you try to create more than the allowed number of **Azure Container App Environments (ACA Environments)** in the same region for a subscription. +- For example, in **Sweden Central**, only **1 Container App Environment** is allowed per subscription. + +The subscription 'xxxx-xxxx' cannot have more than 1 Container App Environments in Sweden Central. + +- To fix this, you can: + - Deploy the Container App Environment in a **different region**, OR + - Request a quota increase via Azure Support → [Quota Increase Request](https://go.microsoft.com/fwlink/?linkid=2208872) + +
+ +
+Unauthorized - Operation cannot be completed without additional quota + +- You can check your quota usage using `az vm list-usage`. + + ``` + az vm list-usage --location "" -o table + ``` +- To Request more quota refer [VM Quota Request](https://techcommunity.microsoft.com/blog/startupsatmicrosoftblog/how-to-increase-quota-for-specific-types-of-azure-virtual-machines/3792394). + +
+ +
ParentResourceNotfound + + +- You can refer to the [Parent Resource Not found](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/error-parent-resource?tabs=bicep) documentation if you encounter this error. + +
+ +
ResourceProviderError + +- This error occurs when the resource provider is not registered in your subscription. +- To register it, refer to [Register Resource Provider](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types) documentation. + +
+ +
Conflict - Cannot use the SKU Basic with File Change Audit for site. + +- This error happens because File Change Audit logs aren’t supported on Basic SKU App Service Plans. + +- Upgrading to Premium/Isolated SKU (supports File Change Audit), or + +- Disabling File Change Audit in Diagnostic Settings if you must stay on Basic. +- Always cross-check the [supported log types](https://aka.ms/supported-log-types) + before adding diagnostic logs to your Bicep templates. + +
+ +
+ +AccountPropertyCannotBeUpdated + +- The property **`isHnsEnabled`** (Hierarchical Namespace for Data Lake Gen2) is **read-only** and can only be set during **storage account creation**. +- Once a storage account is created, this property **cannot be updated**. +- Trying to update it via ARM template, Bicep, CLI, or Portal will fail. + +- **Resolution** +- Create a **new storage account** with `isHnsEnabled=true` if you require hierarchical namespace. +- Migration may be needed if you already have data. +- Refer to [Storage Account Update Restrictions](https://aka.ms/storageaccountupdate) for more details. + +
+ + +💡 Note: If you encounter any other issues, you can refer to the [Common Deployment Errors](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/common-deployment-errors) documentation. +If the problem persists, you can also raise an bug in our [BYOC-Client Advisor Github Issues](https://github.com/microsoft/Build-your-own-copilot-Solution-Accelerator/issues) for further support. diff --git a/docs/images/AzureHomePage.png b/docs/images/AzureHomePage.png new file mode 100644 index 0000000000000000000000000000000000000000..cb3ce189a720929e06f3f33c479b740a31cdf23d GIT binary patch literal 68688 zcmZU41yod9|2BxINGV832@)bDAYG!QNJw|XNOzY4(jg#S(nupQba#zN!@$rzbmzCZ z?|bii|LZ$z%^HStX3sf$|MGdB9V9O+f&K95LlhJgY$-`m1r(I~UMMJNLl5qO|KQbP z6oEHXdj*NND8++h>)^v(h=_~`3QAc7=A}M5_>5sAscsK0b+~<@&YDoVprG7jONojo zx#(=oID04`U3Kk^r%YmCND&87p^~7AJ`%He7MMqn^>RYve#g+uh1UG5(claozCy_Y z0`BZ!LSIeM$0SzYsop+&#*g}|C-o#1LFv3S8-4cy&6~q34}rnwHHe*)WcO{!>LpJh zLJ{~k*(g2Th3kxQ$qyadS@MBwBTQ^TJa7FlI^rruurl$V0 zLWXlch0l@t_3IHhtuPf=QdoPtXlrY0zxM4Ve>(qnN0LdmkHp6IxNgR3J1XrN!eJH+ zsaAA;kO^+Nd&q!yJu4c^{`SLPNB8`%Y2<_jmjxH8N`dm`*>!)777N^p`{?LsWPCiS z?)Fx)V$=o&4nF+ve*SY`$oLhF|32n_-%MBkcO(B9zQX>Y*XUr_KfKR=_sJ~=q};lZoN zXCw-{)8SwJ{2mbzNoZ-2;RN{Z@7wkE_1#`oRTX=F?smZPuW|l_Pa>U?W$E?x^+mg>Kzyd%rhPy-V0V%dST(Tj0|csbMt|B|9YY& z`u=qXu)q>;-`jgE_x(AG|I%?rG2XE?QlEG#TY%F5>a`W2O&Ocop*-2d)> zlOehUdBMUGo|Q$@+Sb-HIH=C$Hpf9wPt_+#$`rk=K5qgeN0Tu)mMkWRZHu{g8+C)6Xm5~u4yH8jJ*dZNv1_V5>P1<(hui$D-16V>I9(qlG z85vP}`}S>jZ*N9^elMK)-*YJmR;$7NBqt|Fe0~QOIBM2`bn-nB{9R(r=XsG6ot!MH zWx*tyi~kuc9C-Ym!5;lK{|{V7R#~;RwL@%6dQ4x+w%M}hPI%(Je;57m0V^RP;o)B{ z@ZV?15(AdLHVtK^rKL?gK7%w9P5Ar!vv0s7L&?~ZWmEX{%O~?nN-~R!aiG)q*cwHI zz+0T^(Bk?!W<4*|sw{Y=g|y$x$P~3A-YsBzB5~ko67RGiHg%{W^bfhPu-Z5}UkS8H zd%wtXv*t`6yThk3US;;F5Zn+USl9ROAChue{5~A~*E8Fx-OlQG31yuVG9xdq8_eqC zM$^QL!|jgtAl2yD#P?!i=$xFKVV$V63@N1f%J1I24GavO>!O6RtqR{PcNJ-J9vz^% zrt+(bk`XLYwy$wMC}`t|=D6o_;D`lV)t? z8#~tOwAxgNg098`7PL0fEccgQEjA>2&^eZ$*@t08Jn%;P&k7>@KZ0Fj=ZUW8l;6b`a^tf!oufbC-EM{R8x5s@!77x8PeH&_M>(GzKDmfL1!zkaa; zw|szskzQEX5BF5c&dFi-Ja^b`ys+}eB6+K>P6Aez9hcwW=g*&8h$aLap6`!C{$4@h z4;%Bs0eU$k&6AG%di!2}egU|m#iU%n%;kZejUcP1ZH9Zl)KGnW4y761-_TZmtr;1Amx8&miM_Eg zGcrn2t#z;14K1$Y?Z6m&VckoN%{iLa?IM!#*yf_dYA-QL4$+$=at2;{WoBlcbqQaK z1mKWYU+(t}%yfm#w*}a57D4}%8A=FU9V&8J&wkL?r!qD+cG!j|2zj2{%zq_{ii_*& zV{Ph}p!95a9M|q*$W3l#hQbYmvGBRmZ z{}xdCJHCKKWe8$578%dC!4&?aI+Te9cW(3X;v?0qvk$2P2~dPr5{M4>DxsC%zCAiW zMoN2l2!Ocid2w83*cIM(u6Z&V-iFs>@Xn z>12a(n5$$+M@Pp{owGHU6@22;IU=*etdiaP+QaK|PvZKd>4wei@Do_2-sL#m*)>m( zED#kZ%M1f*YDRDGjy}x;?=$~)wQr^Qcw#^WukPA(0YvV4C&Kxore?r2vem58fY_hD zl=B$}M|29mlf0@f#lHe*YovU3~cP>F-`02jEq)+rTB6+rps;TCd1zE43{8a%(vX9H0p_> zwU{XVf}Dm%M@PR?Q6bdz*h42FA!+aIWH;`8a)bLWjzxQlNmAy+hr4>KgDES;zI<1Q z!wKxh4|nPha5x}?OARNpZAd5k^Uh#6IsXerM$9RYYe`<| zp@5jTw7iUtbOuip9TVd?@{+%;t&N1sf_OE}TL=^)N-8P}%F1G9W-oJ7oStG}U_5#9 zL|$9la`q@*Qd-(v31h>q=5()%5_pyV?pzD7vI^5rF0Yy{XLJL%zM;J7Vwq7t>EK_X zVpajefU*fh1CZ+Nos-t4Dj63$!xk%sWetD-5D6t_^BI=q42<&XiflyTi{OPMY!d3)vX=Py6*@ceB*+YsXf5N zTn?YM+EY1qf9Z+c*8BtsJv3@IvrL7^u zYeZwR)0C;m1qJiWIaa#8neGje<>%GfSgzwBEhyuvt9k70lS-$N^&p&ulJmzZ%JP1_ zeQnRwl{+CdZp1Jg9UVQ6MpbEOY0tVY!7tB8Nwcs~Zdcn)ndT1V%2Dcg?1|~P%%h(l zuD5}>X+SZSoQOE}V%FbADdm0@0YV zwPituBO(7uOGQO|XSRU{ayof^aXHB$Fz>P)VT3Q?hRAJ;cm6c;L!X1NV`koE{2)5pIS|R5TB{&Kx~-)@G!Pv@Il1!g~Z=E z#48`?b!9$lwJi_X<@tEi3YhD?`zDYSN5VKQJB-`gWME)e(bgO&)o%+74K4; zCL|zW1JVB>CMF4oNvLow&FY&jo#eHq7{l*CgPRMqxTNy)Em{u#*8@X-yoqm8e_>r1!QNvx(7#?&3b-DWfq zPfS84z4pMzB8K7IO&2peE;m;vJ$-%8Sy>}+g)hG)S=IIk6w1`hSb;i~n0J8;1Vdl1 zDzMhTinico_yKM6)*R(?2@j`CD$DFIWZ_m&Z*9GL5o={-g4I-hyxigSk_Xpk`vR<$ zBgoay1^tEQ_Y_#@)y`DjVDK|#) z*Aj3Oj_N*bSXdc7hg&z|Oe}4hc=+H13+cu-AO7heg;J>iy@@`>0S_i{fQ9h7V0K1$ zp0Fj6Uh9Ea2Ay?dEQi2yN=tvkXS&Z;(_kQ0ZzEFOHW+|~CdHIXh>I)e=*U`H7S$A} zzYD-w2F|&BG_DJ2{*0>SG-+&PWOUc^c9qcXValP9vU zE1~J0G(hh;MQN5?nvjI&@#q(N z+#ia$qM))XyH_}4Ua9f3urLeca9xtEI(eFJUvg*6$`w>}#Fdp}yaiBOtQE#t4rO-m zD|TR!^X}(Sb&v2>-(@|I>;CqZI_0aV{}a4rrd zhH3ur7xQJ$lrIK4OQ(sd?Oudf=u1gG$#$1}Vp=_Ue!@1c`-XngeDQ;G%^I|)f{URX zr=WP~383`q_Qe&NFiHPp&T|5{2d&~N5{lq*=3KAaXG}UOWCudJ#1fQ=!8_M)j2U|iifotl$Dx|z65s_7;fdd zJk#RmC-%p%c{15;Ryp)~fbwhc? zE&IfW1QiFRKWmC_ZDqH^WbEl(2EJf0LjKev@lyCL(BKO1H9GJ1ei6!dAhv$Z)w7c7Sv)i?yKXb*;x?(-K zqaSlO;+1k=@0p@9uh^;z*M%y@y+S5 zI$G+5F<+mg9yjQsxgzSm2*2i)j<<22%k#IN9ATVuiJ3jATVKr>f`#ogP}tDNY_78J z<;hw9gFW51@1cz=g}^9OinUC)@9g~XN0E+Y!Z2&v>m+r(Ktg*42C&VnEmu?BSqMYE z-Uc^HN`wWcwU?LBG}Jm5ILF}*BJJ#8H3@Vu1*5%LQqiaTeXNreruYC6z#`}Segpai zk>PQ^6j?f%#P(mteYZte(-qQsvQ!@pM1Aewd*LJP!UNfg?>Cj?w zoVP!K24bSZG`glh8Q=o4f=5cl)e8@0g|8e2`8JsjzKH|KY_Th1bwp7v806Sq+DQrgCacd-v6P=%*jvbPF4cdU@ z>XYN)RHFqTyI)>c_pXm!4v6S3E&O-`>#si)&O6zR-qzHwO4Z?dwuWr*38s65aGtiU zC*Hq&f5A?CI_~(3NSMDqY1a)H#*@yT|Ivxe8hnsGXtU57K3%#{MJzf&-IISE4DB^Z zg#A#LP}*}}&yKr=1%xhl-?4LWki7o%Ti@EUrt!@WP$+U=;2KGTX#&uMft8SfY;L*O zuz>U0?=MA~Pzt*>OnF^h*jSNfP*@nJGkI)QB*}+oz5vm9rQdotcFAwp`wtTxTD@>e z5crp~XEy!!#8+RyX?D*IzH-^sgB@=>Wyq%V{(_WP5g)#}S5QBn z=MBe4wEk37uhi3+oBu2X-{p7F-_-+^f6Ld*`y4L2(-tTGk-qnFtsY2IR{II5qjr&9 za!qH-8k%JqQZ%=O@6GvrZfwD#5Qw(!6QEkd3~NRDhEUdoMvo^@O57{h76`k8SR@5! zdBcA8nc(j--K#U>>Qm8vI~5M~O&z|Y>n)vnkT)E^;XQc&;21}e6dG#WmxI?-k+4eJ zT)o~VnXd)I^ZtB}1(l0$_QSAO*Ima@_1qcmJ+g1u>1htZY+V;-*cB9vJBpDCeW-Qe zw&k#5@*&H+YzJa+2cM3Kir!Qy+T4gf42x$r`AEtC1kwtxF-q3ix>7&5g7+JrPxxPf zK1R>r#vt}|+i=irO*pbqhNbhjfsXWUPQuqbMmW zS4tgirGqB5&8!l*6(LHM#iY-G$#Z7r>K8cxV+MWs1B)31##xp`io;d24@`P8zA{QQonm;^p4 zskEo3r~EhbGBumJ8NYx4d;*fMeamJ_9i%STjW*mbU%q^CNUKM_2hec7cBfC}?`=am zT1&i+7_Sd=g*kf>vOM#5uR-{*2gBa0M7vxdYSNpkFa-@n8+_E{ZLJ?3rLNR{b0OP7 zYQ8B^Y8*aYZRSPY+b2K1P70y>I># zM2b)(E$=snT6z@HxJQ^V(`w|8=7j9)SOvV|*750IwP+#xjLvC923P22t?#RyaOc}d z*<^3};>Vw9lYGq8@u8=#Pn>){{8Tw{e?unJ#hE`V+~8OldqA`$XEiDRrDdxx{s-kM z4ws8M_O03?Y1B z(i9KKHtjjfy`7(a{6f&?k~)mR-S}sJHJQB;+hq_^f5vPq`dFFRMRsvs?-*95$N!&geydHha%ZBfr4rp;docjV{|J!$8! zBwSf09{m?gj$C{{UaU>RZX_A43D7#w`Grjl4-bQXJOGVY(Ft^p4%BFPl$5y5p#N!} zwr-kPs&ZIYKv`H^oNV%LT7MzYB}Jdo-Wf)E)u%i*K3;A*Ob41XYl_}t?YalJC;jn6 zr^K4WKDoIH+8+(9tjacm@n#b}p*3fpK~+Mk{JAQpe2^~$CVZ1TZB>W-=7XrHczq9t ztQ)|S3%Kti0_IM(>vmS!@#U7%ZZ7Eo{Vig ztzc{jOZD8H9NCno6>^%v!Y#5c%*$g}5cQRx~#>T38M@7NBkBp8cBt?*dhZO*@-tEis zx-aiz;JFt|#~X{*NF`jAU&y-7d39M^TEDKSyY)%BSo2p2JO$_E ze?e~E@sTJ|J${0+N286d^yg^kG}D5c8GVjxLw zW=}*r?#ozvyueu}#x;6%H1YzJM_)Q4&VPR=u)ww26%s}?n({3a@PaY5lLFI9_(Fs} z$_Ss>5W`$2>4b1S1{+Rh*0hq?kDt^A+;5_%2YAx+=f0t#A88`n^RQsZk5Z}c;h~{e zis+X3JL@SON_ess+&CT|TQ3SGRWPI)d=Q%}WxO3RHs6lEcBM?E?EdIAj&ME*`J%>$ zu5>Ttzr98G9u*&7?ML(?YfM~RdI5p@kM6*b0hhJ_;3WgluCBbRN`%9;YMqepwcss` zvPdU>P&YjxBuuQE(gJ8Az@-Xxn}qT_L6`-3&W7NQA38ca&ly{Azp{o}7s&6~SkBbOBw05muxQur zmUv%)fcXQs#*G;2%I67_N(0MGOsP9@ZwC7MFgN;OCw^)E`ge~Ah=?xA z`3{6Ef7)v?ONATMs)ei>Aa*c3EmNDJ<&MJI78mPA%v7BDSmVWb8|KYss8Ib!R%INo zXzspL*{LMbnUjy&Cdt4_yc?U5L$YAMRj#kP!(B0NdXTH92`Jye2yEUxw0uZ>y&V( z_4R4N;c2?C{d+dE>)A=1;p@5dA8Wj%7Hyoj9Q^;&PNu%IH}GhytqjO0{QNGEw53i6 znXg<>?dQK94)Ibop>TQ=;1z0W6w0BxQl-^Gp-Aj*An;Mtb}9wCr5u8(lf>N}v?uye zT*N+2RpUIF@=H$6eeV^go!33m_D^#d)5=rd+g&QYfPNZ)m!*7a2+~fBdqTQhqt{BR zbOHTQ`Ri|9Y02f6>2QuXK}Dk_u%)f3%7Tv1Gx_x&jh7AQnuPx+AshdT6ha$@YUS0Dk`?MW5ij2Z$s6qm9v(6_t<4 z&=A!dC&6V}=7J5h7py(2qfOd!wzDku<06yDd@to#_(e!dFwVTC@z{CM#Md!2)?^)`JUY{si6lp_@7O39xKwLEg@k1XXhacw^ z|IBHu8l>z@MAHuG@R{c#w%JDktjy+QZf)X5qM2E~d0ugGr$O28CU0*LvgH~I^n2Hu z4N=t!Rm{xHHJ_|O+h-fxYfcwJYCz<4+3O(kJx}%6m%7E9JHyGPrKH})csM$8-a_&I zq*ikPE5e@2Wp-Sb7g3$|;+uTGZZs{IQTbiA3J=ecmS*a`RB7z*NuBo=S6;_EhfF&g zeSLMj&(|Msv>SYPeXDE!uF^Ul`e$76Oj9I2J2|;F_R53u6uL__QDaZvbaS->{Dgmv z^xEYc@KO>;vOcgiz^32up%neBTp2}_Ln6GNB0)bAUpEcyHMo>Ma~pqjToRY~xIWhO z8(lt^r-1UemSJ(qr`F5c5$LZ8GAYUmOzhi!eoS@rM$6^53L+#kobr*DG~~DDehN+T zdG7jIm{4Av1R?aeI^3p>PypL;PUyZ(>9vae=l5FH*)GOqC*+gNvAZVuow&W442Uti zV6^dRF6%Gmc>jbcxi2*aC6xd$BxKQ&G%}+3t2fHGt?{3+f&iQmi}=;&fh2B4C5Kaj z;(#`j@>|3il$)&}Gd;D`tg=W4=o8Rv9284*T>FseoNYQX=U)~vF)qY3>2zJ+xzX*@ zY+rX!(A>nxnrhu&?n_`Zya%{~fG3xC;DG77O*C1#x%NY1MC9BptZ!sxakyt3@Bu=+ z6xVc_T{B&hmlw!sKK8oCa-y_F;4)D8yQ)G((}eI$Ico(rmK9(@kWRxT-ooOk{81vg zSQ*YcvxnQmNs9Nz!(g1R@4dPw2DPtkW#a}ta(1w2b*nyFr?ikLq=D&i0b1C?>M zk@ZPY%+{o{jgrLMciMg~<44%pQS#Pbt*fCM-2Dn7S(U*L$90^>DLaC7(*WzZe}TA3 z1u-er^H`nFY3mb(9sG2+4R?Jw7lv9g%Nq~G9f0m_1?7CdwnQP2Ba9Mu>NI-r_QkPs zb3^e_&7;m;$JDcVph1o*2;=j1+NIOn0n5Ul`7+kHndbq~2Vt&)bN5Wqd{jNqJOAR)+yWR0z>GTzm>*-5} zy?Z%8Z~`E13~}b)R5@tC%4muW$=HV2i2X$XM>hC~9-M~`J*qtqHzvM^6OfXeENJP) zCgm<4r6_GDi9ybg{oJ@+vv*CB7b8{yuO4v{$ke5*)^=?`$|e17m%q@EEVk6GP~UNa zoRbHcnvbxNM%Vw2FN>Z&W`~!2v{g7GQd4IvoLhu+Y>RW|0 zy4+7VJHk|XZqECKL5H~3@!_uUL-m%?2F3GlqO=|SdmlChxP3!whWFaL?&nPjG}ktH z{fzsz_oi*2AWeG(Ln?08k6t6W#m^av@3^59D@giCREIOkc=6ARi*0l(zO}^0)cmO* z-zMvDEcK>GVYAJq_pvlfOpbpk7iw@N74VK2l34_=s-6lI^dAan4QwG&oC1fLVROix z-atM_v)=~~05bOFL?8j+Uf?Tp{IHd@;ZR74klszY@ap}=nu#BF(;Fr%4<)I&;3uv^xuxg5Jpn2-}+xz8BQ|18h} z@Z-jV$-Ew5O8xx(?T#lQ9DqTsb=(vOu+_oJz%3`Y1b{~fNQ7W^yF3E9hCz1m0-Te# zSY|@s@&Z7pe*o47;J=+F&X2}_CgkVPwbSM3mGk!x1Te9)N9E_gv@a)n17IXj2w$EW z+Hc~w0u;VqQBH6DQ43 zo3XhHWqgwY>i!k&#c_p}s#=RsLjM|O5_fq}>BHSNi-3IR7PO@pE-=Jd>Y-0Fnkdx| zIKu6lC^Nie(#uT-_F0K%5&@SK6dEe9w4QphHF?W(XjWL+ii+MfH8ovYBC|4H(BBP^ zAu+o9q3d${u%w$R=wsN}T9MIJa!FvyNa?-xyezWVyE0;=n}VsyIW2Ys!hD>Q5_+p6RjWwov3*AMVcuoasev+rosp&EK5u_}2kcRsFfK>O2q zc5d}~6|#?{O6_Ij4%geQn?^g^_i0T)ApaIob4ET$c%MalD%+MF{U3K_HbgyfsIFZFmH##Kp%; zf<{;Pa@V(pu^aTXZww5mfm#6=BsJB#sQs`=hx59X6(5akZZxRi!I}(c7Y!0sQCeI< zI0{CmKGh-&nK?NLJ_GFUUZpdjrF94D^jhwKMy`(}2wY&0^ldX2b(DM%&w2BfZcZjCRvZf1U(J>yAaP6%orccQPVCLJVauuvO`i0%6WAQTkFYdRaSj(=^6cY^R1MtB^A)zm!(IMQwY z+3~({CuG%u=Y)u!Y>r0&iW|_bkZrSelX5^W$pI`I1~fvrPsRDka6owIZ1lXS22gim zL+g6{Q~s2i=-Tq4nn%!%PBzbN(OT=IK2iHp$KMx;x6u8ZwqiG=M=dq#!LuhqdFPLHi{RZdh%3d0`JhSxI^Y_Q%e}5J zpNg^9KqUbHZA$OUvYLX(TftdkVj@=p@<30P&yb8r$36|f5ki+R-@==7H2VV)N1&Wq zT3X^#w;$`bf*t_%+AlETkIMy6hL#8?nVJbAfG%woK_+AFEj@QPqa)15<7g*G5hO)9_fwH3K5YV5FIF}sRa(0w9VU; zI&P|rc{bsMjQX@`l2cThltg^_GP;1Vvbnhlgk;fjLJ0sD+1S{$snK%Snuv^By$pyT zV&!FdceeOtX2Ga8;p2xj0{@R{zLmuk`J3t0gu&Ry+xGf9NpC;IxxkePL){qt`*4m_ zu;S3+TsG>j;3}Wnn@(`sW22(jXW%YbAdpJOv19|y5Zqx{2Jl7WK%=DD z`DPb`ae@AC+OKRNdC(@3XVy`GLvYE8l8 zwpX{27^nDV?7Cq5{)9F6s;Ak+C6oZ;_y zVCQhFdIK%^t+p7@htZlYE<6D13Y&_KijtI)O3%vj12>U`>-m9zkWfKIrPFmaMM6Xb z_2OiQ8`qQo$gTVP`+W&zPyy_}j?PJ(u( ze7kWlM6No{>LzQ-mRsaCG@fQ>XG>%+j3b>6KsTgcslmX+WCSR=WRLwGpq4r-DD4U( zl>n+3;7!=K$rPOP0Avs#Np=Fu<8jFODyG~wiJDH)0pSfIj2%qy#0H!PLgOyalbNnK zT`yA#FQro-_{=0P7V+=2i3v3nCnUUMW3<4QFImk}#Rd&q`sRFm8~bv@sjh6&0?-yf zW(gFjKLDK%a`rF7E*u@#BTlVi|6l{uf{tbb^d$5RY;FXy*vJ7-lpj74H#Wyo)TXT? zXB!l9<*2eCC)>PHp{It;#MQjfYQRxCfmK>lHZfCe$?~=hc?p3LRw>2mSE_}3c3A(Q z5Ok|pALWzC2b>Wc+;FnXLeQ;}t-t>RQ1VHFwYa)C$@RXuKHL}$0TlD~8O-}u0F;0` zv;k;%NjW)sM#d1Jy|zD*Lx>dEM9?Y!Bft;rE*l0KM_JzG?}W@JM0q(m{@K!T3P4e| z&Bt;(UTNv8#+z$*`*B!rp6=BbMe*yWpX+Cm)b|FPCL5`EK5VhgS@_@Vn=I71<#g*b z9C|Y)+(i6fyN?f~zhE6Et8JeHQQOLP<^qt9Ko;7A>UUc){l0!>bKN(haG65|aoUi= ztzDyh;Ut`Yd#)9el?K!+x8{5}ELUzd!*@`cIjGt)U9cK-37#wC8EcwxAD9}AGL0e; zks{VL?7~YTpfE+eUQX%(N+qn%%D)M zNvhW(s`@MwxqbMBDz(6 zsA}1w-xAn2l)r&q?l!x0QF`|i>3Y~IF&Y6k05UiJwOTY`sekMBmmoB+1rkM`v*gp{oGIZvR($aVCUo;QeMdx%d5&wYpNG*~kRkLuN~17_V+FYy_^5+%WlN)@XER-2Ljd{N6RQI!>;5jYrSHOoFaxMG(BbW#btgs#$ z8amvXjGn1;R{Z!;e>*nqK}g42DN_blS67G7P00Y9Az~|TmbijRGwrrlmx}x##eyvb zFBlk>3#=QB5sh$hk+RpYJ4&_bg`EREpBs9^`;6%wm9Wy^wU+TRO=Mb3Jhz;9Uc^If z$wOV3%6wO4he3Y|Qum_3J^|OW8*rh?(qKyhP~ZTs{Y!oGuSugG03xhRKu&-E{{55x zP#UW4QSpq`YC8qsj%HKxx+PKIEZi*4f>u!arxhB^6BbSuTriDhV`Bn~nW`{KsAXyx z4hKP*`OveY?GDx8qb(w|817A)_OcccJr>;op4_PBZ|!_4B(6 zaJ%ev;=hG&;5`dK&Qe)fS>MvqN(4lfAfheS{I{{k&W;_74(bhgB_$(@e^0jXJ5ZSd zVd7&73Shuh>~|y6NYI;*-++jXjV%)V1}IN^V6a?(;b_Dx{*5_-`ME3vJ8Eusqex0V z12O9I(qn&rKd+#G1D7B&jRy>&2Kp;7c6&z$5IKV_FS%fY4d}dpxakVh=3fJK%lXb& zb%AX!goNS%y#m`_fTsc-o#@Apk8gveHrUqkwq-{SJlS>pqOx?$gZbpJisk0YIAY198b3&2&A|G#UB#S{fe zJE?I0-hK6-AtF`u%GCO0!pIbMNr_LN`p-9!pEmrf+gfTG-T6HE|MPf{dF=*+8yuzuA}0O} zOfZW_&s&$bl;{&zYq7kfj+z)cc-d>{&am`;IjYKr%G`iw{{$0wmD+9w*O4EH7;56YOW@~ zJN+q47dkDI%@JN0ovT1T%Udc9e*T|hDX`sdYVFZf9@obu`dmyghxE_k;uu5X=u{(U z^1dkw(s#ec5+CEibQb#SD55Af(^_h+1(lVR^>DmA%`XVM4o%$Cq(=N{?{5FKi|)^M z^7wNfsa&ULbnPbFcwpaMT0+!QBKyW^7`-NAzc%ynA#0t@zz-bJuWJ!wMcE(*dXH~G zdimW4zz({7e0wuHVuUPc@dzW^UYU?*0NQ4yb>eD#5|P_DgVHS^9?A9Es@5r3=_Ye%}V0 zD0#7foQ&pR71m`|S!KEFe)I!JYl1Nf(HQC5hpb5o5O)uEzgZAKu~5OhsSyamE$?-4 zw3PaN{g;DDyl=S;=<(!zb0;xKM0YhvQ;}6FR08Zb~vFgk9Eh>&;go2h}`+ z!HyUCwrb3W!MO8n1I7Y&Ui3OT%Kc4^Ozb;TE6H4#9D{O8+vSUATy?<&{4dpF7BSN0 zWFFoQn@jg~Z+sbDU{{KwlEqupUWp{)h=1usNxGBbCD_?mw3>(BwDaMEzLwjQ6v`fI zXg$Se!IR@oOJW+~Dw5B#kxs`~HQYN!v9>+l5nJz|e|gU2{N1J`r>(^h$LR@Oov&X+ z6g=*;5GTrNIrxEXgdjY<>w0C?%eu1U5Q||+tt(`-wtiu(uTtYW&SebMIJL8Tdd2_Mrt9Lu#mczM z^#KYuZrEZFTgsW2H78_nR<;3Zv;Ar6<_#sEKy?oso>k>?1NOn13Q91ahS_MW;~B4Z zwQ6{4#=YQowDR?N7}rz;sgD-SK2jQ~y|V0@Iy}JH*^|3zSM2eqcby;f(n>>-@2iii zvKb{D{v58Y<_RJ5EiCn(7BWa<^-I<6NhX%o&}TDDg}3GAXonM-zKcGat0(^R{B!U_ zD_58Ma!L2ezeVR-ZT5U;d83b!NdXhGqAa+bOD$<%#6aDF+MzNXphE6N>dT@W6U6TS zao4q7%!UlJb=qfN>@kN-ay^1Qc_m6zOgB?3)9}(J z6GR+0+ZBd*6*!g7WtU)(+oKyQ=yzl~zjD3!B;@9W)w5D&`J)o;UgTm+q#s1$*UtQ@ z*p6f>@o<8D>a<8IG_NmLF)IaT^}(>W&RxBJdF@^ScQRpL&gE0PzRjNMjk~g`m7@Z8 zKOZ?KcV~r?y+sn9)A;SSLS^ok79rDzg5VqisfyQ?G5z&VQCN^f1Hb^#cGE^L&VG}M zpX@wob%q|!vG!qxPiwX0GS7O0en3M`Z`YjDxhf}E!&!a}vlLn=^x_bGLP6eL>uaO1 zrL2F-ShWJJrO{pA{vCfw()5+8MOiGl6w-HYej#D%wDmQ82VNHkqk;^nLrJudgf8xM zLy5t)r3;}@20@uC;PLzA^LBNv$i8x*7!-pCUR4zxtnpe8J5vWA2^4V{18VKsTV)U;8=7(Hi|yYv9y6^ZH#}#}-M5>nih=N3i5{ z--Om%V^6B|synUZHKM#Hc;rzs&_FWT?WLvAVM7wz_hfAu87?bd6;!+xB(z6c7@N(G zJq8fDv#{ToJ3kO>{A=9XUfa4FW)x#~%F|~{4a)Utj>KJ4weg!?7d-Y(N&KRBpN&}Q z4^_^rMbD|99J#zc>JPs}WWdG2V_3NgdY`|D4P+8n)Ff*tW>~(>@Pu%ptXVOMDXcVGbAb+<0{kjU<03rlOZ&n zVV)^@B#g~714S)OvyGBy+3uzW155TH2YHcY*be2uhNipxnLMAl^=1pLr{F3LH4;Fbw@ZwI=_*TwE8uue7GSv&86#4t|+5;i> zzet3gapllVgJk(8aFKu1bk`b8Bsk}mLiO2JsBTw`BQm-vZFYul-eIC&JIfI%DdxB< z7It5$G|D}#NB?xmJf35qe1=4~wJZrsCQGmvGPG}3950ZF3R3cQ96j;EO-&*P^69nE zo#Q9*Ctn?bwk?Bh>WT}wuzAgJx&H^VhMM)D*-%V0{P+Hrj6*p5ZEiQ^I@#It6l&;i zUOUW8zVXt9$?MCL6&gCrmj>-Q3Vf`rh6l-4fMI+rCOnpayR3TLA)V|!vstea)h`3p zZYgvxnX_(spHrLHD^Rh#Aim$9(R9#{p8B$qa1xqgeO%GX8+a+IJWeVA{T0tjpUXBz zdWXW=KlxGT0yDviNmGYrFK@Q_bimIqbRhCkrV+imj+vnmN?uMAKieU4q`r`Am^fLT zyu57BY>Qh?`Z^64p| z!cA=gC8jyaF#jLK&7rE;NHvz1a2654OX1;=;Sx%gL|xYgn#JK~jR2U}()_^G1u$z$4c{C%zSA+=FwYE0{A9crl)izL%* z;i@H)a~Z`>d}^rBkP;z8I|-#*7oke&@27)eWrSSCQ*RJh^s3?Oy=}ST-&U^3N}IBx zRE7KJV|N6-d1Ux&FiWnf{q@;Wg-cabm3DXA_+9Qk;|*7(r?FG_fu2{-c61iLEq0?# zvP=S80j2M4QQiWB4gyL zS2qs&2V@x4dKxhGHp1S0a$C@XeQ(x9d(=aKI?P_;zS$&DHp|ac#_fGV@%*M7T3z=n zh;jdyi(m#~J1m1HTA8i9a1|fm77OYN6MCaqFncrC)~ca@*gWr8RWS^wke{ zq9?oCuM)g&y!qxUD*}hx;*4Coc~~pN8t*w8JR~X)xwOAS5q)zMt}pWY^|VZb5Jte| z`F+8Yflu343%sjTSTL>ihMUjhn8W6=6G{9RE4yyxih}o3ts&d>WT_A0H+a|1xCK+i zuNv(wwi}*&0wI?y9>?#zJdPUahel94i>xp|l3+iwGOuPj|ri0gn(t;`>jGyW@vlxjd1yeF`p}&^DS}Ij%=CQZ_bmV6%wc9@;U*GGa%iqvS z5D=K>)!F3#QbNfWlGiz2^rhfC!HsLU1ffy1Hs(idce~-4akih{#HGRR;x}=YaUb-y zwsB7{@?XB?J6eL_%ISN&EWJRZ(}Kot52r0zLlSe{iVde*<7yyIbfg8(Xmar5W{%yr zNoOB7x`cZTbC3>F9qr}>SRon2#KP~dtlP;6Q-$J|AUlBz*X%56;Z*b&xFV{YUYt0; zFw__hCLLBQ&kepHT2)j)CMX}mp5yb>NWbvDd_{!jE5W+fehz<1p}kSF#6>sK89Een zxcQ_tk4PXvRPzi}P-g@zUL?EL{iD8o@;h8ER~L9FjWsp=!b>XlZFdki$$B7>JaAMa z-qV^+O@l!dYY)1_YCLKysV0OoHF>;m#OMc;8w5WU%4yb*C2pu^9g$W3Ol5X?$%1y% z!2T)^H-2=|lK9%TD|&sEmSW7Qe!8(X*mHvSWW-tJJQ?a0+#vF-b%P&K`k<%-i8C8b^cD>|m3?tKC@=X=S^!mJZ%qxp8V zZsx^N&Si^2bTh#&Z_WarujbOH`B=?*hAJ2bW3gp)b5DE^LygrWev}61G8i~gJtF8; z_Vq-|SFf(}b2YD@#(|B{jhj0y)Z#iH5%Om#OM+vKL+#eFYj-V>%vB+j2f0mAVcM;j(V#f z>il`5fhUGq=T9*;9;$NabQ2h{9h86On?K@xb^UbZu3sbLxy~B7u;2Xkm*x6KvK+VR zfED91J;gSCAEY5VgsqET%=Q{$jjp7m)4kg-)(?pNOU!(OV{}LLelNO^Cx}Iisa1L4 zpLHD3r*$O?wIvXx^3_UH+ux_Y=*}%&HFPfotoBcJ++$;GIxEcM?H75mc1U~1NlA64 zFT;3$a}PWzP}t?meTL{MvP25CN5D zrAQM|QE37~=-832^j-y|hTcO^1XMawM4CvG8hVI80wTRcdI=DaUPBKdBsmZM*IMtp z);VMBJ@z;s&NzGs86)MHna?cuyytcO=Ae@dk2#(mZkNBCk^D0SeEK-u*|=w_`#NF} ze@x$)l@_Rw7AQ(<0(a*%BcYvGEy`kF`5iT9etN81o^j=d*cj(Ifn0%pNi99X^#Q5M zkMOw5Wd5n2@OV~aG>;W#nWHwEd_g9Cs<&!UmxRZ>uT45{BY0~>|4DjdoOB@H>};>@ zaWGL5mZa=C2TnT+c~hgM)HYA>j`CmD5~5(uCNmOxvABRQwA83|DUsu-fo_~70UC_X z?GT=XTF`OQunfF=3%7UQ?%B98<;B)b!3$&}sLa#0rCDIKn)TreI)SIS5$=nRg`mgB ztP#lTN^IEmvOhxwweCi!+hkAw1cZtDGkJIhcdK`hui9fnzxGsa#CEBIvuK&EvOD*v z?YvueIna5EOny`$zJ~XU)sm?_@VOTpEH#%k^^*fk7gbm`vtsbE;h~cdbotVw@AY2_vX0_`CW&zDi#6SUEIjrY zvHtc<(?fcJ7cYC2rn{F`Vtoejg=X@na?K{+c=B6XjF|sWs!uCk#4ScGF&q}L%Ho%s zT1?I%Y=dP2r$Xv8wU+EXyFWf6Z)-}W4pX@vkU#h8;Vw(b=>hX)SUjEcq+XMcqyH-d zCd;M7PeD3Px?8cnl?caRDFflco2A9vt4@CVjS52dxz{YET*rFNu%o9mGi7hSW!`i& z4+@q80YWsSflo#G`A2e3@3l6Zu!guKt@xM&GUGZh-6$4Em@mSAWlEI8cSPmk27Pi< z7|6UOqr|xZiOa8P&;+*;i=NI6vS%7@W)#(eK!nil({y;3&vc9-WC&*w8$)f?+iCUv zh|osFZwlmwK@?u^@L(~DTrP-0JTI0oIK+2bH;dQ(S)cLTTIW=Dw3d-mADmr;CU=gK zB2Je$r8R{uc`=`R#v-&u-Ze^6X+pGwR7J$Oeck;So9$$NHn35F{!`rfv(cxJG+fx? zwfAqxZsO86_R>9b9#kSmm#ARS!6lz-^uZ`EZuevgN&H%46=f`QRA{F0veLUkes!EA z?=E2=ElmWsPEyQz+Km_6+XsUlJ0|A2vK2ko+0|ax1lf zKIbj3J-D>i?N8#seYQ8CQ-G~0c3)pe54n4I$gXX?rFijz!-v(6OpI=umLZ<>0x~}L znh>8pamjIHd=8nii{G8s2x?&{Ax|HxMSCX6V6|RdmVFlANIad_} zV$+Y8Cxa%X>Mm`&cSdCeC>GRL5@sU9y#wnht5>z4qsU1gTZ@jV4iLR*8Yl(NX4?`c zcD(WJ)t@<8_aj_zC-{1?AN1)h>6Z7g?o{WwFKtlFpD;Vy1T!Qj5jcuLNly)i5bm_q z!*z^M$$KHKr}zV({JvSV0c8V1>!BZCM9O4Q_BU9fHD68qk=J*IF|HPuNHQC4OihUI zIs#Ma*5wpnrF1lU^FZ$)SOy_SF){cEOi0F!69!KvG}Gu%8>#*q(~qZOpjR3`n=$mc zwOOpc>yKyjxEfUBE~=sR=l2YY8FxseC}>_%skTJQQgz|pz&H8fYWQh&iv~9XZsMD< z_bt)~4kduK1+pW*u`@-@uka6$#eu0Q1LZk}2aRuzsovih-W#j`MSQfoXpUaQsxj@14=$q&kqkHFkCiM)G~vHkW?x!(yHlQ* zZRuU1*YM74O<9xk3|H!5z2IO{G2cI05#iO&Gr%3VVuMxVZ8z6c-7=NZ*OKQ8x6vgOEa)r1( z(uTHfk-BazpD=Z^-5yf;MU$1NOC z*OuR&qH0#EYz)H)B4VL;W<{3S#QP@YGp_X30hs7FA+HJKfnz?dH;|4!$>Od*)}7n3 zZ$?JloO$%?I3n@@V~`=<}_-xGI2P^SQ~5c2DJoNP6+(7iDY`ep7dl3a@d8HBBP z8qTgZd%1qD9fcE$RRcW-fw)D&KL2hOZ%)kH(_oJY*$;+xjVoC3wBwpk&R*!0oi-A0L z@CmCXE68L#TI@A>nu=hVd3R~qDHKiYxGaNX7Xgt9v@nsxfKAqskJ z7%5zn4kz7<=RVXo1nyU`R=mHzQW@OeAR!iZ7Ph&#pw|po%m;x%4M>-tEn05epK82p zfEXh|8^;2{;G#7qS18QNC?Ua-jdQ2SU#_YXn^@`yrult3`dhrU|g$2DurIgVp=P76MfZ zWtk5j;(&C}>E34S&91)W`hcBPSS)NU;Waw zP#L{*dP4Ab-ak%bCQM$8bA=&hBC77csc(xE)1~Knmn^p8eFpIT>TS;7=>yN$r%$>; zAzF-el{GfkAvPh#2D^Ig5gk>vV(VCwiSlsVmy3%VMf5_9t1G0(dgwBF$=mOKA30`{ zahbRBqiZiNQtzT6*Bt%x=UQ)jYJyTn)fZlS!qC*_lm`*w8=jHVqSW#* zBfH0A#^nhBFOiPm$E)0Owcki+%YZd7KZSf09iqDSHmP9~mhZB;&*(MdkVR8~;c@c; z8`i!Lj9rFgqQ5OHP!9$KUP&w$6blz=F1>yo;4lQ-RtR^!zW*!bdCZSck*7<5>`q&V z^5x+)lfn5%BDKtVnZmULlwq+yR0eJB(ep^G&o|2zD2LpFx4FrYWR?)=3yZmbOd#w0 zDVn+`KeJ1Dj7G$;HeVqECFnd{GqV= zCnq+LdSZZ=(r68`nt1y@+V{OYGiq^M_BlA?5&K~KF0|`GM!pr#l#s$MNnunmyxr24*JvW+vr3ri69TJoDGj5^;00 zHKX}sKesx*&Fv$G?Jzl_JLD0QD0xNMl}Z{EUDQAOP8mRG(^|pZaNMjPm+! z*aHJ`-pHE@DE974fnRNulhrqS5Etl(m4b)&u}@z+vzzU}-P+RfE6mw6q~mErAD<~L zvzl#lJcx+!c!@hN3$UC0QVq_gHVwFw7sfp;r#=Dg?*B=gVy`aqH zINeyfHFl=vhkWd;Cl)b-Vv3W>r%$yHyJ&3DZNS-WWM-G6w)A3P?c<7^fTg+f$+o98 zPD5P`L_1yqe-G`mXd4oI=JBW?zd&1Fo*)$9a{)$F;(6xLI|aC2u0UC1Gc$g_nBve4yDH5SZXV18+V8YJh*H<4*%SrxY@a_Y zNk2TN?}3xJX(}dc89ic$Ryaidh^Z@|^~-OclVQP<-w?eQ`?_a8MBmA5@1G|Hj<`a8 zvOPKA{R8RgO*d$^zC~!64`1@1GNCMV?=|gibx_tzL+-@Oc@|KRX);K>A*`KVe!6M< z19G>8Xh@Sq%s0O3sOCuHY-l^DjkvrmeU!4LY(@%nzrMo)3p1ac{Cl|g`Op+ z04A2h^nm-p3J&Cd(r2`ju@MCCb3w6Or*?N|hWF4Z?~irOoco5Bp%5wi+zc;i_Olv$!n~o9sCg}Klec(@%e#EVKO;J&UlH|ZEmH)xueud z%RvEb4mm|bK>(Xm4SYwq>B-0Mryju$&zm2KSjsd@?X`RULHNEp^*D3-nSO9f!u%U9nWAs(fI=sEf_`bb9RBGa)p_?(ws7|%gz?0pCrBtu2;}2p=SuD z9+2B@7L|2-`4(PO^CL4X%T{c0@K)QYnO9vf%^75HwwKQ-AX^+}E3XIz#lE7sQ9h46 z-50&pXQFF{HgLkb56j)m{5XAUQ}jD3btYv`q=bjJ{p*>pCFv$SQyc)*>Mib1NIQ=0 zpA@#T;U_bV0FT?z26kJt_3`q-HWziY*WVdiL0!+n^q+{|yteFe_mEb=fGqw2%E(wy zg`XK7t_dP6@H`WC%`40W_VrIQ^Brc$I#I(ZjyDR;jLWABt7%TUmarL^k@*^?AZx(J zp)iP5fhZHjQuq4L8A(ht%f6}U_Mbr;x6!fekvA{xTinLW zW9zS|Ia=&ymUeI+lMl3BnND?1`uA}6UQ?NWEt9Zy@cckQyPg7LV?#DvN^AWocS+|t z(d4n8e@Xf$)@B&p6N!b4zdOmnT3U=W+TDq9f7YY@MXi+zt{9ET)IGZB zqW3IXtX1~wf1>j5COAACE`MJg*e-wQU4I8+!*eB1?_HSdP4Py7gO1ybDOVTutWp`r&>JG_lDh-; z_Q=u*u-BWfOFr3P@5BA-#$MSSGI+XZhDjf^vuY!Jn1uMqpJc9fGp+=snMF^XEAV3a zpOS*IS{4Yk)Rh1ArN+8X>j~jCQ@uaD-L{5%r&f-Zvqj%{)&Yp+YMy@>$~#rrGX_9J z-xK4%6=?bbAWCmwnnbcEJ)Uvh5o7LyItZ{b2qE~AO2ca8NqQHY@KTw5hp(?DU+l|m z=6zr})#fQG3|c4?o(E@>+-X-S@4LJ<&EO z|CMFDKX(47-X&;J?Mf#lQF8g$F7bcY1U)By`&+`lLX}+FF2~8mTMkeEL+uj#kARDy zB3S62`j0}TL{gFS*ta*>^HcTp|J1wu$3=5{39iuM<2NTicmnlT-6Oa2!X41?5b&c< zi_~3gZ6#gFB<~A4)8kGj8lVgDk1bDTet}uH-4UiGoCj=0Ef+(T|69FrWq*>5*db^*{8iGR z$-BG_`y(cXr6c*FSXu_wI6!IJaI#_bZ+~1DQo3zjWt{;Fa*Hb*80sX4tN!g}KyMWI ze^%W8Go<(bO`G?>uDyBb_>7ERfcmBi$QluAA^-EjT=XL_3p4=qYzC|+fBypp>)yv- z^8UwNm}f68DR~8mHXphGHlG|oQU1MD=HF$_C-*F-0^r_70+!FOC#b+_z>xK|cJZHP z{m*?md6Wr99{ynfRFXZtRDe<+FbZ0HPUC#}Kkl(d6aX2W&_Ezm0Pn#PfM=5c5&su0NXW1} z1kj1j#Q$w@|MlSU#8kxK_r?j+6wniYt<8U;tn9g3SU^_;h|?MCr(%X3_5SA%ss0>_ z<-fpw>{w*~uSx)(0qV>Dm)idSyr=(reE5^b6rWZ3M;$q?>*)F~6=eL}3Dp>I@Q4AH z2(A;tEn+EiZZ1~o1ZF5-{a4FAf4JMrsHuf}jNMb5BtMKs7%-0;^B`x$%No7T4C;R- z{;LPi`^5t;@=1i}2`lT$l`B+$8WjNl(Jl66sOjq``=>lRDSn5$eQO#_dyy2p&53(y zxtj9$iWc6S@4R^2o~~!szq%Y33!sWV!TBI$IsmW_i!q`2q@l)$CPf-;QODhnd!y0> z;s|O$%Rgt<5H~>XD5N@BEOlM}@86+cYHaK3IMCd7(h2}DOYcWtqxHA*Vt5G`b@B3K zd`i*czl}k`Uwqcbtp4r&&zT~X3>j}o*m+qDD97I9FqESxRMJEUH%20-B||vDz7^Nq zdh$d;jX2&EVw(POe-%zaP0fgiR!N^mvCLWQZRmAPjBftvlF&R@2f3ZG0Yg%2TFFys z`VGnc(1y2z^Z{t6ne$~qA`-8kkKyl1mahPr zeT^8xM`Yc_ueG}cK(#0<{IHqXr{nia>edz7KJ$^6$a`Y=3PX;633{JL0>ChZ&!N$O z$Kv)_2uBa`^z*CT1kxMwlEkgEV|#ZQq-dko#1pquw9bq@`^2-kqKa}7Z=ngVebW0a zqHa4CcE^)otZKB~9$B}Ojqlu)oba@;7l_;Kv_N=FGL9Uzr`C<^Y}E<+9ZuD}W8~^S z>=wA^<944DzDSHn1nO4Ipl60OiHt%9othKn)%;o3dQ4i4yc&kkMYcy_dlxyeKie5! z9S?UtxPucm=Db%P-WthD({JkE`ufqSfUAj>f5

z|3Lny6U+N2k=*rQQCdj}7vO+qqOJc|cci8!Bt`;5@C4irHUZG?ygm8_DBJ#T+u!kr zKv94^Q3fdB0AhJkZ!9N&#{8{F2kRI-k@R-#^r3#gReuuzsFI42@o5^Jz+jN~6L!7>Ni`mG4`wmGlBf1-OzEF z2Kr8I-cLO)iR>{PA``v8?=Al8Lw!4$7f!*O?^tuic2LF2lKANoY*A55_Qda6qMtq3 z|0VdI&V|%ppojut-R}X2faH>P+?$8(ST+?ADT?)(?q|(2(BJ)=8Zu_BYZ<9BOwBG)t&`f&1A?_~oe>Pv>7E)38gYrxXD%Kx^l+ROt zOGW-Q$Ue{}!4UkLUlYm7mVU2>U-W$>d*`_HW@h|9 z*T7Op*LT_1Lj$=`(KyTj(z;IqYN5nX>BrwOvk;Y5$!I(O-E489kZO&JA}b-0Q&CZ+ zCx|qX&1_8=oCsVzvbE47tS|4|i4~6=L{j;m`Gm-zZ$5QT*FXA|q|VrAx{l=pGWSNi zH3>tsa(9Q=GmoQGNmOS1@{kWCqw(=izPtP($}VYjiGm27{M@;RbcS~xzF2vt(^1ze!qevu#@FxQ zzCJ^^GO&0q!-;XVos`2e_#eAWFP7-kPK^WRX_K1Q0b&RKl&7J0U?6`ow^%e$1Yyc6 z!bklKhd~B%P5s%7q_gxU*bpHVhYa69Z|?`*(b;QoY@f%W%>j9Oj;EuKj!=)Yt$0+O zBAnVGAEfFK`&4ck4LnA|oo`L^uvky^sNoy^MSAV7<`KX88A2T`M?jt*{?T}Z-1X&+ z_p+APY?DRxZ*u%L3|^kKkX`C)_FLMOJweD#x-y&&u5uEEWA@WYT@7};ID^8#;u2sn zpjzCX{;g9xHWpw0OeTfw4KuvK;T7TlGl7x*1H$Kg!}h&LuV)y%glt!xmABL{pG=%x zAkF}im~nf3P)kn6AQ*y#!5)KI|0k7}WUAEsKaLE`2EQEgaGQ|UyQwD%>*leq-e0NM zdoS;sAZKC0rtmy!?YJ>5jj(w;JxtO%X2;BCU!*^-^Fw^&1MfVd(wx@h2&*#=iMIA+?U$H>&el`yD@5vsA_FD(V`OhNKj!)|(ozVkm1TOH{H*l(~A<#A4F7g5MHc~H5ORX;b zE?vCAbyAXO6>L7=^dP;(elBpf(-d@HYMwox*kSukv;N@4f)6muF$2GC*kDmYD4I2P zoR?U8k{V0%aC;)o;g^KP#%FJ5#_GPDhj2Y6_It6~1%6L0rnvTIte^#}okR%;5P;n3o^O+*Ao2KRZa=>X@fn$d?96woIVTf9mQW<oz{2@9P#$Ad+;1e1Yqfl{RM}93K{TjY~Q)PIgi;X`gS^#IFWIzS+p^& z8pgD8U#H87LTwU-@a|u9Bn!Su-F>W%^RI~{ah2=*dhO~=W;}jT?rlAcEF-B)+@82d zjfI~W?dgW`z~$0dj|xXhX+0P`Nw$NC5>527V~A1+MfKPGyqJS%4MNfOL$gib&+% zi_buCDCjcoH-iY_jWy+<-&!tKdO`GF;v5(AJ>G79 zWE0aKl<)WVU4k?@e$Yj^+7AJ%QP0(I0~@XXC83-7hqzlmY0nMODbTrS#Jp3>K|)ry z#FBU*7Ppuvnh2|{2SMV_b`Jhwj470b{pCpZ#--djY#^W%J9BKXHNj~UzpFaZBv-~$ zNY94+84}$d?`!*6v9wnEA0?ER178ewCZEY?7)|fl*rin_S&Hh!&2v4!*vBRCdS%@S zQ5LmraIrN2SZ)A7g`_Xi&{kP}+}C;sWnNF^gwL2o19 z5M`F$#0Ty6F|#eVWxC*E<@V4pwJEuOH0t8$ zCRLE4j=!U-=GSH0R0N-6L%3Uu8*<9c>wx3i2jwasTc%?DYk%9IP~vDDXkS*%0h>e~ z!Gn`(_kwzHXHoNfD}&oZA^bkQ{f0(NHEejNwM)sYd=%Ux4@WYIW;8MD{&2dhl&Rs= zR)t*1IBhr`s!>mqtY^#2EIn+}=BaI#q_#);qNhKDKG=dB{lakC0s>8<`7kg{Z*Zb# zJ=YU)wH^89h8H{E@Rg$4Km)h-cFm;P#i(Na^)v_@nuSU1xSuKDi; zsmRRLoIH@=+3#w0+5?5*tDpJ^4LoH$sp4f@={W5Un5my!;@a!Tg&R;Mx0RE2S zHZpA5SzX*usTdVlvjoRik@Gn#EofrUof71d-kY`3zZ*zf;OTwlZha8S*S+f}B43(B zGV#Zq%1vRhTM&+cVvr<@9@cYha;)AUi!=7iq(FXf%Sf)4^b=M{KO|N@wo|a{S4_vzhevOQQ;ttTP8#yegw9% zHS4vu?^$kls(Lni=f8i73B#BGS;w~3U4XpzT#~srE+jea-pKMiuY@|?$=z-w+OJ63 z&%}-;ZiQ?cy4^lg}B-4qRHrr`$>w7ExwQ3q_!oXAtv;C_-M#wDb zlA@w$U*PjtN_$zG{WEQ2`I?*sw9OAz-8K2>-)7?{P4&EgH8VciuVz#-VImUo)N13a^!7RT@o(&?z3Hwijkl_BEf*POJ?ZQ zIX#-?d}M1YU*Likf)@ej=JHlm$`SJ8o*mE=E`h|mH2ReV*&b{~GmGad z_hDFgJOZ`Z=P8)OYH^*?A(Qy0ik9lNKL^JjXF@L@(ndJ-$-g&QDh~~+@#+hYByp(z zNgH#tne~eGV;Y@EL2w=Qt(%Y{sEqf$<}4}6F1-KPLI&fDX=w{I6T12H}hPA z^MezJw-(>Rp&CQ8a_X6Qh_J|Em6%f3Hf{_=UZ1&3q{f%;oqav=_LZ||ir=QV*b1$` zG3pP@85(5%ikuxzJpOft1VQ|GtTsR;vDM_dAFqcBG# zg~>@kZ+4oKCeIRo>A;@(q2UDryZ32{Px4RMX?{=gvn?!+l#}9Yk&zyB=y7Kg_>dY;~+U(Hg=Ue*ky3)QCeGS{Ib8@e;#C>hbdjT_sG ze9F9q%1bt{v-2|x%AQMN2skY*24R0gww=Qs)_Z$>S!27L;Mcq-w_?+%cVMW~doSy% z6O|A4Aobou?MY6lxtvQZ%CP@Qubp@*?n$%(Xw_Su7p}t1x-uocgR=_YUdLgR{T?1<=I?;d>DT_SfbO6qhe zF4|~Lx@+*ic_(Fw6R#jXYDj`a`9tZ5>b+R=+0gZ|*MqV~)|lVLi#~YYlnV}QZ>LoF z$6K!7kB5$#7AeEOqZBrjJBL|}`)kQaZ-{=2Tw&03)SN__f9hu|>GE$vQYPl33roJG zG(Ue78gArm_tSHaAoPPTX$qP$UVx7v7TWUDyq}Lqz}gJfVSZ?Y(W*B!`)dmXoVJew zyh}3eBU4z$b;0$YY@W!f-UeQ?GniNZ0IQk#gXMwT@LG0B1ljd2rBx@Ths0Tms_*w) zMq0^l(Jh_Tyc3IK0ccivrjvYyk9S%Hhcrwt$*G8cHaCzSZJo?CFV zkJbf)Uzpx7(m=Ol&j$(_c26}tPm=Ansl>Bb>ni6*S2SkG2K zpHoMUPbfQyv!S?w;*_!unVm9x=D4sOgdThgYQn;s*&S%;f+ub~?fa3Ol>-QGf>eLnH$;B+tT|}%I{!wIz z6YN&Gi8E=b6~zuk0;>Gno=MC?iADlBjj|i{q_fy@j%5XHnStu0hTJ69(3YG-Lo$)N zk)+BkLgCk67WSI?sW-OAaz}Y&Ri0f6e8p9AwmjVXiY?lE0ntPD(DW92W=xLxfOh6` zN8UqTJLk1dJ6cS$2RWaXB_Ae_+~T2d$`#Eue&p)9zGx-faAB=tw}kpv(|)Lp=&8TdocUnk2Ne~fI_~+TO5ZeSf0Dp6 ztP;$}XZG}^tD|L2 z=#wgrONRfvn{INxF6HLSHP>@N@y<3u>Sy2()S+G z=6!jJKjZOzlI4p$7m_6`!)C{B5D$m>Mc*jyRu36R-q&dCxP6MrqZNqfbWCZp9< ztQ^ng;ZadPXC9wh5a``s{!peP2$d0XORg)|C3;*aKpSBZ>EqR;*Ex~L*B;nUFjWT# zi_;Vj7BYSPheN)~im+_gH?^2Wm_8lbDhnMA;XWoQVX1f39&f+LSEpflvT7^Z5J8Gq z9M`7xSh0x%v9v30xuiY^d%AqiDH1aHf_$!d_lY{~&>E85&}{-=E2%AFf6B8v8w5wj z-v`YR-G8)$h-s@oZy_^FMgR?| zGacT!3q0!84hv+JCiNk6@iw^#m~S1|P{IX_%~u@`Q}2IavxiOGR&T}+>eW6P&G~1l zMczz}Dbk)WzMl7jcX_^n<#kAzWQfeE*~p%17YUaT{NX%w_0_)hK3(CoUaZwV4ZAyK zNcDxIWjr^&uhV|iWQp61>e56Zd>R)1u~WmX$H8P1mIzG6`>|-+K)!kY3((#I-CKUa_vH*qI+eke|G+Y*w~Z#Jvd8 zR(S0TISU*U^PP2`E|wo)4xi^2JxkyIbh7u^WixkqUtgS5X;G@J&5^GOSAa#fmOav> zaHq;pAb)g2U!x}yIpdq3G6-c_Yzwc4ejrGc}l~4G#D2K*}F9KyrkxTFMG<&W+=7gQjbBXD)(5W4xaF#OG+FI>a!w4!8NR<>7vn=B2M54(F@_>7* zH7bniJIwX`t@9O;hp4QIkJKlnLNHP7kB$jZ2c&o8*7o1Y{q}PEVqAmw1cM_^*lMGK z-JPw2ZkWlIW2q?j*H(6QgO*;50OXX>X*+dNCNFluKFy;EgI8ulfX@aVpQ_Q?m;{9C zOH?MK=|sCS^X%TSyk?&7oLeYlI078Y=Tm>q$67LQK8sKwBTLS+CuAz`s$7g4AN4?= zSR7RgMq9qSb7&e&b{+2UpSy7v5Wb;&BoUerB#bOAJ?+T$DH4R^iZC!yp3QOzNE$K~ zykc%eMEwDdE!7{A*b_kFDj8e|l(?xak`Q~tRII3!YX&Z96oFh-@ddpnJVs-61J%W{4FJju7&D01H_q~WR*3!{YW9p>y zv_VS)1)F|uln8LHNyQLfD}-43d)nY4<1$}Re<%Po>jxiZ1D7k1_);ZEU`hR(LL1&p zc14&MlzuzU`|d9Gu{UoTbj<}fQ=@iygFl9T8E!4R_?|>Tu?$fF!B+Z`sAd>lnu#<< z=FH6D=nheYmn|4g%ong>=`tzaJKOLkAhl z&Yz)3ExkiULA7!pzX|1AyQ)=l0x2}|?DTW`*?nC8gb?M!_<3%uXPpIJQ z30D`ceLF*UJMnk~y+PO{hd?J7^p97NlN`0N&5buAB`~g?Z`n_3O4s^{wX-+47j+Ie zndR#1?V0b&lIfF7;@qy_9y7|LAJc6HGJI^CKkrP@rHk2IZNVYE6tQ7(8biZ+zIxdF zqwb=aS~oF%Lw{hi z&~*Bb4#oa*vi}I}(VsGcyYu-EW;3E~Hq#2%&K4vcpT&0{86sFhisWuzA9R{w*-^?j z_Tel)7OwCA=gD0f0u?|$d)klVlF?PzU{0A#G+jvL!-}>satbEfm~I{|Kr0wzzB@$U zjXM0^1_yl}VJK||PkC>Yf!arzZfVe?AEcEADnX#h2~9pfm3#Q?S!OJP%ZUX)Ja^}WC|!SzD7%aiL0Y-~#lmx{vP|Tbj|nVOh^LffRz}WZW7Ne* ze>)O05!z?F1)iQ(xDu4HP_EAhOjN_06s^eJPQL%n&XgIJPuS~mcbySQwfx|zPhDD# zw*KLQU5y~V!U>|M97VX`&rD~_OSA8CpkLR& z3N{;a`M{r{a61>s{^v=bDtb$@Jf6O31Z(MRx|ZfQKHQcL%g+pA2XWRjr?rGfk&9q= z)@^*zetx=i*LLN`>3HX472EbZrx*-871)98ZLbYYjVu#5c~TU%_P(u+YjxVDq=a$y9sGe&|F zay+LuZLdAk8xwfXooH{gB5ZSOC3ukuiMl| zxQ-_Gzje57aDD)Vl{vMDlzVk z$qg%O>(KK!=)o z2M#?m7jw+onhDLlQJ+d4w-NE*kIKeo|sXdBu?0eNQqjGBAC+se&fc0#Lypu}$HB4ks^YAIS#s zi_ahrFq45)+N`to*79sVzflv!lPLA95$-JbsBt;yd!Xk&Ie*&I39H}qLW4$6hG*GZ zix+r`zN5mHTihZUDo3e2!Mr_GjEbVLAJ&a96uV%EHQHSj9?uri!0WNjF(#VeNap|I zu-T?0%umrnBzDxJ%!XnGp7~Cs*D4rqu`r;J!DzNW-+ZR0C(&64z$erzoFQU?b7hL= zX8PhZRig(S5cXz;%?&FTiTHjb7S^UQ)n(~e9iK?V9R@lPtL*IcD$wV?g8Y0_Ck+#_2+JC#Og73s z$br&656v-q)m#X=p!qyDhkgGHM}TE7y(p3a9|GRGsq#gVIYIMNfoI*=;uR}id^L1#KgWj%vvqHFbQx;uH3+BFd2F1>Nuopk~X37>yeVz{= zDK?N`9~mxT>sh4(k0tY_6Y1)2@d}K_kTnIZhD3N*LD}i?C(E^(rHyc^s0+@ zW&F=x2B&udS}IoZz=Ps3VH&^&i7SUrTyX6F$KHEKHMwo=qqF&GIUysOQ%)?9Nw^O?`C{|S)zN5gy0xNqxee$OA&ICdL^X+}LtKLAh^=K)!O zDPnkxm=Xky01yZ8)(QR-j1vZ)WOWw^FA&+51)>80{Md&1xEAC)sACLMU5Bc9Ru>2qH!+)y&g>YK61A=cV3^W4K9!oVpn^uj12>%;chYc>z1FpOs z6CFK(bNH75i)YWX(UsrZCLmM$Qz{)FN5~($h5j!28auY)_}k@eb&;=%h~*;9q3!9e zJSpzIoWY0wDHFr){b}!gq;Qk{R9*mo`kn`rZc7P_}AH>l3NDfsq+`CFB z)(QHS+xgTgOe7PQd8d2Bo0dR`HB&l_5IB+O^&#%iVZa*V`|+fnr`?GGH!R^(d^Y}e z&K8z=YTqiAt8R<y5T90D=Xk0jdBwuq5Kc$)G_mwf~ zy{6*u;gw$37`^qua>UI9cJg3ON!a$00oP~7@|lr#qgL(!uZ-wZCp33gDEg%3;g52t zFJ7|J3uV#sDip+z3%_=Qc8$+3{+BobvhsbZd`uw414&G&iNg8K{=;b$vcC z^7Xp2H6`wcoW+6NRv3IgfMRXpR{wB2N=ac2z65OO|18hMj?UvA&?fqRJ*@|}R!~O; zp`SgnRFGwytA$wvpE9-5T`1QXaBLA05y55!pm7l@vO~G{n*WB#dsw?(ZlgOK5$zVz z=ck)#4VAi<#~6T$Ff4}9Oa}=l9nOkY!A(^Jfk%|e7I~}XaA;|7PX80YemukZjXTET z?`ih8)=jJw1AF9!r(hL3*ucHasJ+{y!UxQ<0{JoaX|+11S>LE*Zv!EHQ|6vSP;1pQ z-hayn@VWpw`)?4Yf37gAx6EOc9{?tZ_)lri5r1<~9E;w!i=ksuur~U7(C2>xw0Z0P zCuXDnm)F&Q0XmPHy>9;>syF%f{cfo!=Mo18!A$>w=xrco1EK^<04S@3nPKjq&43u$ zCr$qD05J9O%a;}ZN@vuUn=Q}FlADll3BZLv0tks}X}wPfGt;T7t8*#`B>|uRs~9Ay z{oss``USwXU2lG{cd9gz18wBXoBr4Q^yS))0pQQ~K->!d4^B6=GJqau0%B3t!QVHL zW&H^4MF)!#f#m71#7kYcaANBP?FCb@S1A|o-8rw^gn#I(dSTu3JDHbPy7>EQ6R?`+ z+JG@>IE%$%(-U7jb8yI)?=80W*7f<^em}GO-W>*}Yr;1_wV&`2xVUcY$b4H^aQ<%i zJFfSST-dqWu@bfLu8=R(@DE1@Su7X7nHqvH$4IUtV9V}NUko{iE13ahH2zpCN;7-W ze6is~lIibp$S_1W+@n-zyyr7Dyk}R_&+0+GQ`P|380g}@HN$qBjaKhH*||kq~|_Z-2UTKiC?2fMQtqg5#>}oIdQr9!@~iEVQLw$ zBq2O6Dx7r{=BH+0ygpT?n@^mCvmbhceIhIM0uZ-^+s*4sB70Jt|6_Gcy142Z9nFJHat2=1!k-z~$Nq{yUf*R9yo4Vjc; zIC=5#wxa`2Z*OmSFC!3W23!-OfS7PHKw`57#Pu2I>)VO?cLQ47bM`VL&UiZ0;k6SR zvBvvrA4s&kEHtB_i}4v)!e8CQ^QFyeWnR%wSn;f@=0{1JKX}nS+wp#PAyE(BE86J0 zbRR(0pQvA{>IWh_Z4dUgaCXisftBwnE*v!|M?M?{0$UaWy}B6a8+krhus*}9KX{Q+ zPO$`JMm}t3@F*$PYbEVqHPEbHXVT!o_K+4m&#!l}#HqnDOGO2%(bU-AG&cYu zr|KmKE?<7AsJWXxt>UDTIp7#>&qhv{#Q8V+Ri?ND^gxznO<5A2fmUIAm$$v&q}BFA z2+FF#xNERISm|5)Hn<&}Va$DHXIf!i@b*+PqJ-b0sHk6M({ZPUfzuE!}96{7u zxev{qPopNzf8Hy3K5nedKA0-XT3_^#Hb$}>@Reh4Ssqk3tNx=kVp9^oC z8m+IMDB;v>;vX9L=Ik*pOfy+_tD+P`Y$S^Y<34|%;f@<(!(+2=;+vKgp_5Xyp!(Pt z$`gX?^rO^2>J)5+4H}4a^c7=UA9ws!#Y0$Fp=42?5GWR`n62KvlK=0 z`|+4dvMOv<-YR1C0nanjER1y2JzVfupO*)va+BYUy7UdW`#M|PigQ4hrVxGblp?~XM z1bm1H=NV1Rj)U9H@W41@R@HY=rj%8Q&1PGrrHC~3LA}I9l;4HDliYi7c(rdkNLPA(X?jBL}XifIFB*TOByNIXDL7Dje zY$!z?ffX%1>f^NNWIhqAn1E`q$OQ%c-xCz%FqA-4YHP1JD1h}GI9^0zCSy=XEi+FV zLtsX+d3XlzKX@3)zwu^McK`PTS5$dJn_PL0Jylkm5}&;p!pAkQX++vLR96^kO$K}x7=EhpdThH1T@WbbyfY#n)I+m^|Vs1)-2=_JWyhU!Yyk1s-PKKm2Bir9C4fSv& zjO$f`YXs6j9emv$wOG%YzOkisw~YfrAv?W8gnh6Ztgisg4he{9(k}>6Il=|I>3lC# zXd^rxx@fXC2VSZ(gEHsq&B3~~UL#tjU5$j7J@cg%I0C!(AgpYsxJ17EP{5FoH-aE9mMqzTQaRnOZAi*1_PVy15rj`pE3xFo}RWK z5aRrVgXUHCRO)-!Kc8Ld^a*Q;zo0qGJCxJ(e!bWy{C2{T%==mnXq8{TMv6{j{Owl< zALrIpef+F*547K~~RwHqFA&M2#8R6EG-;?|KfFWvBXl|NCCS(p7$vxrR^HEce8 zBeBH|Tb4sN0PtD%B1DhcP6ND7cz_)4H$W^V1q5;dK}O6XW}QGtB;1sD&eI>PWp@93 zB9*~maLAjq2F&#>m!{HLnJlk7+lYqvqHzb*fz^Y3!|jbbb5tEZ$2H!eKGzje^3S`$ zFCE5(yYmYw`Zm|@uYBz_o`@N{;8XrHa0EIH70Mr|;e4aJ&Us5<}F?&l`|p#f{FQA)EBLiv?Ajz0A{8Rach3o=q@#3p zMRiSELnf@Y?>)`{Q7Pc(Y-Wf`Db$^=igKO?o`$?AZeNe4{>mBqbopPcZB{D%FS$`@ z$>FMjW=u+6N)0WMu;`Y)mZ#kEg}fxvHs`Lehc$WZOs$G5uPO*K$252W+Qj5rWxLxMg!;-V`f$S+C~-Lv{vATC;La>G@9PsNB^qg#6})Ez<1 zN#(-kq_!9tOCexnm8V&vQ7;aHlYNvUu$H8Y?l3b*@bA3_d*i?fjef!Yi8_0cVkNhZ z?=X~EHvsP<`U_i_j7#}wd>ps8vrcsc0!?LDw(8+EWA}pD;s}%~ z5x#DF7t3;D;1mK2hcUnc@!F3++6OZbd?5i$t21Rh^L4CknS|l)X&1*V7}&q*;~U*og*9G11kTa$_XL9f^A05&+uqu#y-n3NA-g zuGcellkG7oOq2<4MAW#Cn=!hjbG|CVb&6>lsxZ ziR;|b{_j8-m>K%cbW>j&bf-1PDr_}SiA3X(Lm`WxiBCh&Nd)!ipBf))mDYrbVr=@971!1DpL=rZGTIFA;-YSc+!n@lwKjO$dX40fME)V!vI zkjO3M%IcWmuQQ67ewc|zrx_N|M_yswMWJo?=JED5|df(zqVOIePkbJNA`{eY8Qk)H|M&NRjJ^xlZ~qw@2y@1oT6B?e?H3}veI zyC>=bgv_CcSA+8pWf^6mqv8wGeO@&jF+TO#Gp8hhHO#4_Db{ec>i-k&%6kU8|=3NbO;E*bnhcVqe8x|u}j zd7G!~+Nc*?bv>vcB`c=GCwl$^cE6shagm^rT5R2aL{LvSKh=vKCycRmw%IARs#p># zp8IC_g#ibm#?PDGaCn5(d`LYds9qB!cQkf=077--dR@{ST%s6+X;?x|RVt)4VywU+B7n$^PMMxuwzr z+rgsXC0?3sDTtI9EY2#CYa9b8LMr<-?J1sZRz~a3a929K*04>(&7yia*aL11;%RDK zmEf%bGad^-AH%A2NGJa+G**J|Ot?y#7css4AvMwHC&|>-n9TOZ_O~y}KZ$DEx>^Lo zD2nE|x8jsX%;R9a1}(LWH}Dg2=Ocb@nNXUEP%(;--gqJ`IJuTPh&q7R0b!NV{FX^! z*((;YIR^^2SDX6qs}Gw)T8qd;eoXw;gTn7POai*R!MA&+BC_(T1qQg_d)q3={@_G~ zeU4Ep;G%|}hsISfcsf?NS0AYIpwhh)y@m`?uF+)v(Qo>%Zb!6iUdbw%&#MHZ18MvS z4`CeL;*PZh2g;^%8{sRzL z5OrI*5MF)pv0IHi!?lkStf-t=uQ702=!^Rg9-Q`U-IBR@5pf%z6)@8U{1|$qOVxT} zRD2Sm07A}Vc1VEf3W!IokVmNljFSUeqqj4O>8H|XpP!C&T(IB%WiNx0vK@RL+_Ko4 zPv5a7tfdDmD*q6-oGY4YpH}S}1iIMdyj<^Yd^x}rPO^&u9~3B~0_0AKZAa<^w^pSO zq@y^0q*ZQ9kiZvE{AY$*YorhJD-S&9AC@he)Qg9rWn>&#*|$-E!J)1ioa*;AQ>*aE z!g+N9(Y_RMGy-MsHG$p@cP1XmASx{Y1xUOZx=|-0`mW?8ZAVpQUUwr+9u#Y1`1CWffPjYn0AJNMajQo&bX+&A$7n>*+NL;jQ0H zTW^V(6K?dcHrZhYTF1++eRAaT{MHK#M3F03>U`4vFN3<^OfiZTl5$%Qa!*SP_Wh4+ zL}hu1+1PwQvY%C25%LoXjYMfw}g;U>@#r>VswFfzHqLf?_jPnZ++%FFd2O5@y{w zu6OtDEBRh^@2agf223cmRK9;8+1ScO_qzsaVqxP>L%J<*0JI2&uv+w&G)cJ3FQl2W=N;FV zE&m}q-QFGMk}U8Un^!Flb8cP%gu3(c>NozlOh5}~eG@~h_dJ=6?|UJg;1W@EXpicw zo-DQ9v8a~)gyeM(KVY4}4VjV%A@Y;(M@zkhFgL#iLeZMQLZvq+5xr;w7BBtz*{Ex) zqH0PpC}F}1m#x-RH^0snBlTp~S^gO@G@B4a@M;rkwg$Cjb23c;=Tnk4wRQZY)A+c}Y3j^>dt4fZKg0y` z$@AsCXI|fiEl+bigo~v{q<%6<(A067-HR!)R*K23L-RHi!^j~oL{C(TWW}vGu0jhN zdIxSH4=Ij_b2Jz)HXC*PD@9(e{IGFmaf>TI-()NM zIUJlC%he$>lqob{`Sm6q1m{DUv`_tF;;Ole}vSn8`pbJkNMsKM4$A&uuM)o|?=~>2}KDJA>&iq<3cG z*28xu0@@5iVyyWahwNP=CZUe0#Lg$q%9RIB6-<~Hr9Q1I=|VXwbl)9SOTT|b8E zYcS|lW2}W;g?3I4iZ1qh*7Qhfln4+wVo@Uiwt39Y+FDeOKtaDiLT zW-Jtc5^7YyEl#d&_vZ&kf1fz*=Z#pcdae@HCuE+x?c3VD?ToN+3dOcc0+L*^Bea5F zXZ0x>xP=m__l{OS%66sW?MeuVIoQ#-=o3tlycoHikkG6;CFj3lLTaH{0puibQ_*8;C4j(XpjUMuEW*5K@y%c9+;n8Tc@E?bJ^xS{zl%WK z?5!Xjr%_CuBHpV0dQ4KeyP9L*j3yJDpp~v(H}39b?GI>|K~sIv*So0vwd}n=m)1o) z+{I@O-xm6%(Fdc`;DO`((8RgP%?fHLJ3f9XJ;jcd)Ge@^Th!jl!{o_}w%X$Cpwv{y zvRJ1ViGgQ4hcCGL11l>C^an;EB(de*P5+VmMg{ruDzMg^>>!Om1E)&B_6q4e_?9xMQ{op=f=m!FSMW8LSZ|8N zn|Bd=&kkG0`<44b4qH5$mblFkp!L95%fPnOXU!!6STKbR#2q1;p7bN)8brWz8e=om zF-G9q9W8~o2f9P&Rp35XlFR!DA>~(SoZS3(V{f#ca&X(OD9;X5<@dm&ulbLd&?p2+ z7lw~#%d3oac&qfsG_(e_uonehzF<$xk9&)84VFMM0Kf9PV(^U+7^Xk0Ek2RbuFo*ybQ*-E#~lq&osh2y|;@9V(9V5QZP8j{b*5!_}ps`u?g%Jja59*s`*L369`O)0*Ze6m`_m8EH6C`w$FSdKd3-%F zuv%>8GA{c^SWQ07@y+$UZ3Zd;Y5FzR&q!Jo-ECjgw__V{PS!&IFvJayJ^0<6jHvf_ z$q(G;Ry7!{)IBv5Z0UgH>|`wjJ?~Mb0FI}HW+Se)Z{O{+Xj;*KInp$b9-qIy+gsEi z(X;14=9qWWbLo=Yh>5(ZIScuu(!gP0ogEaST=4s{JN27vPpfaxx4XpGozibeNEj(G%C9q z>;cq#gpr_Wl^UcDKfLPC>F>qpaDHekv}CruD8&E&kE;!wM|hO zh}nLNarVNzI<7qN9#VvNxzd}D;?LSsG5hJT?r60U~SNF*kHR~)Jx&;$M++M>c zAQglckOQLH8~aF@8GoayKUFM1z}cwSNQ8#5WTzyFzsd@r2M7TPk6W9(uUjW-*~qgf zG0A(+lJN&BPi(GNS#`xlWrtDTkd3$D0^GrFyJEo9H89!Y^^PYk1cz+<*1W#eDs&7) zVNyMhGYNjEbWmS9?6u^?r!0k;`i*wFEoD?{HaUi+%@G}YH}A^%-Tj%q@nd3#R=OIN!b83yt#Abat#GV3? zRQuDW`!95U@O-+ZfrX*WjTh9w#DBU}3?M%vld?Fw{e0hY*IUKG!Wv0-ypba?h4g;zIbTKCe34>qejz7k+$C znJIl0lg_MPPdwc(P!-0JdBW=}@a3zRC&*&z4-kKrR|KKJ+3b8VB9&XTk@DIl?Kb&W zz~KYHzmHJF_j(F?3i=$Q1LDb=42;!$_?GEi`c)aUf+ylPTc}EYq?lTpWpfCd(*TJG z#W(?!{?Y8xAK%fjY*1I*GNa^<8P39vsF=4A?2QVbCxhayC)ySu-FD%+m9>EX!8;rE z37qljM7>fsbg%RnSfKMKu)uKpAEH8g1dxt}mM#|gx?ZFG5NBMmYA4~n@Gv1Uad)}6 z{4BuyW>BQdZ4IPJavCc!y-MR9;s??e(M>Tqy78sRM_rWOk=Zp%#Zk6-g@le19XR3fe@Jgu_ki-?f4oz7La)WxGB1uhBwW zcHmIw2dBhC{H{6PAgt$!M`P=$r?n?P_8hpV81S{K?S-0Ga`0D&PTid@ytpBB9+!j% zvbvZE<8e>N0oO6RtcRPf==khq#}$Owt=0N>^Ign{!NRB>HXuMQsP@!{5|+FDAFi># z%eE}C0lK%J`44cz@}F>u9kDY$o!7Fwh$Q=X`N?YwA8B`A?wKg;2He#A0XYv4zCC&N z%t5#8B0!M#_Wk=zAUWILrISv%>N*V<>mD?IIuInhrAFU-eJiph#mt*LR06?B)YHAw80A;>#;jWF%Z~1e){{_B+AV;J$ z7kIA%e*&oSf%IC^8-RMLf9-E#PXHr9fBWC_vHg$yYX1QS^uI+Y{wH+b{}P4t|5p!e z_J5cE2oL@L*x*~A_Ni)}zE2g4%Sh}h$^XRQTD_?3!ar;qJsrw!9M>Aas)0a}8t;djbSUz5oH1f!stj;_e=uZbNRVtwt@MK6) z!9EFpV;CoP)WtPJUf$01K(6A_)%-xE0Vj}f3@m=0%9_aC?s~ACP#YraSL9fYX@*$* z{<6DRz#J0fa>4IKCi&IewK?9$fjFY}6cPnHl=L|nA9WYU-FU#+>8!_R2da|rQeDNb z!}S3e+25tD03c}m;&nFbjT)k+%^~uRq94O{6n7 zRVi<4crVel)f-eM5s*%HmHlk&t)!|t{@VE?IWpLXiS7Jik2;^L^Th#C_Xp!^*UrY- z!3)AmrcIxV^09m!Iz>oR#e;Eh|1gmmJ5iCpWN9wvm|n5SpJZ>Pp`!VbFeFg*p;qza z{RY1W!NOute4c3T3Ac%-5AchP5Wya2>=fAT7wdhNaQ(3Y(tMXjOfTyoWO3t#j}4f_ zKA54$arKvo^f*Mw`(BhQ*UwfAQ+3(rE9bq_w}aVQ%-&CWOP@x)E7@#UP@#JBGc;m) z*Dg{Tn-!LpX#W}!`M9LMmq$hVqn0L33$v2F|R2dnB#gb$;{ z>UdtXX>k$a8Sl5Oo?0h^)mSkP;?63R1xC`V5@~tEr$7ZY>rO%63Dx&JH|hJ*(-zGFPXS zd3J}&HwB;QYMj3tD5b)V$sVwuk|ZBVf1cC>Ii!u_?;&Udh6*4yu;a$}XYpjW#psBT zY|+RgTN64T4MU;1w({w|yeKl9&0~XE74BXhf}HI383TvCUL<^3G)p(*WbKj~KeM(+ zY28Hh+LE%m8=pnG+9Ka;n@`8dhN0W{SjwdFRw1oyt3jNK0duREG&)0mLc``^`c*8o z`(it}BCZTn7mSddZ?Q0N-3gYb6!g*YeKR&6v*LI#R6DR!V|-(I!u$J+vohpXW5Wvu zh-m0t#=BINv26KNnc5U{xv*R5{FWb|2?ubIk=|)_X|55HArFo1;B3yW{rYDj|2j5B z?c@iyq_O&&Dv698GSUrFAA`oNa#grex*P28Bh^H6T)Iy`4Kq@~MUG&^QdO7#mZ7y~{M5N~;#g~Lc5X^YPfZu@!MgC<;-hsQ4=N6c zrSUx;s1KeIYdK*SY9HjQ^*2l#q&}V0+RptF!jkpueSH>x$ocJiy!I>#gyBqge)8M$ zErIKiW4M~A$yZPar65OkO`pjpoSZKG;qYuXc>i@icI@sz05W5PdHcQaXU(uZCsEsE z`RC+;ODPPj2B+>#e2lOmzmvXzYh2|BOnl#R`g?w3GE0xddB#^XVU3As5yti5WqQHO z$Xzg)#&&R^XJ!eN_3W>KS#TSDB90fY#<@EurH;>M_Vr995mHDYj@3V!AIZAzQ0+~l zEuX)*>F&iOb|Bxqn@^z~<6j^uyOt;$C@E>R;MGXp!9SD<1Ha*A&8}hPfi!+mKfhbm1hRr)G1Ya$gT~*my`>q#f+)V2WkbW4K@lQic zUDVwWTXvK{m$j&cGK9@{SPt>a9(scE#V=BcD zg&rzr(+SGqO(W3vam|7+_vJMEIK02D$Z5uAUPf8~YpHSM{c3hCk0$D1nWIBOl9ooB za@*eX-uV1MTQo65%0SX~(w=*)gZ+?n3sVb?q$sS-R5K-REwU6^nMlwF&vI*)zi9>2 zgMc+?!t}~@N!5n#u~Fmn#WX5Oe|%$9AorGQ`G(}_OeSxYK!|`l#+wj6cJNM&wci0< zIip#EcZi`7#?8SMm=5}ural6j8Tw$e$yukR99VxFU~HvIlcgR{0&YyP_It2?mb~>T z`IiY_>HUF;_qGq6I=9_*Mh>&~J^IWQPzuo~$Rv4pZ2O5ifD=>txb3LqBBoy{aDe?#<$2_6^UoUr zp${1)Hdmsm?}7A~Z{AEucEohw66W}Y2`Up>kpFB@8xHa58tEYs8&@Ubx9DK;pTuMD zs4*qMzp-r|fld*Qe3&GxknfftnGrqx5Cw)q7i#lKb0x}cMf2hH|zm+Xq538Vhdlv9pBP`G+)Iq~;VcqcY+G?k zjR?Z8AJQ6x9=@(T_lv?nU6*Z4NmW|FhrHyEbF0o)8~1Uji?|haS$RaZhy(_PR00`u z@aGzse1s38lsITPsqT%NAPcS~C`kV%(VUEC8$&prt^0_rcrnQ=7GA^}w5{VX>5Z3u z(HuJ7b`f?3b^r&Y&%EzOe<{Y2z!LlBTU@YG3Ql1uNVW78bkEJcq)hv}+4_p(DMBgA4So4nkzHI4^@|E&(s2IC&bLw|bQ|d$(;brHCkSUM9REBQ?(pTUv zojpx+=}Xuxv-fxB6dp7bC|XzgN|p~L)_GlxR?g{}Xq(`eabp-}lH-txX<@WGR2rNV zAI(z*yN5Ljg(*AVa3vbe6|xKw%T7D_R}}g+@A8Mncj%igrr<^FrH+cQ*c*fyrgUm~Z)b}w-Gw(> zp<8+#>j(XEm)w*WOJGP-%(VzxrcIx7oUCyH*wWdoZ}lN6%H873Oln_FUeL4vG3+Yy zO}(z<4$4m(%dGZ3m##vqCnVqg!LebbikDr4&= zq;lJ{y3*YbE@$$OxMm$?wW&98Kf>J5r#w-VGVI+T=30gn9Zz(jwwhZV3+U4q0@C~- z8-|5_%1hTE%v5sPqoeZ*Jx~tH#nCDDiTWBpDP1Q|=*;_!U*h!V<0jrH%EWN!L)Xr} zw=}R^2x*I-UW^_4&2;yfapSFwR*5f7Q?I2iT;?&KRme(M#GmcFldDq#ZeiQ6J2TfP z_vTLOHt}PgK{*rZZ$(Kfzy z0%E;kPNR4^v(5%f42Ngby?fQ>nM;;E*&J$3r`8c7t~K*>(Wr?zD}>95fl#4oPcsn= zEdj>VD|)v&_&HpTUaxP&+bPuJBL0%_S}TMS@#KV?nxAQEOS(XMHoJuTkK5(|BqvOV z5Ao~v($9i>2ev7;bd1LmWKt^}dl*Ji4_jFk2;)$$qV*;HE)!&0I03}j`>{ny393G& z9!48D)G~HKolCNOaDmI`e2m6hQhf#w&bW#tie9DXSGP#$zwpYCw}KnW=tJYXoqE!- z3vm}w;dDo7xT&xoA|LTtCn}hA*k95>oW@y6eDvGfy22<)(a5)boJZ%=Pu&4zaLeMT zVmxo7rv5!h6%{ckNYGOQ?ks?^9bcg{qoU^!W{B6|{%3=(}ehHpgWnr~H_S3q2eJluX zNnt^MS)FrPrgHt4mzU2OeixXkJg1*<>gld6JekP8|C3srj&b5;v(NZd`T+sRl;x>PV%!!NJ zw7Da&@S&2$lXTI7`{J5y(_eB1|gh4QLwl1P=n@^eCuongBS#u7W>G zc-~+MBP>wltSe8DceRbyc4_a?1=6}W2w4gD;A>}al16NM-TU}o9`%CH-_9*g*ZzD?Jxw^2;3<|%RYyre#op2 zw55E)7T69dBtcRe_+E{xtzI=HyyWbt@R30g49W_wp z?GWU}(~ohuji>tdR&Fi`a#wBhrUry)rrJbk{o-S52#~Ri3LdZWC~V%&3HENyPZD3` z2S>f3a9dI$pz7Q3iYfm>7Kpy8pD^bpx-#g7rOei4X=P+m+WKIWvF6i8Bj5%Jet;~@MTmnq~>Zsexp z!?KTE`yhHSi>fvYEJVYEa)8K89~!HL8Wwup?q}i-;MBt`tEmgt=S-2j5beY4hlgng zN|1ZtW91K(vPQw0j!q>mYuD5Ix`jh#3hsRLyhps`+K{-5+TD&i`qWx}&e%Hm9Co0} z-jO2(u2LRzAbl;eks6Q@RQAlHr#5ZJWSNBNfdc7ENeXo5k$!r}YoYV0lr%7S52m4H9cED>^^oG85gD!28^UdXr=0{^A(>@6$O0XzljlOxCr7 zb0-q)T-V8mi6iIYJ{v=wd=>JvluO7Sza4oAB(Y}o>1TvBV@m~9T@7;#U}}d#b|;j7 zuy$}Vjc*M7^m1T(f4DApvdK`p{IDWWzzfY@V?N!XMBcJ}{wii^lt+>wyoCE8vR`<$ zch5)ULz!B}I($~`<^cL}N@@5|UX?vOvn3J5_`cfzAvPE*x5?UJ!k4(Q!VdK@{zgC( z8hQXD0-{DUFbZU~>{&}!g0#FLYHMwH#@ixdu!`|IWu`iU=7{@pDQwtFhflJOc*z{( zDi5nX-U2L^y9XSUeiw|h%PntL!J0>}Q}#(>ZZDPFR{7#_r9~Q1!D*E0%IttDMCE2` z@Q3RWwqQw%vP7V=tyoaa%hSA)F2lH=+8EMB63SwI+=RJ@&4t+t-Hd5I7g(=@u78VQx+k>ixpDrcIH{d zlHeyuq{$C${@?WU1J6F``}W$t#4;=mW}G$m?EVcS^kB)0KQ8MJtf(nx_dYS7FMSUvEJ_Y^D zcIl0PIO}k*RCZUE1eRSDlTqoN7ZPrkt6^gxNZXr9DphaZ^a7hDgrRA@e>XYvY!~mc zAn3xk8n((FZ1B9eJA}S5Ed$~J0`qx)FNus~g}h4LrXkO%opWn_91i*D0B-2=UukYT z<{xNI+iCirX2BQYpL}f1Ius4&Sq;>~AiK-+9YUV}r#Bff=4dOEW zhZy6(eKDO(yXr{JhCsmd!7pII;In!&nyiwP*rTqd>?3-IMIQ-S%_er@BS@?2XE*~2uI-G zZm8qeg)d+J8;!*O!+rwcf8 z<@28@G5$~Y1t8n8t4upiKJw49zQ>(Gazzc^F=dRk}P{|#Dzg(GOds+Upn)?vHqsV^2+lk3kU1fIIWaCz&Y z7B|@_u-DZrV?Rms1-FirPN@O9F|dDQL`ylPu<(OR0!ztR!QmQhhNt=;uid?%PRlxC zi=)8cZcd&~HtkPY_CnjN$O#8ZNGn&Q84}&Iw}Njvaa?YT+cN(7=5F{opFiK6IA3zt z7V!}tC+Zt)r&uw*;J)O3j z!qioKeRGsTu`-|~P1Hhx5H0R7PZ7UM&CzQkA0{`kj2fz3{`3ESa}kL^Ixz(DM`|;q zi*~br*qI4l`6k#QQ8xyhCDZr$2iW_^h=BsJ(@4=3qK(bipkUw;))2$hDL9`-zz4@>0UMbIi3 zZ#I8IhTyEzpA9zl((WyMA4tJ{(X=XFbjj2jx5$|xSyenj)x-KS&X*QoY%OcX{n1>7 z&eM@lJe4en!rUZt_j&MJpZQZ2b4 z;5TA1lcUja&iGmoYGd*afrM&_L;c7M<}7lyD8>i*fjwJH12C=Lt$zTGeRA8=K8CtT#=TyDaN>)S`i*e;GAF)y=Qe$ z^|l$GAMHyG73(NcH7PrVUxN|xxzDSDo3*C@m76TpktLrlU*JPiA?U7>9R(_U_Vn>n z(dlN7^wGS2rWV83GoKlmm?t8*sApJZOVy*C&=e?UGlTnuT)?PZt@>gK4=QZ85JTgwb5$G)|&s_)&-hICZ)?Q50hBGN z%4mzYrX7c(eSr^+|L(QDKi=Vs4hp+fcTPV#emHl7sy$Mv4*WNd3k5$?#(4HQp zh(JA}D5HXaVF+4JML?i}%tKTNbBGeA2uZY7X^TK*o^fE1SrVoINmMFRfGBgA0zwkQ z6iFZo#4xYU|w+|4X-Dto(=*=B90 zA+a|HXhq3ghakvnd-%a}O(B6mn(BSLI+j9OzHQ~U)DscS3qPl;`OzvXQ8OY%mG4ul z^}h0e(7fnfF_`#;Um%L?pwTC&G*H?}UbCFqzJPaObh?IRcFom`2MHlXr^rqz;d7Qw z@=0QphEO!A^{E#TlIgq|l|8&|&Ay&`m1;d>Q|Y3oB71fMyl!^AbERp!-DGS+nm$rc zj8=v>v)CJIZ9%UO2q?dz4cWg)tq8tw@qxZpF*qc+xR;lxEA}>^HpOr&%GlMGF;!N6 z4(FGPUug^NVzX(WcLfhDVkN&Cn zWkU#Zw%32`+21Pb_;^x3!&zNrAP+$Fx_iS=P>R3)WIkt-B7ijSM8Y&DiJ(PB$T3>MM`w`4B_OroWh+prgy)n*)~YltQkQ@_2$uL#{0ay?UbFZ?A$gieXDtqt=IFFwprWL8KltX zbyWM%^;%?D`>}~o)do>g`rwTVX^$Y>+04554YwKNpfz^-;d~sGbgihdDfMjI-r&8P z`xjPiTG>CQ#=_7VM&Bw3!bGRR07`DaI~ND|KlbHJx8UFXzBI)D9mZ-WI^3Rm?SqPp zT=<33mwY&%(v+b-lqqZq6{EdFqQ%H3n6`7JmQ!EjNeg>3nbU2pelJeC^7tiPBf)CwC6ziUgNwPmHEI~L2z_N6;`6~<%Y z1KSdH&z1BOTx4iSq={~q7v7+&KEQ@_oinm8?|8H2JWd#@ zG!;EmjfieClD{>2bNBXQUu)aiGL88_?ILi7uqwKFgBp2nv3e^=dV3!f}_4^h# zykbJXP<4!0dsTauO0hq`H}tbn7OCL=!2(RI>TlW_g+-pJ13fAFpSsj&HoG;!DsJq1 z@l~oH#ZL6_CCBV^mjF5Ul9#uxR^V@}9ZU1h=u%MXB_G}m@3ILD9Q7x-&77;uoPXvU z(&#wvlyvAE;d#Ycd6aT4IZR9rn@8&s^0u2FZxG3*V&rFE*}d?~*8|L~>z3o{#oog} zU{=C#?Gv?EchTlGkyC(SN`O@5eNMN61I&!7uLmUy68Z2K8~M(Fx4Ec2JL~5 zj(T~g&gWSfQ(EyMCSD9KP-2&r{79yfLPBL@p-NRQb*bS4@$=$=pqB8v9Z)Z*Bcj1# z-LRUC-c)SED;-8FPDG=zhgI5`QxDYK*4w`#5mug~;c!^sv{lXJ(eO61)dBZ=C2QXE zrB~r{g05^u%)Gxc(>HaxIr1M*$X0~uj-k>G{PW&Sy{cxO^gBiQJD6!_u8>9TgRGi$~Qt$1%nCeuJxop%h_lp(Q=~9>o z?cipC>|M3EJZMp#&<^W#&lm&P0#k*im{pgA)hrLEXj->FTAuf+HS(MeGi;9N(xqEk zlvW({yOYVTA%2feh)7)?Zk~HnZNtuRGiZ$K6DtnX@0)Z8dF8W&0va;WEaT^J)6@e$ z7EO8`OI@tUU_Uy}fgXkL4*#z=>ibHgguBThr?^0k6dwHjA z0?$y7+&*BRtWG~2k>F*g;uaK5v7f(@GyUa7lU-lYs*mP)ZM4*2#r-A}h{!Q@?=juO znWw;6<;s4}TfXr{Ff2FQ)Dx~`nNct`iWHmak{Vhubyqd-#K>jXk@L+;o4wF-$m5!R zje24)gOr)y?oKW~5D`cImLXd_mn^IZJr*73M~GKcf8bfVW)mpv6vsBmZ8T3h_K+7V zUOboEXdu?C$IW9h+%%pXCh$XP2 z(>IsthOyGu=rU<%*iCH7%`h8)vDyedO*$|tC$8Tlh}$;oH4s;2Rqai#Y!CYhOT)a1 zgn@`yLl5@$PB6zm)P@c}y|m$=ytugN?CqWZ`PWASz%k9t!(+3|yxy$FXTXajh2A(C z%_%^oF6Z9aE$BX4nAR>04ea+T2t!s4G@7WfY|53oZ|74;qM&QozVz%@X~JWTH^DTY|ULuon(#>=97Hp z?_XKXjAJ6x)-BhzdXKvKuQmEpHROn?fFblpZ$6(%IkIvMT(rMBuvyiA+(QL;Po%%G z4ibwnrb2p&eGzf_>&$-n%Pt@LA?Ms&U;MGQLTge~YSMC3yslY$VssE(sr`~0Oa4gv z(4P)=pJK553UHh*=Ez9{h;!xT!4F?<{UvOz2omdqMVn)!d_bIsP-iI=zHb2pSKklg?)H#$F8G&pM3cCPi}sr zs0gAKPKq{_iD=Y`ThWB;42+8z($8};zRaKNQ4r~Tv4;gPdMW}y1P36 zZv1NQM6NM);wQ+5CL8_%>Q3Z;xBl|=hXL-`@wc24AHxC~KtMw(Y-;ss-0H8>tXwQmf&VcI1#MfmVQrJTG3Lm`R`pYRx8dxWve%DOTwOWihv>m~Dx)~V zht#WdY#NkN=_4nV$25^6M7bM2VRPtZK@~+feK2P80cWxf23M&zW6|=nKW}DarBmL+ zGJUIp_4_I&BCl7%d>`bE`Q|Jna|SDPtV|cjBUFOjhr6Z%YH|~oQUy?V(B2ub&OdS+ z;nb|pBR#kqD`Q;68iT5e8tRWn8V#HW4MP3uXyB`-0ASKFFKVehL4;NkQ!H&p0_=xQkU@8^!I~9%#d+P4du-@HKgdyWeNE7 z22NZ-(Ds2x-?O}|UlxpI1xl%5Lc(U_zD)1iT0LM7ks?jm=2l-3iM1zz5JVeD#U)_; zz@8$#1|HnYlnDXtRii4kjfO9^$A@#$dRazEu!KXpp<%lDW1$f>{o`2@cxQq9177tm zSwn8|A8uExTph4JN*mBHqtVcm@OfH4fzY)u0t?W=tL!)EJ85&Uh%;2?8uLcUJbE#M zB$QsLZRh>{i)^kg8{(qf(?|0G=Nn}G<|)16%i z)sb=e!|i^IGUPoRhFnT;MFAq+9*G}Lq z-!Io~OY5Z3P)^pz4GnHS!s~_&8F27j0q;VSnrQ+F_TafUdt|}{*H6~>e^`2^T>q@&#ObaHBi2fk@F#D= zDKeq0-dj_01;=nt2AktxYXPZ-2EX z`w1LYz#he{sUfG{oHvK@%CEO+nFB+G$)I{LieRvyFaVyGpTmU4ijsRiH zf7InW^_Ca`RVGJM>+DbqqU=i40uBZ<6*O?CA1G)b;wUD?@m2Y1Rg%prps8~dB-*5( zU|cj{TMX4n1xI`wjD7csoX%B!8>h%wSx|SE`r;N`<11C%6HBCkL9D0a?q|Xa$L*<^~lPb`|9n<##cPK5w4( z+FvWdE)M(1hKgCYkGt^4i)Cr`#aox@^T*TOv3G<`-Vp;KK}Gau+xFyK0(tXQ`?Kp; zE39p^^u4oCH1nzknx>x+N1UZlntD%<`no(sz1YR*-ldZ8cmQi<%)wt58xpHuMV|dSf_Ml}oxJYViJ40xF2AhHGH^6y2oz(d^2>|jsz(OD zb5QAFJGOj_qi`e=K^=~^GZk7`N`+BX$no)HsNhy#;q|jv?A$M+IYTecJQ{?AAyTZweJ-lPyM}TSL^4gxSTk*_-IO4i zu8QxPH9cN-QdLYeT*&iY^?|>7nJ38pRrunWy3K;Nb!%(5nnMw;!aQhuwsvZZ(G|4% z(;nVNy~4sC2Fc1ko06rVfib6SFEQ--4{fps(C?8gYU5fI(~{Gr4ZWC1 z9pU!uNO?J}%&NDuWKCF8>!-@otH747ld)BD1>AJNNRL7m)+Pk>Jqp|V2cJ6}wH63{ zVkVJpqFl*1!+o;3wg|(jkWY;_!dQ!RQ`oa$E6mu^v36>US<2FuZLytkL*llOXr{4~ zRqignJIQHqbW*8DCRrF=Zs2!MUGE}6mfy|mBx)tY4ZOE}5ai-s;fui|Bj~GhKgtzv z)g%IM%9ewUIDWPJRY#qjg-qM5X54S-TBLZP<3`$|p0^iyZ}7TbJRE$E*zQIU;Z4&K zZFp%%ilq^0hF`RPlNN9EcBe%hZ&TAaD62C?U6{Lf8Y|Z7>7I_U2P->`fdGXyH7-0z z)HK*AzflY}<`lYZG>Y?1K00cWBnUTP(V*}iHrn{0Q#w{rULgrIH0HG4&s4%}LzHeUv9 zitpf?BXg7~xO@p_U=NsDMQ(>x%r^yE970HH0%rLWI1TC0JfOy++TgUN!J}HDaopaj zgK1jyz%zAyzUz@8(N1BG*5!wya)3l7Y@|`s_Wz}5gAOJwU_9lpozP`*ury2w`u6M6 z!OJ!M^}fP!!g>KPC4cvhAR!1fO%V=a`&Z&C!|zDEmp`(-^;}P1?VVS}e_y&ujToEFwMt!EmhTwWo^fw1)CA&S;dUwmqj)7pzK57b8j0tLUF%Gs)I3qi{j!h znXZmPXp~JmxO;9*vwB*wgCv^s<2DfQi_y^=2N+>1A)V{md|!x?FF z8jL%;nNvR>IrT^(qt|g{SdR(-Fahvarju`2K4M09y0xyg^5Kz8Vd6V5Q>{SARA9mA z0puEa)bJq%AC4Bt29@oY)duZrQV>slRw&I!I=6mFY0`*W=M0*HT@M^Gr)}qViRD}r z&hOb132YtX+S8%SrqvaD2h9c`Pa-e0EqS=Q(i?tly6>r$$qpv?kqF_O!S?mHkKNcF ze1FvM(kE0m(`wr=aTo_9=YBc!!cjd43Z^=$)}~-{#S((@+pkyam|9yn6huhYO>CB% zb7r@U4Bb|?u%1ld{kY$KJ!+}_Dp?3iWQUD0@}-G|Fr7DU73g1D4+}I4yJUm!iSOM) zu&D*Ug3Z@D!q*`VA5h;Zdf4x#koj(z4>PFltI6snq^~4iK*2}ZMmNpW9_pUH*>)93 zW9#2e$F7W$UO}cyc*d*!^4pGTvEdR{HFm$CZ@v;ksJ zVJ6rdgSnpWG;p#@JzH*8tu43M%isjkOeb$`;5bqTkJt~k2`g1@DOoQ3W+@n6#}}84 zoNYguDeSIjPjq~PS#2=PtnmW$yV_qHzwGHY8jf~w$kO*64E-5z>gTim9hkc!17E6r zWn%y*jpw>6vkryW8|Or`j9Kw4bq+#l`~0R$xgMNWjSppUQ4>vRbGL1Z2TZ<}dFa$_ zq@$pokdlRxbI=^(*9P5*42r`5DexLRwkTowjiof(1}~KQ8v%PPw$0R+ZpGt(IL{;T3+HK#+o=&$$JtkmI4VBQ6>%UC!+ zXiE{+qvS2H-@r5BRe@Qu<=hxm?|I#wb@eUVA@6|2A~lt43rM<)-qk0iMzb@0*-k zvz8sF4aAvusoy-)d0!)UUdifP`Twy_-~cJN+#wTc7JQD+w@cNvo|n@w%fNF3djWZw zOWw9;?#ajI*#*}oKQ5BT4&s{EoYqZ)b%;&Db1yF&Rv7FuAV}9z`{G!+y>kfDxu!`w zU2H@fGfS_+FYT;%7HoJDo2c$a98+3(V$>btyl{r?r0l5QChU@%Wm#Oz`Lv85(}If8 zm8S$_!c2wFgxhaZ2EYG3ezo0-nq|-fFwrH`vWf*S3A*fIAW^ZZwxO#oRw&0)Iv}rz80!;ECj_=^rUfiGJ zO)^96H!#TbjtA}~fFGH<2e!f|4$J&nPSg4ko4&Y6MzLL$wiL>~i+8o6Nc=n2{0c|y zY7wJVv8}@K(-boD8YiERY)Uru;5RQ%8kN<=Y(1|sq` zOLx?XEMe&*D@X+9de;m68CqFI07K4k=H*|PSr&QH%zjdtZVS&iHKV4T{K+g3cfF9k z7(dU}_J&F}5dfA7Ty6VnMjGgVNlmS{^R}H_`yS2&oA3ImgYN;bKBaRZ^2U;ubTHOheF87t zaum;~+s;>30UMTvfTCaMph~>iipCLrxP_tYsSIE-ieb{Vii4uLuWaYR;RH`fc9WJ% z3bF7jT;2(-gJXT>KheNE2ELD68^YmqwxihD&*OapFo)C6=8&635PeLo+ba8Of58C3*vnw7+5CIUk70nzCLnyh zRi+Rf9bHjV)35$V_|^2;(EB+~G?xTNtS~JYty-`x-h}V(?d*Hyk$x-Z>5xWZ$Q=h~ zln+&yf4s5|%PiPC6S{N|_ww^h-}WAPV0B%7?3tE>M&-L8_5bzlYC_LNYDe1K(<{;7cJkhH|M*W zj(6XFyi|W_(y+I--@`*K%(!_pFx)<7CgzO%12;yjpHH<0%KbHVbn-+wsyTwwW;^fH ztfKVMDAAxyOUpWVPT{N?Z-n@-aVC|f&g|q~@hQ)sjZjQCW4$UcwI$vcU ztM9ZJlVF; zVPk#8W_5VgQC&W}-yTGsb@L~ysk_w&+r+y$gSJmQ)W+l#k7YdcCHpcO)Bo2X?*9hw z^k^4#4sM1l`?VGP!$EwZzv$tbnWCPSZ2HtL+YvcKGU#(~>$3F0wYz?p)L_lKpP^=_10DV- z`8##V{$uvxzZ|arGggFb&T-w>OW;w{_Lnn zlCUrLeGRg7=ojs*)K6zFb8{^r>doVi@v8EMZp9L_Ito7j+6Y8Qc@N}Q0J|_VJU|E4$6 zA$mhM(>JWfCN&xL`z0IZIn4Es^Mn8c)JvnRspdd?|t*MmUQ%sc)kv;8N~yoIUj9Zn$k=S|<${`pUkrC>T9 zu4*CJTsDu)mWXJnSsT8w(~Wc_1w*(#_YyB?J_k@)CnLr?f+%7Qk7Y-sg2KS}Dg10} z8Kih?;iM|LL!fFJbs@#_?fdqY;7iPB%~L!k)tDm{^Jg_Rs+U9GG=vQCDx5UZIYY&E zqjRDi?G_h{EFo>&rm!-Hd^h^Ys&t!+3tC<)9st_b4v`Awl~3`h@%VtAG<^N@{Bt?{ z-TIz*t!ymY!m~Z@27GL?mQ04mN+s-E^JqH_a4vylIm`P~a}cVw7$nf3`G(lF_3-w! zPF0+Eg}9^=8&oHg(0Z>TlZve3Sxsxr?4+QAOZ1V)yY}o~pa&H@`1*EG7^4Ewj@L8$ z4gCr@L*+rQerbb;#I_1;>ZkeM)o<0*iNzT5bY<{96b{wkW8+W!S?(N#(ykQ~wd}UQt zdMDD~7fXlJ(Z7oc{GWyF{A`~zkfU;4zEK)fj4>@VojQ@Ic_To{eDk;PCJ_t0{>DZp z5kzyUg@Lr9!n`~tgj14k9MJtboX~G7lCYh61M`t&JJrTAyFD-naJdkDxmFfka-0U{ z2qGQoVv0okbS;`C|6+>t_e&pUjtDmVN`g&gYN6-uszkC>g96xJacS?>n`rW!j+Uqw z62$(wLz&997837I%vj3s(u}NaTb(GW&+8zI6SS;FJ*oT`m*PMCHq+gw2;>jt zQ>%Yo5)E6vRQ}F3lHw$mh^pEPRJWK z*z7cml$57ZoDjg>t7fjHuuOqT zMKoL>U})m|ANLqVW-AAbzdi)ne7MsXv*nU&ituvW#QF1kb?X)u(xI@*3+tJvfr0V* zt|0Ie&-!cMccxPG<1ukj_Y~FBFi?Cf*hI8waAUBp02Kp8_q`#`sxi6;4$l-Tr%XJF zO8#50+SLKIb+V9T2s^zoJoP@kc%etT`04UMSRE;Ah7`}v%18Dfk2($P_YQwRHKgbn zlGdF=`ktgh)3`o2JsnTa{UliqvZfMY5Hl5@xU1yIRiYeu6oMp0=;G26in-5y!JTsR zTeIl|R79(Yby2#q;3LDy+?yz@CVF80eO2S5uCBJOu6VFHG<>1kSN9`-fd1!^G2(?~ z0;NDfGvS1;TlLd}`18#SC=_PCWKZO<@(EX7{OFUg_C1XfkWtun(Y^t&FCPDHM(Q@Q!jXSN8r&(TB9VDXIgZw-xB95xx zyx`4{#;g{vpJ|$onM~mH8S}u;vpToeQk7XiOdnL$Zux<*y(*Cz)SR)kBQ7XjKo8=J zXczLO~<$TXtc5gkTaUlPr!=dQ=>(OzKDiFTePe z#=WZMT3>5RjbdjulG>$POQIQ9cP54#4>M?!{3`FHfrw>>qV_ij0HM;IWUtJ3eGp6< zPFixYNHTda?)6y+cM8StvaW4sOs;c=2%z6g!K8M;>xXBFTZ>h4{>nfo1Q(ttR|i8O z)1$|xBSrA+Ot&|uID@73Q0%)$sx7bfb0(W%f`eA()X>o0h`oXyC-rq=#1Wv}2(35@wYRG}WDa+47 zJmE0zC2C^hpprS|a-2*mS`LEdZ*6T!m^7S0vcEF?iK&E5UpJXm(Rp8$9yg!NC!f8O zhqzj5;M-QsLy}mP=1~_CJNI05b#*PZuCpDYC^j>&iO$}s$!3mh17BxS05f|}z8@|f z*vgW!va)JLu$&{P2y4DmLFt&c)lj)pv0hb0572G}I4QrFjuorBiQ+>=JSLHum5=&; zE)hVVgP9iGc0x79#6H`ge;I-@it2vt+)a3MUagc}mLF(KFeJym8?Rs9^F>`d&(6=I z;pzG}9#(*o^lM4_yt{@4c}sON3Lc4@#>p_EEgZ;e7M(Vrw6kXlVzxMNT38j(wn6Dd zR-j6o&92}$Wn*h8(yq*Nz*^2Uf@#LfWi+3$6$8HW60IfV;jzny=0@CM{ci(H>vYBG zC9SRI>K1?$O6o>Y@19Me#|$Hv5c9@Jsl*4fF#`H=*0Uk_k{qaHBc9YOE-gBbd3;gZ z)CA%#q4*cO3w07}9wQlKI`J3(EfrV5ACQG&H>rzDSsSxK1#N5lfawBFQ%PA1?SvCA*lIdhP zVYz830+?r9fm|XzTdrSSZ4=H|3da3EQYTuv*`` zR=L;7MZ_8e`y~8gS)YUTc1YkrfhX~osr4Z>V?L41n`B5$0nfw>rchD9Xz{1Hez_n7 zT#8~3aKK?@gK?=vpX}Vdkk4q>l0(!XCnp-PHJ6D!ZvFXYhRriEVXT3|e0fESWSeV@ zmwlq3WosTgb4;$W<@m7GGdjKBbW$&V6=E)b4^}+M2wa^aiWr*P$eRLi4%1_`8+?wG5Y}EreVq~N)VECzWS#8AkWZ5KgR^`WYiP~b#XSl6(PWqcCHbILS z$3+xbn*r!=KR7fuSJgOh0i0Or)TN}3pSxGc>oay@*WtFqLacqwV+YQwAu=ccCs))O zP-yba^YJvOI1sdvdtJPO#!4kqar26DEG0L|naR6GL>`kD zVhqR?m5l~ktLq>tO6efK-g5o*IpX`rNk)+i-M|)UmnX~(OXNB2N1E1Jjk=+P`4kJ+ zh`)0+PyW4ldpU@A7kC{{FFlFm9WM`fl5E(fp#H!6m%4gt<$_7<6N#_C{MV|uilRLm)}?e($*j(rf*S~_o?re(2w!cqqG zBtS_pS_SO%5wIz*p;cXFrB1nY499$aQh{Y3RPhJew_~%qf?8L~PS1LhEHO&LD1|&V>z`ky*cs1RyINHte^xE$Ui!NP9ZJ`?# z{`|WxPW_EzKKb%SV=w0X7BSt*eCMCySngODx$u?ELZ@0pch((+Yysib!?W~Q28KVc z2`hdAlAB37y-(DE85O;(C}&l2eKSddt?StBDj1WeTiXX%zqj*~r4QATrE2ZNBO|3i z)W1G@vHSV$om3k#*uG_J+((wK21=?t>=8L-M)Jw@>;wacJ@ZB{>KI(m!+uo2Y> zchd#rQH7UdcJ6qtQ{9JF8As9H{P?lZ(8K9}@nDgUzIHr)Qpm8j{RPzZO;mq*)iG5= zjSW(-Zl-nVxW0m14J*^y@QS*`eNKec-L z#)vEZ{jdww<}dA^kN}dTU%B&fx!I1ckCGL+^rU67bJ7~{4W05 zt6zsf${UnuHV|U=pmyZ_5u|vH{j;aAG%I);{J;;9o8R4&%cWFAQ}0lMjOOid%0%z>|L)H9Va!L;7mm}U@n zlCaDuwjjQl0^NGoyJ}>{NI;naj~yI!Yk+2mXWZ$E+++M^`q2#&6FA}zn!^zvX0+N9 zR86`#K_KP0wfo_axa**pQQXy+OMJ_tMve&UYL`RZaX@+UfEQ-6v9ZA!sd5)EaI(v; zu19S5kDA^`({?M$YO1QJ+d@XMieKCz01D(tCk6njK<_)T`#`Fa3<72!B6;6|dj5$*2@rgf=$_%l1(hTzPjhvr9n_KO4he)}bN z+_FS8)@PX=@D|g*GmCAs%{i$`a3_>C_Z9fa)=~rB=D?T$rI<-1Gy!@QmN|c`=}T5a zI((z2E=${KSi4a@&A{^kqra$_>mZBuLu0;YzBkP&b}HgW8MMiWmkSAaAG-A_&fJxK zF)Td4m}-DA&$fbzrUHwKGp{DkR}G*Igr`i+7y~*hW_`WzY*T@+xvs36x-T^!DJD+w z<2u5s&4auuBXStSZNK6%wSgZL(lhg&cv{X?bOfuwnOdN*>Zo)e{gH8C*|522_2NN1 zCU4aZxQI)>VExV-}R0d!-~=_LXA5FVz;uOYf$%-F)MqiIhftm2`yve_opAD ztzWynEQo2k{yonb5!qp17dU|*(`_3Y{Ix)o&^!#oV~Qq_=?iOYqs< z7$u=tk9*mt%&d#s@kg}_Mo*BXX*IkzT0K*^w#+Hv;mMZVbcLfw{T-qD z{*kd7!9LqY8{}V9%>#?UYO8Q)whZBOgkN<(ck{u$$l!EZANgzLzLK&LW5$8P<>-*2 zRvA?_@1$sMz1(>2r@-46^A)?H{%PFh@`eKq3UcK7ZO^lmoJ=Ul;DF6P(^mnJva*(mx>Dyk292M_nBW{EebrTKLSF*HR7T|}N6IjUzMzpfkR z=J_eXcHndbGX~n~!se|$vO=9NzIP*0iQCdU&!L$as*XV@mbDy{h%{pXr-0*R;77cv z9kSMliz*-3Hwh%;S0>XZM$~ZG7aBhzqrj?ScFa1|kKI<~NBLjp*C`mCR0u5A-yKke zDDM4Tfu)t3&JH|Pb3Zq&%D{mf#e3JOZRSlUj0jY>)*Je&#}neex&Bfey@*Sr5n)R0 zSHbkYhV~_aqs7xC+YjSsWh0pijER)UZ^A_)^xmkw!M)eIi5N-EBZ~6>{1(!y|cF_JS++|zjSN`^`c7p;6uoBDIluz z;QHk<>+A($NG^G0cJBo|8|3LRohZdmXhU}ht`*gE7tsJ#ms-6N&v8)%r+4Omjtrc| zkCi41skTtF_18Y!BFHmGP)jmf-Je!em{w*ne+ri90&~w6#>QD$e}nfVj7GZ>d&`dE zJQ2B#TAciR13F-@@FGd|n$$eiI=fQA^jM~4mFyp{dJD|1VG(7*nWqhZ2`Wt?He6n< zS!4HQ>P7=K^q8&{lyE~ljJt8=m8`iAVX@s)5EEC$x?G-iR= zBp6NM>b^Zaz@1q2% zB3iC?rfL=+cPT3Y5nhbhg-%4)UXF9NY)sTB3BJPKJMq*%Euc5!IB{DrDfb`Y=?g1rIr{EOetVHhWfQ4u46a(^{TJO zaf?k1L=)wM>W9DlT#@vAqC%c)8Tu~m4{#allW@aa1D8O5{wMHh%&sqnnvTg9I(u2s zqcf9X{h(-yw*-@!kBp2!SULVWZB+R&-5lWk3TEQDaz6nduij77rg`Q$`QcrmUi1yO zIIP<}U(bAdM(wr>C}jB8&>=Syf; z$$B*X>G2PS?!D4cU}x1WOf#Il3qhq*I&Ml9Rq+qc5y%j)e%G`yzol0^^x?@JT&W8N4gX?aK#>bqF*MG#ZNS!}$=$YF91aRK?&rAGSePGDE)Li4C zCtaO?V9Fg!rInYSoF6f>dRkRwk3b++TQ=WDSxPtYw+rtnJJrQ=^~1spDdOqj`;?8* z%kC6skXO06K7QKfVDx3^`Xf+Mf2+A$f@reEI5>EEGgOV8N5Em)Y9Bfp<^~pd#0CA4 zRcR>x?mrOWL-;?TzK>)2rzJN3IS)jf{{s;3jyV9eB(0K~t80iVFpnPuE%B)iy&5~Y z8iuRS&Mk(8_D-0_^Lj2vuXue-p0zh->DM>{91l0%H5PGI=?vIOE1Y1LGZfpo_ak+l zoBt39;4Jw2-^>(N|6k$Jt{D Date: Tue, 26 Aug 2025 12:51:48 +0530 Subject: [PATCH 43/84] Added website and aifoundry related avm changes --- infra/main.bicep | 321 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 245 insertions(+), 76 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 267867c1a..42a41487c 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -4,14 +4,11 @@ targetScope = 'resourceGroup' @minLength(3) @maxLength(20) @description('Required. A unique prefix for all resources in this deployment. This should be 3-20 characters long:') -param solutionName string = 'clientadvisor' +param solutionName string = 'clientadvisor' @description('Optional. Existing Log Analytics Workspace Resource ID') param existingLogAnalyticsWorkspaceId string = '' -@description('Optional. Use this parameter to use an existing AI project resource ID') -param azureExistingAIProjectResourceId string = '' - @description('Optional. CosmosDB Location') param cosmosLocation string = 'eastus2' @@ -21,7 +18,7 @@ param cosmosLocation string = 'eastus2' 'Standard' 'GlobalStandard' ]) -param deploymentType string = 'GlobalStandard' +param gptModelDeploymentType string = 'GlobalStandard' @minLength(1) @description('Optional. Name of the GPT model to deploy:') @@ -30,6 +27,12 @@ param deploymentType string = 'GlobalStandard' ]) param gptModelName string = 'gpt-4o-mini' +@description('Optional. Version of the GPT model to deploy.') +param gptModelVersion string = '2024-07-18' + +@description('Optional. Version of the GPT model to deploy.') +param embeddingModelVersion string = '2' + @description('Optional. API version for the Azure OpenAI service.') param azureOpenaiAPIVersion string = '2025-04-01-preview' @@ -37,7 +40,7 @@ param azureOpenaiAPIVersion string = '2025-04-01-preview' @description('Optional. Capacity of the GPT deployment:') // You can increase this, but capacity is limited per model/region, so you will get errors if you go over // https://learn.microsoft.com/en-us/azure/ai-services/openai/quotas-limits -param gptDeploymentCapacity int = 200 +param gptModelCapacity int = 200 @minLength(1) @description('Optional. Name of the Text Embedding model to deploy:') @@ -52,8 +55,6 @@ param embeddingDeploymentCapacity int = 80 // @description('Fabric Workspace Id if you have one, else leave it empty. ') // param fabricWorkspaceId string -@description('The Docker image tag to use for the application deployment.') -param imageTag string = 'latest' //restricting to these regions because assistants api for gpt-4o-mini is available only in these regions @allowed([ @@ -79,18 +80,18 @@ param imageTag string = 'latest' } }) @description('Required. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') -param aiDeploymentsLocation string +param azureAiServiceLocation string @description('Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') param AZURE_LOCATION string = '' var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION //var solutionSuffix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' - + @maxLength(5) @description('Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.') param solutionUniqueToken string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) - + var solutionSuffix= toLower(trim(replace( replace( replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''), @@ -138,8 +139,6 @@ param enablePurgeProtection bool = false //var solutionLocation = resourceGroupLocation // var baseUrl = 'https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/' -var hostingPlanName = 'asp-${solutionSuffix}' -var websiteName = 'app-${solutionSuffix}' var appEnvironment = 'Prod' var azureSearchIndex = 'transcripts_index' var azureSearchUseSemanticSearch = 'True' @@ -166,7 +165,7 @@ var useAIProjectClientFlag = 'False' var sqlServerFqdn = '${sqlDBModule.outputs.name}.database.windows.net' @description('Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true.') -param vmSize string? +param vmSize string? @description('Optional. Admin username for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.') @secure() @@ -232,8 +231,6 @@ var replicaLocation = replicaRegionPairs[resourceGroup().location] @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} -var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' - // Region pairs list based on article in [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions) for supported high availability regions for CosmosDB. var cosmosDbZoneRedundantHaRegionPairs = { australiaeast: 'uksouth' //'southeastasia' @@ -248,7 +245,6 @@ var cosmosDbZoneRedundantHaRegionPairs = { westeurope: 'northeurope' } - var allTags = union( { 'azd-env-name': solutionName @@ -320,7 +316,6 @@ module network 'modules/network.bicep' = if (enablePrivateNetworking) { } } - // ========== Private DNS Zones ========== // var privateDnsZones = [ 'privatelink.cognitiveservices.azure.com' @@ -381,17 +376,16 @@ module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ // Extracts subscription, resource group, and workspace name from the resource ID when using an existing Log Analytics workspace var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) - + var existingLawSubscription = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[2] : '' var existingLawResourceGroup = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[4] : '' var existingLawName = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[8] : '' - + resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = if (useExistingLogAnalytics) { name: existingLawName scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) } - // ========== Log Analytics Workspace ========== // // WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics // WAF PSRules for Log Analytics: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#azure-monitor-logs @@ -456,10 +450,18 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 } // Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) -var logAnalyticsWorkspaceName = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.name : logAnalyticsWorkspace!.outputs.name -var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace!.outputs.resourceId -var logAnalyticsPrimarySharedKey = useExistingLogAnalytics? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey -var logAnalyticsWorkspaceId = useExistingLogAnalytics? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId +// var logAnalyticsWorkspaceName = useExistingLogAnalytics +// ? existingLogAnalyticsWorkspace!.name +// : logAnalyticsWorkspace!.outputs.name +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics + ? existingLogAnalyticsWorkspaceId + : logAnalyticsWorkspace!.outputs.resourceId +// var logAnalyticsPrimarySharedKey = useExistingLogAnalytics +// ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey +// : logAnalyticsWorkspace.outputs.primarySharedKey +// var logAnalyticsWorkspaceId = useExistingLogAnalytics +// ? existingLogAnalyticsWorkspace!.properties.customerId +// : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId // ========== Application Insights ========== // // WAF best practices for Application Insights: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/application-insights @@ -501,9 +503,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { enableRbacAuthorization: true enableSoftDelete: true softDeleteRetentionInDays: 7 - diagnosticSettings: enableMonitoring - ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] - : [] + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : [] // WAF aligned configuration for Private Networking privateEndpoints: enablePrivateNetworking ? [ @@ -511,7 +511,9 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { name: 'pep-${keyVaultName}' customNetworkInterfaceName: 'nic-${keyVaultName}' privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.keyVault]!.outputs.resourceId}] + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.keyVault]!.outputs.resourceId } + ] } service: 'vault' subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId @@ -521,9 +523,9 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { // WAF aligned configuration for Role-based Access Control roleAssignments: [ { - principalId: userAssignedIdentity.outputs.principalId - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'Key Vault Administrator' + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Key Vault Administrator' } ] secrets: [ @@ -537,24 +539,151 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { } // ==========AI Foundry and related resources ========== // -module aifoundry 'deploy_ai_foundry.bicep' = { - name: 'deploy_ai_foundry' +// module aifoundry 'deploy_ai_foundry.bicep' = { +// name: 'deploy_ai_foundry' +// params: { +// solutionName: solutionSuffix +// solutionLocation: aiDeploymentsLocation +// keyVaultName: keyvault.outputs.name +// deploymentType: gptModelDeploymentType +// gptModelName: gptModelName +// azureOpenaiAPIVersion: azureOpenaiAPIVersion +// gptDeploymentCapacity: gptModelCapacity +// embeddingModel: embeddingModel +// embeddingDeploymentCapacity: embeddingDeploymentCapacity +// existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId +// azureExistingAIProjectResourceId: azureExistingAIProjectResourceId +// aiFoundryAiServicesAiProjectResourceName: 'proj-${solutionSuffix}' +// tags: tags +// } +// scope: resourceGroup(resourceGroup().name) +// } + +// ========== AI Foundry: AI Services ========== // +// WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai + +// NOTE: Required version 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' not available in AVM +var aiFoundryAiServicesResourceName = 'aif-${solutionSuffix}' +var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' +var aiFoundryAIservicesEnabled = true +var aiFoundryAiServicesModelDeployment = { + format: 'OpenAI' + name: gptModelName + version: gptModelVersion + sku: { + name: gptModelDeploymentType + capacity: gptModelCapacity + } + raiPolicyName: 'Microsoft.Default' +} + +var aiFoundryAiServicesEmbeddingModel = { + name: embeddingModel + version: embeddingModelVersion + sku: { + name: 'GlobalStandard' + capacity: embeddingDeploymentCapacity + } + raiPolicyName: 'Microsoft.Default' +} + +//TODO: update to AVM module when AI Projects and AI Projects RBAC are supported +module aiFoundryAiServices 'modules/ai-services.bicep' = if (aiFoundryAIservicesEnabled) { + name: take('avm.res.cognitive-services.account.${aiFoundryAiServicesResourceName}', 64) params: { - solutionName: solutionSuffix - solutionLocation: aiDeploymentsLocation - keyVaultName: keyvault.outputs.name - deploymentType: deploymentType - gptModelName: gptModelName - azureOpenaiAPIVersion: azureOpenaiAPIVersion - gptDeploymentCapacity: gptDeploymentCapacity - embeddingModel: embeddingModel - embeddingDeploymentCapacity: embeddingDeploymentCapacity - existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId - azureExistingAIProjectResourceId: azureExistingAIProjectResourceId - aiFoundryAiServicesAiProjectResourceName : aiFoundryAiServicesAiProjectResourceName + name: aiFoundryAiServicesResourceName + location: azureAiServiceLocation tags: tags + existingFoundryProjectResourceId: existingFoundryProjectResourceId + projectName: aiFoundryAiServicesAiProjectResourceName + projectDescription: 'AI Foundry Project' + sku: 'S0' + kind: 'AIServices' + disableLocalAuth: true + customSubDomainName: aiFoundryAiServicesResourceName + apiProperties: { + //staticsEnabled: false + } + networkAcls: { + defaultAction: 'Allow' + virtualNetworkRules: [] + ipRules: [] + } + managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] } //To create accounts or projects, you must enable a managed identity on your resource + roleAssignments: [ + { + roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + // WAF aligned configuration for Monitoring + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + privateEndpoints: (enablePrivateNetworking && empty(existingFoundryProjectResourceId)) + ? ([ + { + name: 'pep-${aiFoundryAiServicesResourceName}' + customNetworkInterfaceName: 'nic-${aiFoundryAiServicesResourceName}' + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + name: 'ai-services-dns-zone-cognitiveservices' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId + } + { + name: 'ai-services-dns-zone-openai' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.openAI]!.outputs.resourceId + } + { + name: 'ai-services-dns-zone-aiservices' + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.aiServices]!.outputs.resourceId + } + ] + } + } + ]) + : [] + deployments: [ + { + name: aiFoundryAiServicesModelDeployment.name + model: { + format: aiFoundryAiServicesModelDeployment.format + name: aiFoundryAiServicesModelDeployment.name + version: aiFoundryAiServicesModelDeployment.version + } + raiPolicyName: aiFoundryAiServicesModelDeployment.raiPolicyName + sku: { + name: aiFoundryAiServicesModelDeployment.sku.name + capacity: aiFoundryAiServicesModelDeployment.sku.capacity + } + } + { + name: aiFoundryAiServicesEmbeddingModel.name + model: { + format: 'OpenAI' + name: aiFoundryAiServicesEmbeddingModel.name + version: aiFoundryAiServicesEmbeddingModel.version + } + raiPolicyName: aiFoundryAiServicesEmbeddingModel.raiPolicyName + sku: { + name: aiFoundryAiServicesEmbeddingModel.sku.name + capacity: aiFoundryAiServicesEmbeddingModel.sku.capacity + } + } + ] } - scope: resourceGroup(resourceGroup().name) } // ========== CosmosDB ========== // @@ -568,7 +697,6 @@ module aifoundry 'deploy_ai_foundry.bicep' = { // scope: resourceGroup(resourceGroup().name) // } - //========== AVM WAF ========== // //========== Cosmos DB module ========== // var cosmosDbResourceName = 'cosmos-${solutionSuffix}' @@ -667,7 +795,6 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { scope: resourceGroup(resourceGroup().name) } - // ========== Storage Account Module ========== // // module storageAccountModule 'deploy_storage_account.bicep' = { // name: 'deploy_storage_account' @@ -793,7 +920,6 @@ module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0. } } - var sqlDbName = 'sqldb-${solutionSuffix}' module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { name: 'serverDeployment' @@ -817,7 +943,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { // } databases: [ { - availabilityZone: 1 + availabilityZone: enableRedundancy? 1 : -1 backupLongTermRetentionPolicy: { monthlyRetention: 'P6M' } @@ -825,15 +951,17 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { retentionDays: 14 } collation: 'SQL_Latin1_General_CP1_CI_AS' - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - elasticPoolResourceId: resourceId('Microsoft.Sql/servers/elasticPools', 'sql-${solutionSuffix}', 'sqlswaf-ep-001') + diagnosticSettings: enableMonitoring + ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] + : null licenseType: 'LicenseIncluded' maxSizeBytes: 34359738368 name: 'sqldb-${solutionSuffix}' sku: { - capacity: 0 - name: 'ElasticPool' + name: 'GP_S_Gen5' tier: 'GeneralPurpose' + family: 'Gen5' + capacity: 2 } } ] @@ -906,12 +1034,6 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { } } -//========== Updates to Key Vault ========== // -resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: aifoundry.outputs.keyvaultName - scope: resourceGroup(resourceGroup().name) -} - // // ========== App Service Module ========== // // module appserviceModule 'deploy_app_service.bicep' = { // name: 'deploy_app_service' @@ -1017,7 +1139,52 @@ module webSite 'modules/web-sites.bicep' = { { name: 'appsettings' properties: { - + APP_ENV: appEnvironment + APPINSIGHTS_INSTRUMENTATIONKEY: enableMonitoring ? applicationInsights!.outputs.instrumentationKey : '' + APPLICATIONINSIGHTS_CONNECTION_STRING: enableMonitoring ? applicationInsights!.outputs.connectionString : '' + AZURE_SEARCH_SERVICE: ''//azureSearchService + AZURE_SEARCH_INDEX: azureSearchIndex + AZURE_SEARCH_USE_SEMANTIC_SEARCH: azureSearchUseSemanticSearch + AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG: azureSearchSemanticSearchConfig + AZURE_SEARCH_TOP_K: azureSearchTopK + AZURE_SEARCH_ENABLE_IN_DOMAIN: azureSearchEnableInDomain + AZURE_SEARCH_CONTENT_COLUMNS: azureSearchContentColumns + AZURE_SEARCH_FILENAME_COLUMN: azureSearchFilenameColumn + AZURE_SEARCH_TITLE_COLUMN: azureSearchTitleColumn + AZURE_SEARCH_URL_COLUMN: azureSearchUrlColumn + AZURE_OPENAI_RESOURCE: aiFoundryAiServices.outputs.name + AZURE_OPENAI_MODEL: gptModelName + AZURE_OPENAI_ENDPOINT: aiFoundryAiServices.outputs.endpoint + AZURE_OPENAI_TEMPERATURE: azureOpenAITemperature + AZURE_OPENAI_TOP_P: azureOpenAITopP + AZURE_OPENAI_MAX_TOKENS: azureOpenAIMaxTokens + AZURE_OPENAI_STOP_SEQUENCE: azureOpenAIStopSequence + AZURE_OPENAI_SYSTEM_MESSAGE: azureOpenAISystemMessage + AZURE_OPENAI_PREVIEW_API_VERSION: azureOpenaiAPIVersion + AZURE_OPENAI_STREAM: azureOpenAIStream + AZURE_SEARCH_QUERY_TYPE: azureSearchQueryType + AZURE_SEARCH_VECTOR_COLUMNS: azureSearchVectorFields + AZURE_SEARCH_PERMITTED_GROUPS_COLUMN: azureSearchPermittedGroupsField + AZURE_SEARCH_STRICTNESS: azureSearchStrictness + AZURE_OPENAI_EMBEDDING_NAME: embeddingModel + AZURE_OPENAI_EMBEDDING_ENDPOINT: aiFoundryAiServices.outputs.endpoint + SQLDB_SERVER: sqlServerFqdn + SQLDB_DATABASE: sqlDbName + USE_INTERNAL_STREAM: useInternalStream + AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.name + AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName + AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName + AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback + SQLDB_USER_MID: userAssignedIdentity.outputs.clientId + AZURE_AI_SEARCH_ENDPOINT: '' //azureSearchServiceEndpoint + AZURE_SQL_SYSTEM_PROMPT: functionAppSqlPrompt + AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT: functionAppCallTranscriptSystemPrompt + AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT: functionAppStreamTextSystemPrompt + USE_AI_PROJECT_CLIENT: useAIProjectClientFlag + AZURE_AI_AGENT_ENDPOINT: aiFoundryAiServices.outputs.aiProjectInfo.apiEndpoint + AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME: gptModelName + AZURE_AI_AGENT_API_VERSION: azureOpenaiAPIVersion + AZURE_SEARCH_CONNECTION_NAME: '' //aiSearchProjectConnectionName } // WAF aligned configuration for Monitoring applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null @@ -1035,7 +1202,9 @@ module webSite 'modules/web-sites.bicep' = { name: 'pep-${webSiteResourceName}' customNetworkInterfaceName: 'nic-${webSiteResourceName}' privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.appService]!.outputs.resourceId }] + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.appService]!.outputs.resourceId } + ] } service: 'sites' subnetResourceId: network!.outputs.subnetWebResourceId @@ -1045,7 +1214,6 @@ module webSite 'modules/web-sites.bicep' = { } } - @description('URL of the deployed web application.') output WEB_APP_URL string = 'https://${webSite.outputs.name}.azurewebsites.net' @@ -1065,7 +1233,7 @@ output COSMOSDB_ACCOUNT_NAME string = cosmosDb.outputs.name output RESOURCE_GROUP_NAME string = resourceGroup().name @description('The resource ID of the AI Foundry instance.') -output AI_FOUNDRY_RESOURCE_ID string = aifoundry.outputs.aiFoundryId +output AI_FOUNDRY_RESOURCE_ID string = aiFoundryAiServices.outputs.resourceId @description('Name of the SQL Database server.') output SQLDB_SERVER_NAME string = sqlDBModule.outputs.name @@ -1079,7 +1247,7 @@ output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name @description('Client ID of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId @description('Name of the AI Search service.') -output AI_SEARCH_SERVICE_NAME string = aifoundry.outputs.aiSearchService +output AI_SEARCH_SERVICE_NAME string = '' //aifoundry.outputs.aiSearchService @description('Name of the deployed web application.') output WEB_APP_NAME string = webSite.outputs.name @@ -1087,22 +1255,24 @@ output WEB_APP_NAME string = webSite.outputs.name output APP_ENV string = appEnvironment @description('The Application Insights instrumentation key.') -output APPINSIGHTS_INSTRUMENTATIONKEY string = aifoundry.outputs.instrumentationKey +output APPINSIGHTS_INSTRUMENTATIONKEY string = enableMonitoring ? applicationInsights!.outputs.instrumentationKey : '' @description('The Application Insights connection string.') -output APPLICATIONINSIGHTS_CONNECTION_STRING string = aifoundry.outputs.applicationInsightsConnectionString +output APPLICATIONINSIGHTS_CONNECTION_STRING string = enableMonitoring + ? applicationInsights!.outputs.connectionString + : '' @description('The API version used for the Azure AI Agent service.') output AZURE_AI_AGENT_API_VERSION string = azureOpenaiAPIVersion @description('The endpoint URL of the Azure AI Agent project.') -output AZURE_AI_AGENT_ENDPOINT string = aifoundry.outputs.aiFoundryProjectEndpoint +output AZURE_AI_AGENT_ENDPOINT string = aiFoundryAiServices.outputs.aiProjectInfo.apiEndpoint @description('The deployment name of the GPT model for the Azure AI Agent.') output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName @description('The endpoint URL of the Azure AI Search service.') -output AZURE_AI_SEARCH_ENDPOINT string = aifoundry.outputs.aiSearchTarget +output AZURE_AI_SEARCH_ENDPOINT string = '' //aifoundry.outputs.aiSearchTarget @description('The system prompt used for call transcript processing in Azure Functions.') output AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT string = functionAppCallTranscriptSystemPrompt @@ -1120,13 +1290,13 @@ output AZURE_COSMOSDB_DATABASE string = cosmosDbDatabaseName output AZURE_COSMOSDB_ENABLE_FEEDBACK string = azureCosmosDbEnableFeedback @description('The endpoint URL for the Azure OpenAI Embedding model.') -output AZURE_OPENAI_EMBEDDING_ENDPOINT string = aifoundry.outputs.aoaiEndpoint +output AZURE_OPENAI_EMBEDDING_ENDPOINT string = aiFoundryAiServices.outputs.aiProjectInfo.apiEndpoint @description('The name of the Azure OpenAI Embedding model.') output AZURE_OPENAI_EMBEDDING_NAME string = embeddingModel @description('The endpoint URL for the Azure OpenAI service.') -output AZURE_OPENAI_ENDPOINT string = aifoundry.outputs.aoaiEndpoint +output AZURE_OPENAI_ENDPOINT string = aiFoundryAiServices.outputs.endpoint @description('The maximum number of tokens for Azure OpenAI responses.') output AZURE_OPENAI_MAX_TOKENS string = azureOpenAIMaxTokens @@ -1138,7 +1308,7 @@ output AZURE_OPENAI_MODEL string = gptModelName output AZURE_OPENAI_PREVIEW_API_VERSION string = azureOpenaiAPIVersion @description('The Azure OpenAI resource name.') -output AZURE_OPENAI_RESOURCE string = aifoundry.outputs.aiFoundryName +output AZURE_OPENAI_RESOURCE string = aiFoundryAiServices.outputs.name @description('The stop sequence(s) for Azure OpenAI responses.') output AZURE_OPENAI_STOP_SEQUENCE string = azureOpenAIStopSequence @@ -1159,7 +1329,7 @@ output AZURE_OPENAI_TEMPERATURE string = azureOpenAITemperature output AZURE_OPENAI_TOP_P string = azureOpenAITopP @description('The name of the Azure AI Search connection.') -output AZURE_SEARCH_CONNECTION_NAME string = aifoundry.outputs.aiSearchFoundryConnectionName +output AZURE_SEARCH_CONNECTION_NAME string = '' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName @description('The columns in Azure AI Search that contain content.') output AZURE_SEARCH_CONTENT_COLUMNS string = azureSearchContentColumns @@ -1183,7 +1353,7 @@ output AZURE_SEARCH_QUERY_TYPE string = azureSearchQueryType output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = azureSearchSemanticSearchConfig @description('The name of the Azure AI Search service.') -output AZURE_SEARCH_SERVICE string = aifoundry.outputs.aiSearchService +output AZURE_SEARCH_SERVICE string = '' //aifoundry.outputs.aiSearchService @description('The strictness setting for Azure AI Search semantic ranking.') output AZURE_SEARCH_STRICTNESS string = azureSearchStrictness @@ -1217,4 +1387,3 @@ output USE_AI_PROJECT_CLIENT string = useAIProjectClientFlag @description('Indicates whether the internal stream should be used.') output USE_INTERNAL_STREAM string = useInternalStream - From 9ed413c4b6dd3b0f0800faf9f34885b308138d28 Mon Sep 17 00:00:00 2001 From: VishalS-Microsoft Date: Fri, 29 Aug 2025 10:59:29 +0530 Subject: [PATCH 44/84] added colon Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/TroubleShootingSteps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md index af1c2e346..9b45e30d2 100644 --- a/docs/TroubleShootingSteps.md +++ b/docs/TroubleShootingSteps.md @@ -55,7 +55,7 @@ Before deploying the resources, you may need to enable the **Bring Your Own Publ ## Option 1 ### Steps -1. Go to [Azure Portal](https:/portal.azure.com/#home). +1. Go to [Azure Portal](https://portal.azure.com/#home). 2. Click on the **"Resource groups"** option available on the Azure portal home page. ![alt text](../docs/images/AzureHomePage.png) From f9e0c08ba7c2f3c14258c244abf76b9540303d13 Mon Sep 17 00:00:00 2001 From: VishalS-Microsoft Date: Fri, 29 Aug 2025 11:00:07 +0530 Subject: [PATCH 45/84] Replace 'u' with 'you' for proper grammarUpdate docs/TroubleShootingSteps.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/TroubleShootingSteps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md index 9b45e30d2..21d24b6aa 100644 --- a/docs/TroubleShootingSteps.md +++ b/docs/TroubleShootingSteps.md @@ -79,7 +79,7 @@ To prevent this issue, please ensure that the resource group you are targeting f ### Steps: 1. Go to [Azure Portal](https://portal.azure.com/#home) 2. Go to resource group option and search for targeted resource group -3. If Targeted resource group is there and deletion for this is in progress, it means u cannot use this, you can create new or use any other resource group +3. If Targeted resource group is there and deletion for this is in progress, it means you cannot use this, you can create new or use any other resource group From d31d0e89fad89e0fc8773475ebc2bc0004b88e35 Mon Sep 17 00:00:00 2001 From: VishalS-Microsoft Date: Fri, 29 Aug 2025 11:00:48 +0530 Subject: [PATCH 46/84] extra space removed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/DeploymentGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index f8196592a..b710c7e4f 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -218,7 +218,7 @@ If you need to rebuild the source code and push the updated container to the dep This will rebuild the source code, package it into a container, and push it to the Azure Container Registry associated with your deployment. ### 🛠️ Troubleshooting - If you encounter any issues during the deployment process, please refer [troubleshooting](../docs/TroubleShootingSteps.md) document for detailed steps and solutions + If you encounter any issues during the deployment process, please refer [troubleshooting](../docs/TroubleShootingSteps.md) document for detailed steps and solutions ## Post Deployment Steps From 3c2645f360c1d1d595b6f0a6249bccad98de12a6 Mon Sep 17 00:00:00 2001 From: VishalS-Microsoft Date: Fri, 29 Aug 2025 11:01:31 +0530 Subject: [PATCH 47/84] The command block is not properly closed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/TroubleShootingSteps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md index 21d24b6aa..88e6fa57b 100644 --- a/docs/TroubleShootingSteps.md +++ b/docs/TroubleShootingSteps.md @@ -116,7 +116,7 @@ based on available quota you can deploy application otherwise, you can request f - Make sure there are no typos in the resource ID. - Verify that the provisioning state of the existing resource is `Succeeded` by running the following command to avoid this error while deployment or restoring the resource. - ``` + ```bash az resource show --ids --query "properties.provisioningState" ``` - Sample Resource IDs format From 3f092e4d52610152f1e3f3e35cc896899960bd9c Mon Sep 17 00:00:00 2001 From: VishalS-Microsoft Date: Fri, 29 Aug 2025 11:02:33 +0530 Subject: [PATCH 48/84] The command should be wrapped in a proper code block with language identifier for better formatting and readability. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/TroubleShootingSteps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md index 88e6fa57b..899be78cf 100644 --- a/docs/TroubleShootingSteps.md +++ b/docs/TroubleShootingSteps.md @@ -68,7 +68,7 @@ Before deploying the resources, you may need to enable the **Bring Your Own Publ - This error can occur if you deploy the template using the same .env file - from a previous deployment. - To avoid this issue, create a new environment before redeploying. - You can use the following command to create a new environment: - ``` + ```bash azd env new ``` From 19567e7a9cdc55f7bbe338535f3a9de481ccaf8f Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 29 Aug 2025 11:18:16 +0000 Subject: [PATCH 49/84] Added Log Analytics, App Insights, CosmosDb, Sql Server, Storage Account, Search Service related avm changes and Updated the Azure credential retrieval in multiple Python files to include the Managed Identity parameter for improved authentication --- infra/main.bicep | 456 +- infra/main.json | 58481 +++++++++++++++- infra/main.waf.parameters.json | 59 + infra/modules/network.bicep | 2 +- src/App/app.py | 5 +- src/App/backend/agents/agent_factory.py | 6 +- .../backend/plugins/chat_with_data_plugin.py | 4 +- 7 files changed, 56610 insertions(+), 2403 deletions(-) create mode 100644 infra/main.waf.parameters.json diff --git a/infra/main.bicep b/infra/main.bicep index 42a41487c..5713ad3ec 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -9,6 +9,9 @@ param solutionName string = 'clientadvisor' @description('Optional. Existing Log Analytics Workspace Resource ID') param existingLogAnalyticsWorkspaceId string = '' +@description('Optional. Resource ID of an existing Foundry project') +param azureExistingAIProjectResourceId string = '' + @description('Optional. CosmosDB Location') param cosmosLocation string = 'eastus2' @@ -118,7 +121,7 @@ param enableTelemetry bool = true param enableRedundancy bool = false @description('Optional. The Container Registry hostname where the docker images for the frontend are located.') -param containerRegistryHostname string = 'biabcontainerreg.azurecr.io' +param containerRegistryHostname string = 'bycwacontainerreg.azurecr.io' @description('Optional. The Container Image Name to deploy on the webapp.') param containerImageName string = 'byc-wa-app' @@ -162,7 +165,8 @@ var azureSearchEnableInDomain = 'False' // Set to 'True' if you want to enable i var azureCosmosDbEnableFeedback = 'True' var useInternalStream = 'True' var useAIProjectClientFlag = 'False' -var sqlServerFqdn = '${sqlDBModule.outputs.name}.database.windows.net' +// var sqlServerFqdn = '${sqlDBModule.outputs.name}.database.windows.net' +var sqlServerFqdn = 'sql-${solutionSuffix}.database.windows.net' @description('Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true.') param vmSize string? @@ -265,6 +269,17 @@ var resourcesName = toLower(trim(replace( // Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] +// Extracts subscription, resource group, and workspace name from the resource ID when using an existing Log Analytics workspace +var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) +var existingLawSubscription = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[2] : '' +var existingLawResourceGroup = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[4] : '' +var existingLawName = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[8] : '' + +resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = if (useExistingLogAnalytics) { + name: existingLawName + scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) +} + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -276,6 +291,96 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { } } +// ========== Log Analytics Workspace ========== // +// WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics +// WAF PSRules for Log Analytics: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#azure-monitor-logs +var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}' +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring) { + name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) + params: { + name: logAnalyticsWorkspaceResourceName + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + skuName: 'PerGB2018' + dataRetention: 365 + features: { enableLogAccessUsingOnlyResourcePermissions: true } + diagnosticSettings: [{ useThisWorkspace: true }] + // WAF aligned configuration for Redundancy + dailyQuotaGb: enableRedundancy ? 10 : null //WAF recommendation: 10 GB per day is a good starting point for most workloads + replication: enableRedundancy + ? { + enabled: true + location: replicaLocation + } + : null + // WAF aligned configuration for Private Networking + publicNetworkAccessForIngestion: enablePrivateNetworking ? 'Disabled' : 'Enabled' + publicNetworkAccessForQuery: enablePrivateNetworking ? 'Disabled' : 'Enabled' + dataSources: enablePrivateNetworking + ? [ + { + tags: tags + eventLogName: 'Application' + eventTypes: [ + { + eventType: 'Error' + } + { + eventType: 'Warning' + } + { + eventType: 'Information' + } + ] + kind: 'WindowsEvent' + name: 'applicationEvent' + } + { + counterName: '% Processor Time' + instanceName: '*' + intervalSeconds: 60 + kind: 'WindowsPerformanceCounter' + name: 'windowsPerfCounter1' + objectName: 'Processor' + } + { + kind: 'IISLogs' + name: 'sampleIISLog1' + state: 'OnPremiseEnabled' + } + ] + : null + } +} + +// Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) +// var logAnalyticsWorkspaceName = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.name : logAnalyticsWorkspace!.outputs.name +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace!.outputs.resourceId +// var logAnalyticsPrimarySharedKey = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey +// var logAnalyticsWorkspaceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId + +// ========== Application Insights ========== // +// WAF best practices for Application Insights: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/application-insights +// WAF PSRules for Application Insights: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#application-insights +var applicationInsightsResourceName = 'appi-${solutionSuffix}' +module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (enableMonitoring) { + name: take('avm.res.insights.component.${applicationInsightsResourceName}', 64) + params: { + name: applicationInsightsResourceName + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + retentionInDays: 365 + kind: 'web' + disableIpMasking: false + flowType: 'Bluefield' + // WAF aligned configuration for Monitoring + workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : '' + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + } +} + // ========== User Assigned Identity ========== // // WAF best practices for identity and access management: https://learn.microsoft.com/en-us/azure/well-architected/security/identity-access var userAssignedIdentityResourceName = 'id-${solutionSuffix}' @@ -285,7 +390,7 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id name: userAssignedIdentityResourceName location: solutionLocation tags: tags - // enableTelemetry: enableTelemetry + enableTelemetry: enableTelemetry } } @@ -306,7 +411,8 @@ module network 'modules/network.bicep' = if (enablePrivateNetworking) { name: take('network-${resourcesName}-deployment', 64) params: { resourcesName: resourcesName - logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId + // logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId + logAnalyticsWorkSpaceResourceId: useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : resourceId('Microsoft.OperationalInsights/workspaces', logAnalyticsWorkspaceResourceName) vmAdminUsername: vmAdminUsername ?? 'JumpboxAdminUser' vmAdminPassword: vmAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!' vmSize: vmSize ?? 'Standard_DS2_v2' // Default VM size @@ -327,7 +433,8 @@ var privateDnsZones = [ 'privatelink.file.${environment().suffixes.storage}' 'privatelink.documents.azure.com' 'privatelink.vaultcore.azure.net' - 'privatelink.${environment().suffixes.sqlServerHostname}' + 'privatelink${environment().suffixes.sqlServerHostname}' + 'privatelink.search.windows.net' ] // DNS Zone Index Constants @@ -342,6 +449,7 @@ var dnsZoneIndex = { cosmosDB: 7 keyVault: 8 sqlServer: 9 + searchService: 10 } // List of DNS zone indices that correspond to AI-related services. @@ -351,6 +459,7 @@ var aiRelatedDnsZoneIndices = [ dnsZoneIndex.aiServices ] + // =================================================== // DEPLOY PRIVATE DNS ZONES // - Deploys all zones if no existing Foundry project is used @@ -359,7 +468,7 @@ var aiRelatedDnsZoneIndices = [ @batchSize(5) module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ for (zone, i) in privateDnsZones: if (enablePrivateNetworking && (empty(existingFoundryProjectResourceId) || !contains(aiRelatedDnsZoneIndices, i))) { - name: 'avm.res.network.private-dns-zone.${contains(zone, 'azurecontainerapps.io') ? 'containerappenv' : split(zone, '.')[1]}' + name: 'dns-zone-${i}' params: { name: zone tags: tags @@ -367,124 +476,14 @@ module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ virtualNetworkLinks: [ { name: take('vnetlink-${network!.outputs.vnetName}-${split(zone, '.')[1]}', 80) - virtualNetworkResourceId: network!.outputs.subnetPrivateEndpointsResourceId + virtualNetworkResourceId: network!.outputs.vnetResourceId } ] } } ] -// Extracts subscription, resource group, and workspace name from the resource ID when using an existing Log Analytics workspace -var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) - -var existingLawSubscription = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[2] : '' -var existingLawResourceGroup = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[4] : '' -var existingLawName = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[8] : '' - -resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = if (useExistingLogAnalytics) { - name: existingLawName - scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) -} - -// ========== Log Analytics Workspace ========== // -// WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics -// WAF PSRules for Log Analytics: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#azure-monitor-logs -var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}' -module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring) { - name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) - params: { - name: logAnalyticsWorkspaceResourceName - tags: tags - location: solutionLocation - enableTelemetry: enableTelemetry - skuName: 'PerGB2018' - dataRetention: 365 - features: { enableLogAccessUsingOnlyResourcePermissions: true } - diagnosticSettings: [{ useThisWorkspace: true }] - // WAF aligned configuration for Redundancy - dailyQuotaGb: enableRedundancy ? 10 : null //WAF recommendation: 10 GB per day is a good starting point for most workloads - replication: enableRedundancy - ? { - enabled: true - location: replicaLocation - } - : null - // WAF aligned configuration for Private Networking - publicNetworkAccessForIngestion: enablePrivateNetworking ? 'Disabled' : 'Enabled' - publicNetworkAccessForQuery: enablePrivateNetworking ? 'Disabled' : 'Enabled' - dataSources: enablePrivateNetworking - ? [ - { - tags: tags - eventLogName: 'Application' - eventTypes: [ - { - eventType: 'Error' - } - { - eventType: 'Warning' - } - { - eventType: 'Information' - } - ] - kind: 'WindowsEvent' - name: 'applicationEvent' - } - { - counterName: '% Processor Time' - instanceName: '*' - intervalSeconds: 60 - kind: 'WindowsPerformanceCounter' - name: 'windowsPerfCounter1' - objectName: 'Processor' - } - { - kind: 'IISLogs' - name: 'sampleIISLog1' - state: 'OnPremiseEnabled' - } - ] - : null - } -} - -// Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) -// var logAnalyticsWorkspaceName = useExistingLogAnalytics -// ? existingLogAnalyticsWorkspace!.name -// : logAnalyticsWorkspace!.outputs.name -var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics - ? existingLogAnalyticsWorkspaceId - : logAnalyticsWorkspace!.outputs.resourceId -// var logAnalyticsPrimarySharedKey = useExistingLogAnalytics -// ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey -// : logAnalyticsWorkspace.outputs.primarySharedKey -// var logAnalyticsWorkspaceId = useExistingLogAnalytics -// ? existingLogAnalyticsWorkspace!.properties.customerId -// : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId - -// ========== Application Insights ========== // -// WAF best practices for Application Insights: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/application-insights -// WAF PSRules for Application Insights: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#application-insights -var applicationInsightsResourceName = 'appi-${solutionSuffix}' -module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (enableMonitoring) { - name: take('avm.res.insights.component.${applicationInsightsResourceName}', 64) - params: { - name: applicationInsightsResourceName - tags: tags - location: solutionLocation - enableTelemetry: enableTelemetry - retentionInDays: 365 - kind: 'web' - disableIpMasking: false - flowType: 'Bluefield' - // WAF aligned configuration for Monitoring - workspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : '' - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null - } -} - -// Key Vault resource +// ==========Key Vault Module ========== // var keyVaultName = 'KV-${solutionSuffix}' module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { name: take('avm.res.key-vault.vault.${keyVaultName}', 64) @@ -492,7 +491,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { name: keyVaultName location: solutionLocation tags: tags - sku: 'premium' + sku: 'standard' publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' networkAcls: { defaultAction: 'Allow' @@ -502,6 +501,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { enableVaultForTemplateDeployment: true enableRbacAuthorization: true enableSoftDelete: true + enablePurgeProtection: enablePurgeProtection softDeleteRetentionInDays: 7 diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : [] // WAF aligned configuration for Private Networking @@ -529,12 +529,36 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { } ] secrets: [ - { - name: 'ExampleSecret' - value: 'YourSecretValue' - } + { + name: 'SQLDB-SERVER' + value: sqlServerFqdn + } + { + name: 'SQLDB-DATABASE' + value: sqlDbName + } + { + name: 'AZURE-OPENAI-PREVIEW-API-VERSION' + value: azureOpenaiAPIVersion + } + { + name: 'AZURE-OPENAI-ENDPOINT' + value: aiFoundryAiServices.outputs.endpoint + } + { + name: 'AZURE-OPENAI-EMBEDDING-MODEL' + value: embeddingModel + } + { + name: 'AZURE-SEARCH-INDEX' + value: azureSearchIndex + } + { + name: 'AZURE-SEARCH-ENDPOINT' + value: 'https://${aiSearchName}.search.windows.net' + } ] - // enableTelemetry: enableTelemetry + enableTelemetry: enableTelemetry } } @@ -562,6 +586,21 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { // ========== AI Foundry: AI Services ========== // // WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai +// var aiFoundryAiServicesSubscriptionId = useExistingAiFoundryAiProject +// ? split(azureExistingAIProjectResourceId, '/')[2] +// : subscription().id +// var useExistingAiFoundryAiProject = !empty(azureExistingAIProjectResourceId) +// var aiFoundryAiServicesResourceGroupName = useExistingAiFoundryAiProject +// ? split(azureExistingAIProjectResourceId, '/')[4] +// : 'rg-${solutionSuffix}' +// var aiFoundryAiServicesResourceName = useExistingAiFoundryAiProject +// ? split(azureExistingAIProjectResourceId, '/')[8] +// : 'aif-${solutionSuffix}' +// var aiFoundryAiProjectResourceName = useExistingAiFoundryAiProject +// ? split(azureExistingAIProjectResourceId, '/')[10] +// : 'proj-${solutionSuffix}' +// AI Project resource id: /subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//projects/ + // NOTE: Required version 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' not available in AVM var aiFoundryAiServicesResourceName = 'aif-${solutionSuffix}' var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' @@ -874,6 +913,8 @@ module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { { name: 'data' publicAccess: 'None' + denyEncryptionScopeOverride: false + defaultEncryptionScope: '$account-encryption-key' } ] } @@ -920,6 +961,8 @@ module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0. } } +// ========== AVM WAF ========== // +// ========== SQL module ========== // var sqlDbName = 'sqldb-${solutionSuffix}' module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { name: 'serverDeployment' @@ -943,7 +986,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { // } databases: [ { - availabilityZone: enableRedundancy? 1 : -1 + availabilityZone: 1 backupLongTermRetentionPolicy: { monthlyRetention: 'P6M' } @@ -954,15 +997,51 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + elasticPoolResourceId: resourceId('Microsoft.Sql/servers/elasticPools', 'sql-${solutionSuffix}', 'sqlswaf-ep-001') licenseType: 'LicenseIncluded' maxSizeBytes: 34359738368 name: 'sqldb-${solutionSuffix}' sku: { - name: 'GP_S_Gen5' + capacity: 0 + name: 'ElasticPool' + tier: 'GeneralPurpose' + } + } + ] + elasticPools: [ + { + availabilityZone: -1 + //maintenanceConfigurationId: '' + name: 'sqlswaf-ep-001' + sku: { + capacity: 10 + name: 'GP_Gen5' tier: 'GeneralPurpose' - family: 'Gen5' - capacity: 2 } + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'db_datareader' + } + { + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'db_datawriter' + } + + //Enable if above access is not sufficient for your use case + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'SQL DB Contributor' + // } + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'SQL Server Contributor' + // } + ] } ] firewallRules: [ @@ -1129,6 +1208,7 @@ module webSite 'modules/web-sites.bicep' = { name: webSiteResourceName tags: tags location: solutionLocation + managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] } kind: 'app,linux,container' serverFarmResourceId: webServerFarm.?outputs.resourceId siteConfig: { @@ -1142,7 +1222,8 @@ module webSite 'modules/web-sites.bicep' = { APP_ENV: appEnvironment APPINSIGHTS_INSTRUMENTATIONKEY: enableMonitoring ? applicationInsights!.outputs.instrumentationKey : '' APPLICATIONINSIGHTS_CONNECTION_STRING: enableMonitoring ? applicationInsights!.outputs.connectionString : '' - AZURE_SEARCH_SERVICE: ''//azureSearchService + // AZURE_SEARCH_SERVICE: ''//azureSearchService + AZURE_SEARCH_SERVICE: aiSearchName AZURE_SEARCH_INDEX: azureSearchIndex AZURE_SEARCH_USE_SEMANTIC_SEARCH: azureSearchUseSemanticSearch AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG: azureSearchSemanticSearchConfig @@ -1154,7 +1235,7 @@ module webSite 'modules/web-sites.bicep' = { AZURE_SEARCH_URL_COLUMN: azureSearchUrlColumn AZURE_OPENAI_RESOURCE: aiFoundryAiServices.outputs.name AZURE_OPENAI_MODEL: gptModelName - AZURE_OPENAI_ENDPOINT: aiFoundryAiServices.outputs.endpoint + AZURE_OPENAI_ENDPOINT: aiFoundryAiServices.outputs.endpoints['OpenAI Language Model Instance API'] AZURE_OPENAI_TEMPERATURE: azureOpenAITemperature AZURE_OPENAI_TOP_P: azureOpenAITopP AZURE_OPENAI_MAX_TOKENS: azureOpenAIMaxTokens @@ -1167,7 +1248,8 @@ module webSite 'modules/web-sites.bicep' = { AZURE_SEARCH_PERMITTED_GROUPS_COLUMN: azureSearchPermittedGroupsField AZURE_SEARCH_STRICTNESS: azureSearchStrictness AZURE_OPENAI_EMBEDDING_NAME: embeddingModel - AZURE_OPENAI_EMBEDDING_ENDPOINT: aiFoundryAiServices.outputs.endpoint + // AZURE_OPENAI_EMBEDDING_ENDPOINT: aiFoundryAiServices.outputs.endpoint + AZURE_OPENAI_EMBEDDING_ENDPOINT : aiFoundryAiServices.outputs.endpoints['OpenAI Language Model Instance API'] SQLDB_SERVER: sqlServerFqdn SQLDB_DATABASE: sqlDbName USE_INTERNAL_STREAM: useInternalStream @@ -1176,7 +1258,8 @@ module webSite 'modules/web-sites.bicep' = { AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback SQLDB_USER_MID: userAssignedIdentity.outputs.clientId - AZURE_AI_SEARCH_ENDPOINT: '' //azureSearchServiceEndpoint + // AZURE_AI_SEARCH_ENDPOINT: '' //azureSearchServiceEndpoint + AZURE_AI_SEARCH_ENDPOINT: 'https://${aiSearchName}.search.windows.net' AZURE_SQL_SYSTEM_PROMPT: functionAppSqlPrompt AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT: functionAppCallTranscriptSystemPrompt AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT: functionAppStreamTextSystemPrompt @@ -1184,7 +1267,9 @@ module webSite 'modules/web-sites.bicep' = { AZURE_AI_AGENT_ENDPOINT: aiFoundryAiServices.outputs.aiProjectInfo.apiEndpoint AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME: gptModelName AZURE_AI_AGENT_API_VERSION: azureOpenaiAPIVersion - AZURE_SEARCH_CONNECTION_NAME: '' //aiSearchProjectConnectionName + // AZURE_SEARCH_CONNECTION_NAME: '' //aiSearchProjectConnectionName + AZURE_SEARCH_CONNECTION_NAME: 'foundry-search-connection-${solutionName}' + AZURE_CLIENT_ID: userAssignedIdentity.outputs.clientId } // WAF aligned configuration for Monitoring applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null @@ -1207,13 +1292,100 @@ module webSite 'modules/web-sites.bicep' = { ] } service: 'sites' - subnetResourceId: network!.outputs.subnetWebResourceId + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId } ] : null } } +var aiSearchName = 'srch-${solutionName}' +module searchService 'br/public:avm/res/search/search-service:0.11.1' = { + name: 'searchServiceDeployment' + params: { + // Required parameters + name: aiSearchName + // Authentication options + authOptions: { + aadOrApiKey: { + aadAuthFailureMode: 'http401WithBearerChallenge' + } + } + // Customer-managed key enforcement (optional) + // cmkEnforcement: 'Enabled' + // Wire up diagnostic settings to the Log Analytics workspace when monitoring is enabled + diagnosticSettings: enableMonitoring ? [ + { + workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId + } + ] : null + disableLocalAuth: false + hostingMode: 'default' + managedIdentities: { + systemAssigned: true + } + networkRuleSet: { + bypass: 'AzureServices' + ipRules: [] + } + roleAssignments: [ + // { + // roleDefinitionIdOrName: 'Cognitive Services Contributor' // Cognitive Search Contributor + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // } + // { + // roleDefinitionIdOrName: 'Cognitive Services OpenAI User'//'5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'// Cognitive Services OpenAI User + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // } + { + roleDefinitionIdOrName: 'Search Index Data Contributor' // 1407120a-92aa-4202-b7e9-c0e197c71c8f + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + ] + partitionCount: 1 + replicaCount: 1 + sku: 'standard' + semanticSearch: 'free' + // Use the deployment tags provided to the template + tags: tags + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-${aiSearchName}' + customNetworkInterfaceName: 'nic-${aiSearchName}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.searchService]!.outputs.resourceId } + ] + } + service: 'searchService' + subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId + } + ] + : [] + } +} + +resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { + name: '${aiFoundryAiServicesResourceName}/${aiFoundryAiServicesAiProjectResourceName}/${aiSearchName}' + properties: { + category: 'CognitiveSearch' + target: 'https://${aiSearchName}.search.windows.net' + authType: 'AAD' + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: searchService.outputs.resourceId + location: searchService.outputs.location + } + } +} + + @description('URL of the deployed web application.') output WEB_APP_URL string = 'https://${webSite.outputs.name}.azurewebsites.net' @@ -1247,7 +1419,8 @@ output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name @description('Client ID of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId @description('Name of the AI Search service.') -output AI_SEARCH_SERVICE_NAME string = '' //aifoundry.outputs.aiSearchService +// output AI_SEARCH_SERVICE_NAME string = '' //aifoundry.outputs.aiSearchService +output AI_SEARCH_SERVICE_NAME string = aiSearchName //aifoundry.outputs.aiSearchService @description('Name of the deployed web application.') output WEB_APP_NAME string = webSite.outputs.name @@ -1272,7 +1445,8 @@ output AZURE_AI_AGENT_ENDPOINT string = aiFoundryAiServices.outputs.aiProjectInf output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName @description('The endpoint URL of the Azure AI Search service.') -output AZURE_AI_SEARCH_ENDPOINT string = '' //aifoundry.outputs.aiSearchTarget +// output AZURE_AI_SEARCH_ENDPOINT string = '' //aifoundry.outputs.aiSearchTarget +output AZURE_AI_SEARCH_ENDPOINT string = 'https://${aiSearchName}.search.windows.net' //aifoundry.outputs.aiSearchTarget @description('The system prompt used for call transcript processing in Azure Functions.') output AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT string = functionAppCallTranscriptSystemPrompt @@ -1290,13 +1464,13 @@ output AZURE_COSMOSDB_DATABASE string = cosmosDbDatabaseName output AZURE_COSMOSDB_ENABLE_FEEDBACK string = azureCosmosDbEnableFeedback @description('The endpoint URL for the Azure OpenAI Embedding model.') -output AZURE_OPENAI_EMBEDDING_ENDPOINT string = aiFoundryAiServices.outputs.aiProjectInfo.apiEndpoint +output AZURE_OPENAI_EMBEDDING_ENDPOINT string = aiFoundryAiServices.outputs.endpoints['OpenAI Language Model Instance API'] @description('The name of the Azure OpenAI Embedding model.') output AZURE_OPENAI_EMBEDDING_NAME string = embeddingModel @description('The endpoint URL for the Azure OpenAI service.') -output AZURE_OPENAI_ENDPOINT string = aiFoundryAiServices.outputs.endpoint +output AZURE_OPENAI_ENDPOINT string = aiFoundryAiServices.outputs.endpoints['OpenAI Language Model Instance API'] @description('The maximum number of tokens for Azure OpenAI responses.') output AZURE_OPENAI_MAX_TOKENS string = azureOpenAIMaxTokens @@ -1329,7 +1503,8 @@ output AZURE_OPENAI_TEMPERATURE string = azureOpenAITemperature output AZURE_OPENAI_TOP_P string = azureOpenAITopP @description('The name of the Azure AI Search connection.') -output AZURE_SEARCH_CONNECTION_NAME string = '' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName +// output AZURE_SEARCH_CONNECTION_NAME string = '' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName +output AZURE_SEARCH_CONNECTION_NAME string = 'foundry-search-connection-${solutionSuffix}' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName @description('The columns in Azure AI Search that contain content.') output AZURE_SEARCH_CONTENT_COLUMNS string = azureSearchContentColumns @@ -1353,7 +1528,8 @@ output AZURE_SEARCH_QUERY_TYPE string = azureSearchQueryType output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = azureSearchSemanticSearchConfig @description('The name of the Azure AI Search service.') -output AZURE_SEARCH_SERVICE string = '' //aifoundry.outputs.aiSearchService +// output AZURE_SEARCH_SERVICE string = '' //aifoundry.outputs.aiSearchService +output AZURE_SEARCH_SERVICE string = aiSearchName //aifoundry.outputs.aiSearchService @description('The strictness setting for Azure AI Search semantic ranking.') output AZURE_SEARCH_STRICTNESS string = azureSearchStrictness diff --git a/infra/main.json b/infra/main.json index 5ec0bea04..d5e1a5614 100644 --- a/infra/main.json +++ b/infra/main.json @@ -1,11 +1,12 @@ { "$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.37.4.10188", - "templateHash": "4697605839482939008" + "templateHash": "958310235447252556" } }, "parameters": { @@ -25,13 +26,6 @@ "description": "Optional. Existing Log Analytics Workspace Resource ID" } }, - "azureExistingAIProjectResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Use this parameter to use an existing AI project resource ID" - } - }, "cosmosLocation": { "type": "string", "defaultValue": "eastus2", @@ -39,7 +33,7 @@ "description": "Optional. CosmosDB Location" } }, - "deploymentType": { + "gptModelDeploymentType": { "type": "string", "defaultValue": "GlobalStandard", "allowedValues": [ @@ -62,6 +56,20 @@ "description": "Optional. Name of the GPT model to deploy:" } }, + "gptModelVersion": { + "type": "string", + "defaultValue": "2024-07-18", + "metadata": { + "description": "Optional. Version of the GPT model to deploy." + } + }, + "embeddingModelVersion": { + "type": "string", + "defaultValue": "2", + "metadata": { + "description": "Optional. Version of the GPT model to deploy." + } + }, "azureOpenaiAPIVersion": { "type": "string", "defaultValue": "2025-04-01-preview", @@ -69,7 +77,7 @@ "description": "Optional. API version for the Azure OpenAI service." } }, - "gptDeploymentCapacity": { + "gptModelCapacity": { "type": "int", "defaultValue": 200, "minValue": 10, @@ -96,14 +104,7 @@ "description": "Optional. Capacity of the Embedding Model deployment" } }, - "imageTag": { - "type": "string", - "defaultValue": "latest", - "metadata": { - "description": "The Docker image tag to use for the application deployment." - } - }, - "aiDeploymentsLocation": { + "azureAiServiceLocation": { "type": "string", "allowedValues": [ "australiaeast", @@ -124,7 +125,7 @@ "OpenAI.GlobalStandard.text-embedding-ada-002,80" ] }, - "description": "Rquired. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." + "description": "Required. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." } }, "AZURE_LOCATION": { @@ -142,6 +143,97 @@ "description": "Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name." } }, + "enablePrivateNetworking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable private networking for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false." + } + }, + "enableMonitoring": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false." + } + }, + "enableScalability": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "enableRedundancy": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable redundancy for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false." + } + }, + "containerRegistryHostname": { + "type": "string", + "defaultValue": "biabcontainerreg.azurecr.io", + "metadata": { + "description": "Optional. The Container Registry hostname where the docker images for the frontend are located." + } + }, + "containerImageName": { + "type": "string", + "defaultValue": "byc-wa-app", + "metadata": { + "description": "Optional. The Container Image Name to deploy on the webapp." + } + }, + "containerImageTag": { + "type": "string", + "defaultValue": "latest", + "metadata": { + "description": "Optional. The Container Image Tag to deploy on the webapp." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of an existing Foundry project" + } + }, + "enablePurgeProtection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable purge protection for the Key Vault" + } + }, + "vmSize": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true." + } + }, + "vmAdminUsername": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. Admin username for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true." + } + }, + "vmAdminPassword": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. Admin password for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true." + } + }, "tags": { "type": "object", "metadata": { @@ -156,8 +248,6 @@ "variables": { "solutionLocation": "[if(empty(parameters('AZURE_LOCATION')), resourceGroup().location, parameters('AZURE_LOCATION'))]", "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", - "hostingPlanName": "[format('asp-{0}', variables('solutionSuffix'))]", - "websiteName": "[format('app-{0}', variables('solutionSuffix'))]", "appEnvironment": "Prod", "azureSearchIndex": "transcripts_index", "azureSearchUseSemanticSearch": "True", @@ -181,13 +271,108 @@ "azureCosmosDbEnableFeedback": "True", "useInternalStream": "True", "useAIProjectClientFlag": "False", - "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n Only return the generated SQL query. Do not return anything else.", - "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", - "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'.", - "aiFoundryAiServicesAiProjectResourceName": "[format('proj-{0}', variables('solutionSuffix'))]" + "sqlServerFqdn": "[format('sql-{0}.database.windows.net', variables('solutionSuffix'))]", + "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n Only return the generated SQL query. Do not return anything else.", + "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \n You have access to the client’s past meeting call transcripts. \n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \n If no data is available, state 'No relevant data found for previous meetings.", + "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\n Always send clientId as '{client_id}'.", + "replicaRegionPairs": { + "australiaeast": "australiasoutheast", + "centralus": "westus", + "eastasia": "japaneast", + "eastus": "centralus", + "eastus2": "centralus", + "japaneast": "eastasia", + "northeurope": "westeurope", + "southeastasia": "eastasia", + "uksouth": "westeurope", + "westeurope": "northeurope" + }, + "replicaLocation": "[variables('replicaRegionPairs')[resourceGroup().location]]", + "cosmosDbZoneRedundantHaRegionPairs": { + "australiaeast": "uksouth", + "centralus": "eastus2", + "eastasia": "southeastasia", + "eastus": "centralus", + "eastus2": "centralus", + "japaneast": "australiaeast", + "northeurope": "westeurope", + "southeastasia": "eastasia", + "uksouth": "westeurope", + "westeurope": "northeurope" + }, + "allTags": "[union(createObject('azd-env-name', parameters('solutionName')), parameters('tags'))]", + "resourcesName": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", + "cosmosDbHaLocation": "[variables('cosmosDbZoneRedundantHaRegionPairs')[resourceGroup().location]]", + "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]", + "privateDnsZones": [ + "privatelink.cognitiveservices.azure.com", + "privatelink.openai.azure.com", + "privatelink.services.ai.azure.com", + "privatelink.azurewebsites.net", + "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "[format('privatelink.queue.{0}', environment().suffixes.storage)]", + "[format('privatelink.file.{0}', environment().suffixes.storage)]", + "privatelink.documents.azure.com", + "privatelink.vaultcore.azure.net", + "[format('privatelink.{0}', environment().suffixes.sqlServerHostname)]" + ], + "dnsZoneIndex": { + "cognitiveServices": 0, + "openAI": 1, + "aiServices": 2, + "appService": 3, + "storageBlob": 4, + "storageQueue": 5, + "storageFile": 6, + "cosmosDB": 7, + "keyVault": 8, + "sqlServer": 9 + }, + "aiRelatedDnsZoneIndices": [ + "[variables('dnsZoneIndex').cognitiveServices]", + "[variables('dnsZoneIndex').openAI]", + "[variables('dnsZoneIndex').aiServices]" + ], + "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", + "existingLawSubscription": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[2], '')]", + "existingLawResourceGroup": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[4], '')]", + "existingLawName": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[8], '')]", + "logAnalyticsWorkspaceResourceName": "[format('log-{0}', variables('solutionSuffix'))]", + "applicationInsightsResourceName": "[format('appi-{0}', variables('solutionSuffix'))]", + "keyVaultName": "[format('KV-{0}', variables('solutionSuffix'))]", + "aiFoundryAiServicesResourceName": "[format('aif-{0}', variables('solutionSuffix'))]", + "aiFoundryAiServicesAiProjectResourceName": "[format('proj-{0}', variables('solutionSuffix'))]", + "aiFoundryAIservicesEnabled": true, + "aiFoundryAiServicesModelDeployment": { + "format": "OpenAI", + "name": "[parameters('gptModelName')]", + "version": "[parameters('gptModelVersion')]", + "sku": { + "name": "[parameters('gptModelDeploymentType')]", + "capacity": "[parameters('gptModelCapacity')]" + }, + "raiPolicyName": "Microsoft.Default" + }, + "aiFoundryAiServicesEmbeddingModel": { + "name": "[parameters('embeddingModel')]", + "version": "[parameters('embeddingModelVersion')]", + "sku": { + "name": "GlobalStandard", + "capacity": "[parameters('embeddingDeploymentCapacity')]" + }, + "raiPolicyName": "Microsoft.Default" + }, + "cosmosDbResourceName": "[format('cosmos-{0}', variables('solutionSuffix'))]", + "cosmosDbDatabaseName": "db_conversation_history", + "collectionName": "conversations", + "storageAccountName": "[format('st{0}', variables('solutionSuffix'))]", + "sqlDbName": "[format('sqldb-{0}', variables('solutionSuffix'))]", + "webServerFarmResourceName": "[format('asp-{0}', variables('solutionSuffix'))]", + "webSiteResourceName": "[format('app-{0}', variables('solutionSuffix'))]", + "aiSearchName": "[format('srch-{0}', parameters('solutionName'))]" }, - "resources": [ - { + "resources": { + "resourceGroupTags": { "type": "Microsoft.Resources/tags", "apiVersion": "2021-04-01", "name": "default", @@ -195,397 +380,528 @@ "tags": "[shallowMerge(createArray(parameters('tags'), createObject('TemplateName', 'Client Advisor')))]" } }, - { + "existingLogAnalyticsWorkspace": { + "condition": "[variables('useExistingLogAnalytics')]", + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2020-08-01", + "subscriptionId": "[variables('existingLawSubscription')]", + "resourceGroup": "[variables('existingLawResourceGroup')]", + "name": "[variables('existingLawName')]" + }, + "userAssignedIdentity": { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "deploy_managed_identity", - "resourceGroup": "[resourceGroup().name]", + "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('userAssignedIdentityResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "solutionName": { - "value": "[variables('solutionSuffix')]" + "name": { + "value": "[variables('userAssignedIdentityResourceName')]" }, - "solutionLocation": { + "location": { "value": "[variables('solutionLocation')]" }, - "miName": { - "value": "[format('id-{0}', variables('solutionSuffix'))]" - }, "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.37.4.10188", - "templateHash": "6639330323573416008" - } - }, - "parameters": { - "solutionName": { - "type": "string", - "minLength": 3, - "maxLength": 15, - "metadata": { - "description": "Required. Name of the solution." - } - }, - "solutionLocation": { - "type": "string", - "metadata": { - "description": "Required. Deployment location for the solution." - } - }, - "miName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity." - } + "version": "0.34.44.8038", + "templateHash": "16707109626832623586" }, - "tags": { + "name": "User Assigned Identities", + "description": "This module deploys a User Assigned Identity." + }, + "definitions": { + "federatedIdentityCredentialType": { "type": "object", - "defaultValue": {}, + "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." + } + } + }, "metadata": { - "description": "Optional. Tags to be applied to the resources." + "__bicep_export!": true, + "description": "The type for the federated identity credential." } - } - }, - "resources": [ - { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('miName')]", - "location": "[parameters('solutionLocation')]", - "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]" }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceGroup().id, resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'))]", + "lockType": { + "type": "object", "properties": { - "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').principalId]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", - "principalType": "ServicePrincipal" + "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." + } + } }, - "dependsOn": [ - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]" - ] - }, - { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}-webapp', parameters('miName'))]", - "location": "[parameters('solutionLocation')]", - "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]" - } - ], - "outputs": { - "managedIdentityOutput": { - "type": "object", "metadata": { - "description": "Details of the managed identity resource." - }, - "value": { - "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]", - "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').principalId]", - "clientId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').clientId]", - "name": "[parameters('miName')]" + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "managedIdentityWebAppOutput": { + "roleAssignmentType": { "type": "object", - "metadata": { - "description": "Details of the managed identity for the web app." + "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." + } + } }, - "value": { - "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName')))]", - "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName'))), '2023-01-31').principalId]", - "clientId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-webapp', parameters('miName'))), '2023-01-31').clientId]", - "name": "[format('{0}-webapp', parameters('miName'))]" + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploy_keyvault", - "resourceGroup": "[resourceGroup().name]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "solutionName": { - "value": "[variables('solutionSuffix')]" - }, - "solutionLocation": { - "value": "[variables('solutionLocation')]" - }, - "managedIdentityObjectId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" - }, - "kvName": { - "value": "[format('kv-{0}', variables('solutionSuffix'))]" - }, - "tags": { - "value": "[parameters('tags')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "12303448646842336311" - } }, "parameters": { - "solutionName": { + "name": { "type": "string", "metadata": { - "description": "Required. Solution Name" + "description": "Required. Name of the User Assigned Identity." } }, - "solutionLocation": { + "location": { "type": "string", + "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "Required. Solution Location" + "description": "Optional. Location for all resources." } }, - "utc": { - "type": "string", - "defaultValue": "[utcNow()]", + "federatedIdentityCredentials": { + "type": "array", + "items": { + "$ref": "#/definitions/federatedIdentityCredentialType" + }, + "nullable": true, "metadata": { - "description": "Optional. Current UTC timestamp." + "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." } }, - "kvName": { - "type": "string", + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, "metadata": { - "description": "Required. Name of the Azure Key Vault." + "description": "Optional. The lock settings of the service." } }, - "createMode": { - "type": "string", - "defaultValue": "default", + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, "metadata": { - "description": "Optional. Specifies the create mode for the resource." + "description": "Optional. Array of role assignments to create." } }, - "enableForDeployment": { - "type": "bool", - "defaultValue": true, + "tags": { + "type": "object", + "nullable": true, "metadata": { - "description": "Optional. Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault." + "description": "Optional. Tags of the resource." } }, - "enableForDiskEncryption": { + "enableTelemetry": { "type": "bool", "defaultValue": true, "metadata": { - "description": "Optional. Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys." + "description": "Optional. Enable/Disable usage telemetry for module." } - }, - "enableForTemplateDeployment": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault." + } + }, + "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)))))]" } - }, - "enableRBACAuthorization": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable RBAC Authorization. Property that controls how data actions are authorized." + ], + "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": "[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.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" + } + } + } } }, - "softDeleteRetentionInDays": { - "type": "int", - "defaultValue": 7, - "metadata": { - "description": "Optional. Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90." - } + "userAssignedIdentity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2024-11-30", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" }, - "publicNetworkAccess": { - "type": "string", - "defaultValue": "enabled", - "allowedValues": [ - "enabled", - "disabled" - ], - "metadata": { - "description": "Optional. Public Network Access, Property to specify whether the vault will accept traffic from public internet." - } + "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" + ] }, - "sku": { - "type": "string", - "defaultValue": "standard", - "allowedValues": [ - "standard", - "premium" - ], + "userAssignedIdentity_roleAssignments": { + "copy": { + "name": "userAssignedIdentity_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 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": [ + "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-FederatedIdentityCred-{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.34.44.8038", + "templateHash": "13656021764446440473" + }, + "name": "User Assigned Identity Federated Identity Credential", + "description": "This module deploys a User Assigned Identity Federated Identity Credential." + }, + "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": "2024-11-30", + "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": "Optional. SKU" - } + "description": "The name of the user assigned identity." + }, + "value": "[parameters('name')]" }, - "managedIdentityObjectId": { + "resourceId": { "type": "string", "metadata": { - "description": "Required. Object ID of the managed identity." - } + "description": "The resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" }, - "tags": { - "type": "object", - "defaultValue": {}, + "principalId": { + "type": "string", "metadata": { - "description": "Optional. Tags to be applied to the resources." - } - } - }, - "variables": { - "vaultUri": "[format('https://{0}.vault.azure.net/', parameters('kvName'))]" - }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2022-07-01", - "name": "[parameters('kvName')]", - "location": "[parameters('solutionLocation')]", - "tags": "[shallowMerge(createArray(parameters('tags'), createObject('app', parameters('solutionName'), 'location', parameters('solutionLocation'))))]", - "properties": { - "accessPolicies": [ - { - "objectId": "[parameters('managedIdentityObjectId')]", - "permissions": { - "certificates": [ - "all" - ], - "keys": [ - "all" - ], - "secrets": [ - "all" - ], - "storage": [ - "all" - ] - }, - "tenantId": "[subscription().tenantId]" - } - ], - "createMode": "[parameters('createMode')]", - "enabledForDeployment": "[parameters('enableForDeployment')]", - "enabledForDiskEncryption": "[parameters('enableForDiskEncryption')]", - "enabledForTemplateDeployment": "[parameters('enableForTemplateDeployment')]", - "enableRbacAuthorization": "[parameters('enableRBACAuthorization')]", - "softDeleteRetentionInDays": "[parameters('softDeleteRetentionInDays')]", - "provisioningState": "RegisteringDns", - "publicNetworkAccess": "[parameters('publicNetworkAccess')]", - "sku": { - "family": "A", - "name": "[parameters('sku')]" - }, - "tenantId": "[subscription().tenantId]", - "vaultUri": "[variables('vaultUri')]" - } + "description": "The principal ID (object ID) of the user assigned identity." + }, + "value": "[reference('userAssignedIdentity').principalId]" }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceGroup().id, parameters('managedIdentityObjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483'))]", - "properties": { - "principalId": "[parameters('managedIdentityObjectId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", - "principalType": "ServicePrincipal" - } - } - ], - "outputs": { - "keyvaultName": { + "clientId": { + "type": "string", + "metadata": { + "description": "The client ID (application ID) of the user assigned identity." + }, + "value": "[reference('userAssignedIdentity').clientId]" + }, + "resourceGroupName": { "type": "string", "metadata": { - "description": "Name of the Key Vault." + "description": "The resource group the user assigned identity was deployed into." }, - "value": "[parameters('kvName')]" + "value": "[resourceGroup().name]" }, - "keyvaultId": { + "location": { "type": "string", "metadata": { - "description": "Resource ID of the Key Vault." + "description": "The location the resource was deployed into." }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('kvName'))]" + "value": "[reference('userAssignedIdentity', '2024-11-30', 'full').location]" } } } - }, - "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]" - ] + } }, - { + "network": { + "condition": "[parameters('enablePrivateNetworking')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "deploy_ai_foundry", - "resourceGroup": "[resourceGroup().name]", + "name": "[take(format('network-{0}-deployment', variables('resourcesName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "solutionName": { - "value": "[variables('solutionSuffix')]" - }, - "solutionLocation": { - "value": "[parameters('aiDeploymentsLocation')]" - }, - "keyVaultName": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" - }, - "deploymentType": { - "value": "[parameters('deploymentType')]" + "resourcesName": { + "value": "[variables('resourcesName')]" }, - "gptModelName": { - "value": "[parameters('gptModelName')]" + "logAnalyticsWorkSpaceResourceId": { + "value": "[reference('logAnalyticsWorkspace').outputs.resourceId.value]" }, - "azureOpenaiAPIVersion": { - "value": "[parameters('azureOpenaiAPIVersion')]" + "vmAdminUsername": { + "value": "[coalesce(parameters('vmAdminUsername'), 'JumpboxAdminUser')]" }, - "gptDeploymentCapacity": { - "value": "[parameters('gptDeploymentCapacity')]" + "vmAdminPassword": { + "value": "[coalesce(parameters('vmAdminPassword'), 'JumpboxAdminP@ssw0rd1234!')]" }, - "embeddingModel": { - "value": "[parameters('embeddingModel')]" + "vmSize": { + "value": "[coalesce(parameters('vmSize'), 'Standard_DS2_v2')]" }, - "embeddingDeploymentCapacity": { - "value": "[parameters('embeddingDeploymentCapacity')]" - }, - "existingLogAnalyticsWorkspaceId": { - "value": "[parameters('existingLogAnalyticsWorkspaceId')]" - }, - "azureExistingAIProjectResourceId": { - "value": "[parameters('azureExistingAIProjectResourceId')]" - }, - "aiFoundryAiServicesAiProjectResourceName": { - "value": "[variables('aiFoundryAiServicesAiProjectResourceName')]" + "location": { + "value": "[variables('solutionLocation')]" }, "tags": { - "value": "[parameters('tags')]" + "value": "[variables('allTags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" } }, "template": { @@ -595,632 +911,566 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "651349839825117270" + "templateHash": "8046497849303973351" } }, "parameters": { - "solutionName": { - "type": "string", - "metadata": { - "description": "Required. Solution Name" - } - }, - "solutionLocation": { - "type": "string", - "metadata": { - "description": "Required. Solution Location" - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Contains Name of KeyVault." - } - }, - "deploymentType": { + "resourcesName": { "type": "string", "metadata": { - "description": "Required. Indicates the type of Deployment." + "description": "Required. Named used for all resource naming." } }, - "gptModelName": { + "logAnalyticsWorkSpaceResourceId": { "type": "string", - "defaultValue": "gpt-4o-mini", "metadata": { - "description": "Optional. GPT Model Name" + "description": "Required. Resource ID of the Log Analytics Workspace for monitoring and diagnostics." } }, - "azureOpenaiAPIVersion": { + "location": { "type": "string", + "minLength": 3, "metadata": { - "description": "Required. Azure OepnAI API Version." - } - }, - "gptDeploymentCapacity": { - "type": "int", - "metadata": { - "description": "Required. Param to get Deployment Capacity." + "description": "Required. Azure region for all services." } }, - "embeddingModel": { - "type": "string", - "defaultValue": "text-embedding-ada-002", + "tags": { + "type": "object", + "defaultValue": {}, "metadata": { - "description": "Optional. Embedding Model." + "description": "Optional. Tags to be applied to the resources." } }, - "embeddingDeploymentCapacity": { - "type": "int", - "defaultValue": 80, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, "metadata": { - "description": "Optional. Info about Embedding Deployment Capacity." + "description": "Optional. Enable/Disable usage telemetry for module." } }, - "existingLogAnalyticsWorkspaceId": { - "type": "string", - "defaultValue": "", + "vmAdminUsername": { + "type": "securestring", "metadata": { - "description": "Optional. Existing Log Analytics WorkspaceID." + "description": "Required. Admin username for the VM." } }, - "azureExistingAIProjectResourceId": { - "type": "string", - "defaultValue": "", + "vmAdminPassword": { + "type": "securestring", "metadata": { - "description": "Optional. Azure Existing AI Project ResourceID." + "description": "Required. Admin password for the VM." } }, - "aiFoundryAiServicesAiProjectResourceName": { + "vmSize": { "type": "string", "metadata": { - "description": "Required. The name of the AI Foundry AI Project resource in Azure." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to be applied to the resources." + "description": "Required. VM size for the Jumpbox VM." } } }, - "variables": { - "aiFoundryName": "[format('aif-{0}', parameters('solutionName'))]", - "applicationInsightsName": "[format('appi-{0}', parameters('solutionName'))]", - "keyvaultName": "[parameters('keyVaultName')]", - "location": "[parameters('solutionLocation')]", - "aiProjectName": "[format('{0}-{1}', parameters('aiFoundryAiServicesAiProjectResourceName'), parameters('solutionName'))]", - "aiProjectFriendlyName": "[variables('aiProjectName')]", - "aiProjectDescription": "AI Foundry Project", - "aiSearchName": "[format('srch-{0}', parameters('solutionName'))]", - "workspaceName": "[format('log-{0}', parameters('solutionName'))]", - "aiModelDeployments": [ - { - "name": "[parameters('gptModelName')]", - "model": "[parameters('gptModelName')]", - "sku": { - "name": "[parameters('deploymentType')]", - "capacity": "[parameters('gptDeploymentCapacity')]" - }, - "raiPolicyName": "Microsoft.Default" - }, - { - "name": "[parameters('embeddingModel')]", - "model": "[parameters('embeddingModel')]", - "sku": { - "name": "GlobalStandard", - "capacity": "[parameters('embeddingDeploymentCapacity')]" - }, - "raiPolicyName": "Microsoft.Default" - } - ], - "useExisting": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", - "existingLawSubscription": "[if(variables('useExisting'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[2], '')]", - "existingLawResourceGroup": "[if(variables('useExisting'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[4], '')]", - "existingLawName": "[if(variables('useExisting'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[8], '')]", - "existingOpenAIEndpoint": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), format('https://{0}.openai.azure.com/', split(parameters('azureExistingAIProjectResourceId'), '/')[8]), '')]", - "existingProjEndpoint": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), format('https://{0}.services.ai.azure.com/api/projects/{1}', split(parameters('azureExistingAIProjectResourceId'), '/')[8], split(parameters('azureExistingAIProjectResourceId'), '/')[10]), '')]", - "existingAIFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", - "existingAIProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[10], '')]", - "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], '')]", - "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", - "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], '')]", - "aiSearchConnectionName": "[format('foundry-search-connection-{0}', parameters('solutionName'))]", - "aiAppInsightConnectionName": "[format('foundry-app-insights-connection-{0}', parameters('solutionName'))]" - }, "resources": [ { - "condition": "[not(variables('useExisting'))]", - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2023-09-01", - "name": "[variables('workspaceName')]", - "location": "[variables('location')]", - "tags": "[parameters('tags')]", - "properties": { - "retentionInDays": 30, - "sku": { - "name": "PerGB2018" - } - } - }, - { - "type": "Microsoft.Insights/components", - "apiVersion": "2020-02-02", - "name": "[variables('applicationInsightsName')]", - "location": "[variables('location')]", - "kind": "web", - "properties": { - "Application_Type": "web", - "publicNetworkAccessForIngestion": "Enabled", - "publicNetworkAccessForQuery": "Enabled", - "WorkspaceResourceId": "[if(variables('useExisting'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingLawSubscription'), variables('existingLawResourceGroup')), 'Microsoft.OperationalInsights/workspaces', variables('existingLawName')), resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')))]" - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]" - ] - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2025-04-01-preview", - "name": "[variables('aiFoundryName')]", - "location": "[variables('location')]", - "sku": { - "name": "S0" - }, - "kind": "AIServices", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "allowProjectManagement": true, - "customSubDomainName": "[variables('aiFoundryName')]", - "networkAcls": { - "defaultAction": "Allow", - "virtualNetworkRules": [], - "ipRules": [] - }, - "publicNetworkAccess": "Enabled", - "disableLocalAuth": false - }, - "tags": "[parameters('tags')]" - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', variables('aiFoundryName'), variables('aiProjectName'))]", - "location": "[variables('location')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "description": "[variables('aiProjectDescription')]", - "displayName": "[variables('aiProjectFriendlyName')]" - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" - ] - }, - { - "copy": { - "name": "aiFModelDeployments", - "count": "[length(variables('aiModelDeployments'))]", - "mode": "serial", - "batchSize": 1 - }, - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2023-05-01", - "name": "[format('{0}/{1}', variables('aiFoundryName'), variables('aiModelDeployments')[copyIndex()].name)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('network-{0}-create', parameters('resourcesName')), 64)]", "properties": { - "model": { - "format": "OpenAI", - "name": "[variables('aiModelDeployments')[copyIndex()].model]" - }, - "raiPolicyName": "[variables('aiModelDeployments')[copyIndex()].raiPolicyName]" - }, - "sku": { - "name": "[variables('aiModelDeployments')[copyIndex()].sku.name]", - "capacity": "[variables('aiModelDeployments')[copyIndex()].sku.capacity]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" - ] - }, - { - "type": "Microsoft.Search/searchServices", - "apiVersion": "2025-02-01-preview", - "name": "[variables('aiSearchName')]", - "location": "[parameters('solutionLocation')]", - "sku": { - "name": "basic" - }, - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "replicaCount": 1, - "partitionCount": 1, - "hostingMode": "default", - "publicNetworkAccess": "enabled", - "networkRuleSet": { - "ipRules": [] - }, - "encryptionWithCmk": { - "enforcement": "Unspecified" - }, - "disableLocalAuth": false, - "authOptions": { - "aadOrApiKey": { - "aadAuthFailureMode": "http403" - } - }, - "semanticSearch": "free" - }, - "tags": "[parameters('tags')]" - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.CognitiveServices/accounts/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', variables('aiFoundryName'), variables('aiSearchConnectionName'))]", - "properties": { - "category": "CognitiveSearch", - "target": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview').endpoint]", - "authType": "AAD", - "isSharedToAll": true, - "metadata": { - "ApiType": "Azure", - "ResourceId": "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "location": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').location]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]", - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('aiFoundryName'))]", - "name": "[guid(resourceGroup().id, resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'))]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').identity.principalId]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]", - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Search/searchServices/{0}', variables('aiSearchName'))]", - "name": "[guid(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f'))]", - "properties": { - "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", - "principalId": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName'))]", - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] - }, - { - "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Search/searchServices/{0}', variables('aiSearchName'))]", - "name": "[guid(resourceGroup().id, variables('existingAIProjectName'), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f'), 'Existing')]", - "properties": { - "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", - "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting'), '2022-09-01').outputs.aiProjectPrincipalId.value]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting')]" - ] - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Search/searchServices/{0}', variables('aiSearchName'))]", - "name": "[guid(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'))]", - "properties": { - "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", - "principalId": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName'))]", - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] - }, - { - "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Search/searchServices/{0}', variables('aiSearchName'))]", - "name": "[guid(resourceGroup().id, variables('existingAIProjectName'), extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'), 'Existing')]", - "properties": { - "roleDefinitionId": "[extensionResourceId(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), 'Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", - "principalId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting'), '2022-09-01').outputs.aiProjectPrincipalId.value]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.Resources/deployments', 'assignOpenAIRoleToAISearchExisting')]" - ] - }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.CognitiveServices/accounts/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', variables('aiFoundryName'), variables('aiAppInsightConnectionName'))]", - "properties": { - "category": "AppInsights", - "target": "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]", - "authType": "ApiKey", - "isSharedToAll": true, - "credentials": { - "key": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').ConnectionString]" - }, - "metadata": { - "ApiType": "Azure", - "ResourceId": "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]", - "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-PREVIEW-API-VERSION')]", - "properties": { - "value": "[parameters('azureOpenaiAPIVersion')]" - }, - "tags": "[parameters('tags')]" - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-ENDPOINT')]", - "properties": { - "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoints['OpenAI Language Model Instance API'])]" - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-EMBEDDING-MODEL')]", - "properties": { - "value": "[parameters('embeddingModel')]" - }, - "tags": "[parameters('tags')]" - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SEARCH-ENDPOINT')]", - "properties": { - "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SEARCH-INDEX')]", - "properties": { - "value": "transcripts_index" - }, - "tags": "[parameters('tags')]" - }, - { - "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "aiProjectSearchConnectionDeployment", - "subscriptionId": "[variables('existingAIServiceSubscription')]", - "resourceGroup": "[variables('existingAIServiceResourceGroup')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "expressionEvaluationOptions": { + "scope": "inner" }, "mode": "Incremental", "parameters": { - "existingAIProjectName": { - "value": "[variables('existingAIProjectName')]" + "resourcesName": { + "value": "[parameters('resourcesName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkSpaceResourceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "tags": { + "value": "[parameters('tags')]" }, - "existingAIFoundryName": { - "value": "[variables('existingAIFoundryName')]" + "addressPrefixes": { + "value": [ + "10.0.0.0/20" + ] }, - "aiSearchName": { - "value": "[variables('aiSearchName')]" + "subnets": { + "value": [ + { + "name": "web", + "addressPrefixes": [ + "10.0.0.0/23" + ], + "networkSecurityGroup": { + "name": "nsg-web", + "securityRules": [ + { + "name": "AllowHttpsInbound", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 100, + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefixes": [ + "0.0.0.0/0" + ], + "destinationAddressPrefixes": [ + "10.0.0.0/23" + ] + } + }, + { + "name": "AllowIntraSubnetTraffic", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 200, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefixes": [ + "10.0.0.0/23" + ], + "destinationAddressPrefixes": [ + "10.0.0.0/23" + ] + } + }, + { + "name": "AllowAzureLoadBalancer", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 300, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "AzureLoadBalancer", + "destinationAddressPrefix": "10.0.0.0/23" + } + } + ] + }, + "delegation": "Microsoft.App/environments" + }, + { + "name": "peps", + "addressPrefixes": [ + "10.0.2.0/23" + ], + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Disabled", + "networkSecurityGroup": { + "name": "nsg-peps", + "securityRules": [] + } + } + ] }, - "aiSearchResourceId": { - "value": "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" + "bastionConfiguration": { + "value": { + "name": "[format('bas-{0}', parameters('resourcesName'))]", + "subnet": { + "name": "AzureBastionSubnet", + "addressPrefixes": [ + "10.0.10.0/26" + ], + "networkSecurityGroup": { + "name": "nsg-AzureBastionSubnet", + "securityRules": [ + { + "name": "AllowGatewayManager", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 2702, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefix": "GatewayManager", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowHttpsInBound", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 2703, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefix": "Internet", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowSshRdpOutbound", + "properties": { + "access": "Allow", + "direction": "Outbound", + "priority": 100, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRanges": [ + "22", + "3389" + ], + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowAzureCloudOutbound", + "properties": { + "access": "Allow", + "direction": "Outbound", + "priority": 110, + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "AzureCloud" + } + } + ] + } + } + } }, - "aiSearchLocation": { - "value": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').location]" + "jumpboxConfiguration": { + "value": { + "name": "[format('vm-jumpbox-{0}', parameters('resourcesName'))]", + "size": "[parameters('vmSize')]", + "username": "[parameters('vmAdminUsername')]", + "password": "[parameters('vmAdminPassword')]", + "subnet": { + "name": "jumpbox", + "addressPrefixes": [ + "10.0.12.0/23" + ], + "networkSecurityGroup": { + "name": "nsg-jumbox", + "securityRules": [ + { + "name": "AllowRdpFromBastion", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 100, + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "3389", + "sourceAddressPrefixes": [ + "10.0.10.0/26" + ], + "destinationAddressPrefixes": [ + "10.0.12.0/23" + ] + } + } + ] + } + } + } }, - "aiSearchConnectionName": { - "value": "[variables('aiSearchConnectionName')]" + "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.37.4.10188", - "templateHash": "6038840175458269917" + "templateHash": "10645092392603021381" } }, - "parameters": { - "existingAIProjectName": { - "type": "string", - "metadata": { - "description": "Required. Existing AI Project Name" - } - }, - "existingAIFoundryName": { - "type": "string", + "definitions": { + "_1.networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, "metadata": { - "description": "Required. Existing AI Foundry Name" + "description": "Custom type definition for network security group configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } } }, - "aiSearchName": { - "type": "string", + "bastionHostConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Bastion Host resource." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, "metadata": { - "description": "Required. AI Search Name" + "description": "Custom type definition for establishing Bastion Host for remote connection.", + "__bicep_imported_from!": { + "sourceTemplate": "bastionHost.bicep" + } } }, - "aiSearchResourceId": { - "type": "string", + "jumpBoxConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Virtual Machine." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The size of the VM." + } + }, + "username": { + "type": "string", + "metadata": { + "description": "Username to access VM." + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "Password to access VM." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, "metadata": { - "description": "Required. AI Search Resource ID" + "description": "Custom type definition for establishing Jumpbox Virtual Machine and its associated resources.", + "__bicep_imported_from!": { + "sourceTemplate": "jumpbox.bicep" + } } }, - "aiSearchLocation": { - "type": "string", + "subnetOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the subnet." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the subnet." + } + }, + "nsgName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The name of the associated network security group, if any." + } + }, + "nsgResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The resource ID of the associated network security group, if any." + } + } + }, "metadata": { - "description": "Required. AI Search Location" + "description": "Custom type definition for subnet resource information as output", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } } }, - "aiSearchConnectionName": { - "type": "string", - "metadata": { - "description": "Required. AI Search Connection Name" - } - } - }, - "resources": [ - { - "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}/{2}', parameters('existingAIFoundryName'), parameters('existingAIProjectName'), parameters('aiSearchConnectionName'))]", + "subnetType": { + "type": "object", "properties": { - "category": "CognitiveSearch", - "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", - "authType": "AAD", - "isSharedToAll": true, - "metadata": { - "ApiType": "Azure", - "ResourceId": "[parameters('aiSearchResourceId')]", - "location": "[parameters('aiSearchLocation')]" + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/_1.networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "description": "Custom type definition for subnet configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" } } } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] - }, - { - "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "assignOpenAIRoleToAISearchExisting", - "subscriptionId": "[variables('existingAIServiceSubscription')]", - "resourceGroup": "[variables('existingAIServiceResourceGroup')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "roleDefinitionId": { - "value": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]" - }, - "roleAssignmentName": { - "value": "[guid(resourceGroup().id, resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'), 'openai-foundry')]" - }, - "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIFoundryName')), createObject('value', variables('aiFoundryName')))]", - "principalId": { - "value": "[reference(resourceId('Microsoft.Search/searchServices', variables('aiSearchName')), '2025-02-01-preview', 'full').identity.principalId]" - }, - "aiProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIProjectName')), createObject('value', variables('aiProjectName')))]", - "aiModelDeployments": { - "value": "[variables('aiModelDeployments')]" - }, - "tags": { - "value": "[parameters('tags')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "14256377996349985323" - } }, "parameters": { - "principalId": { + "resourcesName": { "type": "string", - "defaultValue": "", + "minLength": 6, + "maxLength": 25, "metadata": { - "description": "Optional. Principal ID to assign the role to." + "description": "Name used for naming all network resources." } }, - "roleDefinitionId": { + "location": { "type": "string", + "minLength": 3, "metadata": { - "description": "Required. ID of the role definition to assign." + "description": "Azure region for all services." } }, - "roleAssignmentName": { + "logAnalyticsWorkSpaceResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Name of the role assignment." + "description": "Resource ID of the Log Analytics Workspace for monitoring and diagnostics." } }, - "aiFoundryName": { - "type": "string", + "addressPrefixes": { + "type": "array", "metadata": { - "description": "Required. Name of the AI Foundry resource." + "description": "Networking address prefix for the VNET." } }, - "aiProjectName": { - "type": "string", - "defaultValue": "", + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetType" + }, "metadata": { - "description": "Optional. Name of the AI project." + "description": "Array of subnets to be created within the VNET." } }, - "aiModelDeployments": { - "type": "array", - "defaultValue": [], + "jumpboxConfiguration": { + "$ref": "#/definitions/jumpBoxConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation." + } + }, + "bastionConfiguration": { + "$ref": "#/definitions/bastionHostConfigurationType", + "nullable": true, "metadata": { - "description": "Optional. List of AI model deployments." + "description": "Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation." } }, "tags": { @@ -1229,958 +1479,53273 @@ "metadata": { "description": "Optional. Tags to be applied to the resources." } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } } }, - "resources": [ - { - "copy": { - "name": "aiServicesDeployments", - "count": "[length(parameters('aiModelDeployments'))]", - "mode": "serial", - "batchSize": 1 - }, - "condition": "[not(empty(parameters('aiModelDeployments')))]", - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('aiModelDeployments')[copyIndex()].name)]", + "resources": { + "virtualNetwork": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetwork', parameters('resourcesName'))]", "properties": { - "model": { - "format": "OpenAI", - "name": "[parameters('aiModelDeployments')[copyIndex()].model]" + "expressionEvaluationOptions": { + "scope": "inner" }, - "raiPolicyName": "[parameters('aiModelDeployments')[copyIndex()].raiPolicyName]" - }, - "sku": { - "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", - "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" - }, - "tags": "[parameters('tags')]" + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('vnet-{0}', parameters('resourcesName'))]" + }, + "addressPrefixes": { + "value": "[parameters('addressPrefixes')]" + }, + "subnets": { + "value": "[parameters('subnets')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "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.37.4.10188", + "templateHash": "1936468723755149871" + } + }, + "definitions": { + "subnetOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the subnet." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the subnet." + } + }, + "nsgName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The name of the associated network security group, if any." + } + }, + "nsgResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The resource ID of the associated network security group, if any." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for subnet resource information as output" + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for subnet configuration" + } + }, + "networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for network security group configuration" + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the virtual network." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region to deploy resources." + } + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`." + } + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetType" + }, + "metadata": { + "description": "An array of subnets to be created within the virtual network. Each subnet can have its own configuration and associated Network Security Group (NSG)." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "nsgs": { + "copy": { + "name": "nsgs", + "count": "[length(parameters('subnets'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-{1}-networksecuritygroup', parameters('name'), tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), parameters('name'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "securityRules": { + "value": "[tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'securityRules')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.33.93.31351", + "templateHash": "2305747478751645177" + }, + "name": "Network Security Groups", + "description": "This module deploys a Network security Group (NSG)." + }, + "definitions": { + "securityRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security rule." + } + }, + "properties": { + "type": "object", + "properties": { + "access": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Whether network traffic is allowed or denied." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the security rule." + } + }, + "destinationAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." + } + }, + "destinationAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." + } + }, + "destinationApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as destination." + } + }, + "destinationPortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "destinationPortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination port ranges." + } + }, + "direction": { + "type": "string", + "allowedValues": [ + "Inbound", + "Outbound" + ], + "metadata": { + "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 4096, + "metadata": { + "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "Ah", + "Esp", + "Icmp", + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Network protocol this rule applies to." + } + }, + "sourceAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." + } + }, + "sourceAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP ranges." + } + }, + "sourceApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as source." + } + }, + "sourcePortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "sourcePortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The source port ranges." + } + } + }, + "metadata": { + "description": "Required. The properties of the security rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a security rule." + } + }, + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Network Security Group." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "securityRules": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." + } + }, + "flushConnection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" + } + } + } + } + }, + "networkSecurityGroup": { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "securityRules", + "count": "[length(coalesce(parameters('securityRules'), createArray()))]", + "input": { + "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", + "properties": { + "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", + "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", + "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", + "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", + "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", + "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", + "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", + "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", + "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", + "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", + "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", + "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", + "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", + "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", + "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" + } + } + } + ], + "flushConnection": "[parameters('flushConnection')]" + } + }, + "networkSecurityGroup_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/networkSecurityGroups/{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": [ + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ + "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": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-virtualNetwork', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "addressPrefixes": { + "value": "[parameters('addressPrefixes')]" + }, + "subnets": { + "copy": [ + { + "name": "value", + "count": "[length(parameters('subnets'))]", + "input": "[createObject('name', parameters('subnets')[copyIndex('value')].name, 'addressPrefixes', tryGet(parameters('subnets')[copyIndex('value')], 'addressPrefixes'), 'networkSecurityGroupResourceId', if(not(empty(tryGet(parameters('subnets')[copyIndex('value')], 'networkSecurityGroup'))), reference(format('nsgs[{0}]', copyIndex('value'))).outputs.resourceId.value, null()), 'privateEndpointNetworkPolicies', tryGet(parameters('subnets')[copyIndex('value')], 'privateEndpointNetworkPolicies'), 'privateLinkServiceNetworkPolicies', tryGet(parameters('subnets')[copyIndex('value')], 'privateLinkServiceNetworkPolicies'), 'delegation', tryGet(parameters('subnets')[copyIndex('value')], 'delegation'))]" + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "name": "vnetDiagnostics", + "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metricCategories": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + } + ] + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.35.1.17967", + "templateHash": "16195883788906927531" + }, + "name": "Virtual Networks", + "description": "This module deploys a Virtual Network (vNet)." + }, + "definitions": { + "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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "prefixItems": [ + { + "type": "object", + "properties": { + "pool": { + "type": "object", + "properties": { + "id": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the IPAM pool." + } + } + }, + "metadata": { + "description": "Required. The Resource ID of the IPAM pool." + } + }, + "numberOfIpAddresses": { + "type": "string", + "metadata": { + "description": "Required. Number of IP addresses allocated from the pool." + } + } + } + } + ], + "items": false, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` is empty and the VNet address space configured to use IPAM Pool." + } + }, + "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", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "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 OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`." + } + }, + "ipamPoolNumberOfIpAddresses": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Number of IP addresses allocated from the pool. To be used only when the addressPrefix param is defined with a resource ID of an IPAM pool." + } + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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.7.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-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "addressSpace": "[if(contains(parameters('addressPrefixes')[0], '/Microsoft.Network/networkManagers/'), createObject('ipamPoolPrefixAllocations', createArray(createObject('pool', createObject('id', parameters('addressPrefixes')[0]), 'numberOfIpAddresses', parameters('ipamPoolNumberOfIpAddresses')))), createObject('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')]" + }, + "ipamPoolPrefixAllocations": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'ipamPoolPrefixAllocations')]" + }, + "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')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "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.35.1.17967", + "templateHash": "9728353654559466189" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. 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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", + "nullable": true, + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "nullable": true, + "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 the 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 the 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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-05-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')]", + "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", + "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": "[parameters('privateEndpointNetworkPolicies')]", + "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + } + }, + "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())]" + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "metadata": { + "description": "The IPAM pool prefix allocations for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), 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.35.1.17967", + "templateHash": "11179987886456111827" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering." + }, + "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_subnets" + ] + }, + "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.35.1.17967", + "templateHash": "11179987886456111827" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering." + }, + "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_subnets" + ] + } + }, + "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-05-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "nsgs" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "value": "[reference('virtualNetwork').outputs.name.value]" + }, + "resourceId": { + "type": "string", + "value": "[reference('virtualNetwork').outputs.resourceId.value]" + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOutputType" + }, + "copy": { + "count": "[length(parameters('subnets'))]", + "input": { + "name": "[parameters('subnets')[copyIndex()].name]", + "resourceId": "[reference('virtualNetwork').outputs.subnetResourceIds.value[copyIndex()]]", + "nsgName": "[if(not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup'))), tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), null())]", + "nsgResourceId": "[if(not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup'))), reference(format('nsgs[{0}]', copyIndex())).outputs.resourceId.value, null())]" + } + } + } + } + } + } }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiFoundryName'))]", - "name": "[parameters('roleAssignmentName')]", + "bastionHost": { + "condition": "[not(empty(parameters('bastionConfiguration')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-bastionHost', parameters('resourcesName'))]", "properties": { - "roleDefinitionId": "[parameters('roleDefinitionId')]", - "principalId": "[parameters('principalId')]", - "principalType": "ServicePrincipal" - } + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(parameters('bastionConfiguration'), 'name'), format('bas-{0}', parameters('resourcesName')))]" + }, + "vnetId": { + "value": "[reference('virtualNetwork').outputs.resourceId.value]" + }, + "vnetName": { + "value": "[reference('virtualNetwork').outputs.name.value]" + }, + "location": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "subnet": { + "value": "[tryGet(parameters('bastionConfiguration'), 'subnet')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.37.4.10188", + "templateHash": "3082168335446205769" + } + }, + "definitions": { + "bastionHostConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Bastion Host resource." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for establishing Bastion Host for remote connection." + } + }, + "_1.networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "description": "Custom type definition for network security group configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/_1.networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "description": "Custom type definition for subnet configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Azure Bastion Host resource." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region to deploy resources." + } + }, + "vnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Virtual Network where the Azure Bastion Host will be deployed." + } + }, + "vnetName": { + "type": "string", + "metadata": { + "description": "Name of the Virtual Network where the Azure Bastion Host will be deployed." + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Log Analytics Workspace for monitoring and diagnostics." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to the resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "resources": { + "nsg": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}', parameters('vnetName'), tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'), parameters('vnetName'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "securityRules": { + "value": "[tryGet(parameters('subnet'), 'networkSecurityGroup', 'securityRules')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.33.93.31351", + "templateHash": "2305747478751645177" + }, + "name": "Network Security Groups", + "description": "This module deploys a Network security Group (NSG)." + }, + "definitions": { + "securityRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security rule." + } + }, + "properties": { + "type": "object", + "properties": { + "access": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Whether network traffic is allowed or denied." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the security rule." + } + }, + "destinationAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." + } + }, + "destinationAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." + } + }, + "destinationApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as destination." + } + }, + "destinationPortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "destinationPortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination port ranges." + } + }, + "direction": { + "type": "string", + "allowedValues": [ + "Inbound", + "Outbound" + ], + "metadata": { + "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 4096, + "metadata": { + "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "Ah", + "Esp", + "Icmp", + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Network protocol this rule applies to." + } + }, + "sourceAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." + } + }, + "sourceAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP ranges." + } + }, + "sourceApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as source." + } + }, + "sourcePortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "sourcePortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The source port ranges." + } + } + }, + "metadata": { + "description": "Required. The properties of the security rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a security rule." + } + }, + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Network Security Group." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "securityRules": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." + } + }, + "flushConnection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" + } + } + } + } + }, + "networkSecurityGroup": { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "securityRules", + "count": "[length(coalesce(parameters('securityRules'), createArray()))]", + "input": { + "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", + "properties": { + "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", + "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", + "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", + "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", + "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", + "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", + "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", + "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", + "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", + "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", + "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", + "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", + "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", + "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", + "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" + } + } + } + ], + "flushConnection": "[parameters('flushConnection')]" + } + }, + "networkSecurityGroup_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/networkSecurityGroups/{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": [ + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ + "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]" + } + } + } + } + }, + "bastionSubnet": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('bastionSubnet-{0}', parameters('vnetName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('vnetName')]" + }, + "name": { + "value": "AzureBastionSubnet" + }, + "addressPrefixes": { + "value": "[tryGet(parameters('subnet'), 'addressPrefixes')]" + }, + "networkSecurityGroupResourceId": { + "value": "[reference('nsg').outputs.resourceId.value]" + }, + "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.35.1.17967", + "templateHash": "9728353654559466189" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. 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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", + "nullable": true, + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "nullable": true, + "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 the 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 the 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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-05-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')]", + "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", + "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": "[parameters('privateEndpointNetworkPolicies')]", + "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + } + }, + "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())]" + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "metadata": { + "description": "The IPAM pool prefix allocations for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]" + } + } + } + }, + "dependsOn": [ + "nsg" + ] + }, + "bastionHost": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('bastionHost-{0}-{1}', parameters('vnetName'), parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "skuName": { + "value": "Standard" + }, + "location": { + "value": "[parameters('location')]" + }, + "virtualNetworkResourceId": { + "value": "[parameters('vnetId')]" + }, + "diagnosticSettings": { + "value": [ + { + "name": "bastionDiagnostics", + "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ] + } + ] + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "publicIPAddressObject": { + "value": { + "name": "[format('pip-{0}', parameters('name'))]", + "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.33.93.31351", + "templateHash": "2586599138991803385" + }, + "name": "Bastion Hosts", + "description": "This module deploys a Bastion Host." + }, + "definitions": { + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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. This parameter is ignored when enablePrivateOnlyBastion is true." + } + }, + "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. This parameter is ignored when enablePrivateOnlyBastion is true." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Developer", + "Premium", + "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. For Basic and Developer SKU Copy/Paste is always enabled." + } + }, + "enableFileCopy": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Choose to disable or enable File Copy. Not supported for Basic and Developer SKU." + } + }, + "enableIpConnect": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable IP Connect. Not supported for Basic and Developer SKU." + } + }, + "enableKerberos": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Kerberos authentication. Not supported for Developer SKU." + } + }, + "enableShareableLink": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Shareable Link. Not supported for Basic and Developer SKU." + } + }, + "enableSessionRecording": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Session Recording feature. The Premium SKU is required for this feature. If Session Recording is enabled, the Native client support will be disabled." + } + }, + "enablePrivateOnlyBastion": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Private-only Bastion deployment. The Premium SKU is required for this feature." + } + }, + "scaleUnits": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. The scale units for the Bastion Host resource. The Basic and Developer SKU only support 2 scale units." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + }, + "zones": { + "type": "array", + "items": { + "type": "int" + }, + "defaultValue": [], + "allowedValues": [ + 1, + 2, + 3 + ], + "metadata": { + "description": "Optional. A list of availability zones denoting where the Bastion Host resource needs to come from. This is not supported for the Developer SKU." + } + } + }, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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.6.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" + } + } + } + } + }, + "azureBastion": { + "type": "Microsoft.Network/bastionHosts", + "apiVersion": "2024-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[coalesce(parameters('tags'), createObject())]", + "sku": { + "name": "[parameters('skuName')]" + }, + "zones": "[if(equals(parameters('skuName'), 'Developer'), createArray(), map(parameters('zones'), lambda('zone', string(lambdaVariables('zone')))))]", + "properties": "[union(createObject('scaleUnits', if(or(equals(parameters('skuName'), 'Basic'), equals(parameters('skuName'), 'Developer')), 2, parameters('scaleUnits')), 'ipConfigurations', if(equals(parameters('skuName'), 'Developer'), createArray(), createArray(createObject('name', 'IpConfAzureBastionSubnet', 'properties', union(createObject('subnet', createObject('id', format('{0}/subnets/AzureBastionSubnet', parameters('virtualNetworkResourceId')))), if(not(parameters('enablePrivateOnlyBastion')), createObject('publicIPAddress', createObject('id', if(not(empty(parameters('bastionSubnetPublicIpResourceId'))), parameters('bastionSubnetPublicIpResourceId'), reference('publicIPAddress').outputs.resourceId.value))), createObject())))))), if(equals(parameters('skuName'), 'Developer'), createObject('virtualNetwork', createObject('id', parameters('virtualNetworkResourceId'))), createObject()), if(or(or(equals(parameters('skuName'), 'Basic'), equals(parameters('skuName'), 'Standard')), equals(parameters('skuName'), 'Premium')), createObject('enableKerberos', parameters('enableKerberos')), createObject()), if(or(equals(parameters('skuName'), 'Standard'), equals(parameters('skuName'), 'Premium')), createObject('enableTunneling', if(equals(parameters('skuName'), 'Standard'), true(), if(parameters('enableSessionRecording'), false(), true())), 'disableCopyPaste', parameters('disableCopyPaste'), 'enableFileCopy', parameters('enableFileCopy'), 'enableIpConnect', parameters('enableIpConnect'), 'enableShareableLink', parameters('enableShareableLink')), createObject()), if(equals(parameters('skuName'), 'Premium'), createObject('enableSessionRecording', parameters('enableSessionRecording'), 'enablePrivateOnlyBastion', parameters('enablePrivateOnlyBastion')), 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": "[and(and(empty(parameters('bastionSubnetPublicIpResourceId')), not(equals(parameters('skuName'), 'Developer'))), not(parameters('enablePrivateOnlyBastion')))]", + "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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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": "[coalesce(tryGet(parameters('publicIPAddressObject'), 'zones'), if(greater(length(parameters('zones')), 0), parameters('zones'), null()))]" + } + }, + "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.33.93.31351", + "templateHash": "5168739580767459761" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address." + }, + "definitions": { + "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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipTagType": { + "type": "object", + "properties": { + "ipTagType": { + "type": "string", + "metadata": { + "description": "Required. The IP tag type." + } + }, + "tag": { + "type": "string", + "metadata": { + "description": "Required. The IP tag." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "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." + } + }, + "ipTags": { + "type": "array", + "items": { + "$ref": "#/definitions/ipTagType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of tags associated with the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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": "[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.8.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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2024-05-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": "[parameters('ipTags')]" + } + }, + "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', '2024-05-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', '2024-05-01', 'full').location]" + }, + "ipConfAzureBastionSubnet": { + "type": "object", + "metadata": { + "description": "The Public IPconfiguration object for the AzureBastionSubnet." + }, + "value": "[if(equals(parameters('skuName'), 'Developer'), createObject(), reference('azureBastion').ipConfigurations[0])]" + } + } + } + }, + "dependsOn": [ + "bastionSubnet" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "value": "[reference('bastionHost').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "value": "[reference('bastionHost').outputs.name.value]" + }, + "subnetId": { + "type": "string", + "value": "[reference('bastionSubnet').outputs.resourceId.value]" + }, + "subnetName": { + "type": "string", + "value": "[reference('bastionSubnet').outputs.name.value]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] + }, + "jumpbox": { + "condition": "[not(empty(parameters('jumpboxConfiguration')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-jumpbox', parameters('resourcesName'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'name'), format('vm-jumpbox-{0}', parameters('resourcesName')))]" + }, + "vnetName": { + "value": "[reference('virtualNetwork').outputs.name.value]" + }, + "size": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'size'), 'Standard_D2s_v3')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "subnet": { + "value": "[tryGet(parameters('jumpboxConfiguration'), 'subnet')]" + }, + "username": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'username'), '')]" + }, + "password": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'password'), '')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "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.37.4.10188", + "templateHash": "40464970328764907" + } + }, + "definitions": { + "jumpBoxConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Virtual Machine." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The size of the VM." + } + }, + "username": { + "type": "string", + "metadata": { + "description": "Username to access VM." + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "Password to access VM." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for establishing Jumpbox Virtual Machine and its associated resources." + } + }, + "_1.networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "description": "Custom type definition for network security group configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/_1.networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "description": "Custom type definition for subnet configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Jumpbox Virtual Machine." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region to deploy resources." + } + }, + "vnetName": { + "type": "string", + "metadata": { + "description": "Name of the Virtual Network where the Jumpbox VM will be deployed." + } + }, + "size": { + "type": "string", + "metadata": { + "description": "Size of the Jumpbox Virtual Machine." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + }, + "username": { + "type": "string", + "metadata": { + "description": "Username to access the Jumpbox VM." + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "Password to access the Jumpbox VM." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to the resources." + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Log Analytics Workspace Resource ID for VM diagnostics." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "vmName": "[take(parameters('name'), 15)]" + }, + "resources": { + "nsg": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}', parameters('vnetName'), tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'), parameters('vnetName'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "securityRules": { + "value": "[tryGet(parameters('subnet'), 'networkSecurityGroup', 'securityRules')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.33.93.31351", + "templateHash": "2305747478751645177" + }, + "name": "Network Security Groups", + "description": "This module deploys a Network security Group (NSG)." + }, + "definitions": { + "securityRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security rule." + } + }, + "properties": { + "type": "object", + "properties": { + "access": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Whether network traffic is allowed or denied." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the security rule." + } + }, + "destinationAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." + } + }, + "destinationAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." + } + }, + "destinationApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as destination." + } + }, + "destinationPortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "destinationPortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination port ranges." + } + }, + "direction": { + "type": "string", + "allowedValues": [ + "Inbound", + "Outbound" + ], + "metadata": { + "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 4096, + "metadata": { + "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "Ah", + "Esp", + "Icmp", + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Network protocol this rule applies to." + } + }, + "sourceAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." + } + }, + "sourceAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP ranges." + } + }, + "sourceApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as source." + } + }, + "sourcePortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "sourcePortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The source port ranges." + } + } + }, + "metadata": { + "description": "Required. The properties of the security rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a security rule." + } + }, + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Network Security Group." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "securityRules": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." + } + }, + "flushConnection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" + } + } + } + } + }, + "networkSecurityGroup": { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "securityRules", + "count": "[length(coalesce(parameters('securityRules'), createArray()))]", + "input": { + "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", + "properties": { + "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", + "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", + "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", + "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", + "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", + "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", + "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", + "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", + "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", + "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", + "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", + "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", + "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", + "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", + "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" + } + } + } + ], + "flushConnection": "[parameters('flushConnection')]" + } + }, + "networkSecurityGroup_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/networkSecurityGroups/{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": [ + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ + "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]" + } + } + } + } + }, + "subnetResource": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[coalesce(tryGet(parameters('subnet'), 'name'), format('{0}-jumpbox-subnet', parameters('vnetName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('vnetName')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('subnet'), 'name'), '')]" + }, + "addressPrefixes": { + "value": "[tryGet(parameters('subnet'), 'addressPrefixes')]" + }, + "networkSecurityGroupResourceId": { + "value": "[reference('nsg').outputs.resourceId.value]" + }, + "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.35.1.17967", + "templateHash": "9728353654559466189" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. 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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", + "nullable": true, + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "nullable": true, + "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 the 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 the 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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-05-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')]", + "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", + "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": "[parameters('privateEndpointNetworkPolicies')]", + "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + } + }, + "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())]" + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "metadata": { + "description": "The IPAM pool prefix allocations for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]" + } + } + } + }, + "dependsOn": [ + "nsg" + ] + }, + "vm": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-jumpbox', variables('vmName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('vmName')]" + }, + "vmSize": { + "value": "[parameters('size')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "adminUsername": { + "value": "[parameters('username')]" + }, + "adminPassword": { + "value": "[parameters('password')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "zone": { + "value": 0 + }, + "imageReference": { + "value": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2019-datacenter", + "version": "latest" + } + }, + "osType": { + "value": "Windows" + }, + "osDisk": { + "value": { + "name": "[format('osdisk-{0}', variables('vmName'))]", + "managedDisk": { + "storageAccountType": "Standard_LRS" + } + } + }, + "encryptionAtHost": { + "value": false + }, + "nicConfigurations": { + "value": [ + { + "name": "[format('nic-{0}', variables('vmName'))]", + "ipConfigurations": [ + { + "name": "ipconfig1", + "subnetResourceId": "[reference('subnetResource').outputs.resourceId.value]" + } + ], + "networkSecurityGroupResourceId": "[reference('nsg').outputs.resourceId.value]", + "diagnosticSettings": [ + { + "name": "jumpboxDiagnostics", + "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metricCategories": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + } + ] + } + ] + }, + "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.34.44.8038", + "templateHash": "1057634180502804806" + }, + "name": "Virtual Machines", + "description": "This module deploys a Virtual Machine with one or multiple NICs and optionally one or multiple public IPs." + }, + "definitions": { + "osDiskType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The disk name." + } + }, + "diskSizeGB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the size of an empty data disk in gigabytes." + } + }, + "createOption": { + "type": "string", + "allowedValues": [ + "Attach", + "Empty", + "FromImage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies how the virtual machine should be created." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion." + } + }, + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the caching requirements." + } + }, + "diffDiskSettings": { + "type": "object", + "properties": { + "placement": { + "type": "string", + "allowedValues": [ + "CacheDisk", + "NvmeDisk", + "ResourceDisk" + ], + "metadata": { + "description": "Required. Specifies the ephemeral disk placement for the operating system disk." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the ephemeral Disk Settings for the operating system disk." + } + }, + "managedDisk": { + "type": "object", + "properties": { + "storageAccountType": { + "type": "string", + "allowedValues": [ + "PremiumV2_LRS", + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS", + "UltraSSD_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the storage account type for the managed disk." + } + }, + "diskEncryptionSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." + } + } + }, + "metadata": { + "description": "Required. The managed disk parameters." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing an OS disk." + } + }, + "dataDiskType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The disk name. When attaching a pre-existing disk, this name is ignored and the name of the existing disk is used." + } + }, + "lun": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the logical unit number of the data disk." + } + }, + "diskSizeGB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the size of an empty data disk in gigabytes. This property is ignored when attaching a pre-existing disk." + } + }, + "createOption": { + "type": "string", + "allowedValues": [ + "Attach", + "Empty", + "FromImage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies how the virtual machine should be created. This property is automatically set to 'Attach' when attaching a pre-existing disk." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion. This property is automatically set to 'Detach' when attaching a pre-existing disk." + } + }, + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the caching requirements. This property is automatically set to 'None' when attaching a pre-existing disk." + } + }, + "diskIOPSReadWrite": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of IOPS allowed for this disk; only settable for UltraSSD disks. One operation can transfer between 4k and 256k bytes. Ignored when attaching a pre-existing disk." + } + }, + "diskMBpsReadWrite": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The bandwidth allowed for this disk; only settable for UltraSSD disks. MBps means millions of bytes per second - MB here uses the ISO notation, of powers of 10. Ignored when attaching a pre-existing disk." + } + }, + "managedDisk": { + "type": "object", + "properties": { + "storageAccountType": { + "type": "string", + "allowedValues": [ + "PremiumV2_LRS", + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS", + "UltraSSD_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the storage account type for the managed disk. Ignored when attaching a pre-existing disk." + } + }, + "diskEncryptionSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." + } + }, + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the resource id of a pre-existing managed disk. If the disk should be created, this property should be empty." + } + } + }, + "metadata": { + "description": "Required. The managed disk parameters." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address. Valid only when creating a new managed disk." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing a data disk." + } + }, + "publicKeyType": { + "type": "object", + "properties": { + "keyData": { + "type": "string", + "metadata": { + "description": "Required. Specifies the SSH public key data used to authenticate through ssh." + } + }, + "path": { + "type": "string", + "metadata": { + "description": "Required. Specifies the full path on the created VM where ssh public key is stored. If the file already exists, the specified key is appended to the file." + } + } + } + }, + "nicConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the NIC configuration." + } + }, + "nicSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The suffix to append to the NIC name." + } + }, + "enableIPForwarding": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether IP forwarding is enabled on this network interface." + } + }, + "enableAcceleratedNetworking": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If the network interface is accelerated networking enabled." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify what happens to the network interface when the VM is deleted." + } + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of DNS servers IP addresses. Use 'AzureProvidedDNS' to switch to azure provided DNS resolution. 'AzureProvidedDNS' value cannot be combined with other IPs, it must be the only value in dnsServers collection." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The network security group (NSG) to attach to the network interface." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "metadata": { + "description": "Required. The IP configurations of the network interface." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the IP configuration." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the NIC configuration." + } + }, + "imageReferenceType": { + "type": "object", + "properties": { + "communityGalleryImageId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specified the community gallery image unique id for vm deployment. This can be fetched from community gallery image GET call." + } + }, + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource Id of the image reference." + } + }, + "offer": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the offer of the platform image or marketplace image used to create the virtual machine." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The image publisher." + } + }, + "sku": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The SKU of the image." + } + }, + "version": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the version of the platform image or marketplace image used to create the virtual machine. The allowed formats are Major.Minor.Build or 'latest'. Even if you use 'latest', the VM image will not automatically update after deploy time even if a new version becomes available." + } + }, + "sharedGalleryImageId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specified the shared gallery image unique id for vm deployment. This can be fetched from shared gallery image GET call." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the image reference." + } + }, + "planType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the plan." + } + }, + "product": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the product of the image from the marketplace." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher ID." + } + }, + "promotionCode": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The promotion code." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Specifies information about the marketplace image used to create the virtual machine." + } + }, + "autoShutDownConfigType": { + "type": "object", + "properties": { + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the auto shutdown configuration." + } + }, + "timeZone": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time zone ID (e.g. China Standard Time, Greenland Standard Time, Pacific Standard time, etc.)." + } + }, + "dailyRecurrenceTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time of day the schedule will occur." + } + }, + "notificationSettings": { + "type": "object", + "properties": { + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the notification settings." + } + }, + "emailRecipient": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The email address to send notifications to (can be a list of semi-colon separated email addresses)." + } + }, + "notificationLocale": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." + } + }, + "webhookUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The webhook URL to which the notification will be sent." + } + }, + "timeInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The time in minutes before shutdown to send notifications." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the schedule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the configuration profile." + } + }, + "vaultSecretGroupType": { + "type": "object", + "properties": { + "sourceVault": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The relative URL of the Key Vault containing all of the certificates in VaultCertificates." + } + }, + "vaultCertificates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "certificateStore": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. For Windows VMs, specifies the certificate store on the Virtual Machine to which the certificate should be added. The specified certificate store is implicitly in the LocalMachine account. For Linux VMs, the certificate file is placed under the /var/lib/waagent directory, with the file name .crt for the X509 certificate file and .prv for private key. Both of these files are .pem formatted." + } + }, + "certificateUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. This is the URL of a certificate that has been uploaded to Key Vault as a secret." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of key vault references in SourceVault which contain certificates." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the set of certificates that should be installed onto the virtual machine." + } + }, + "vmGalleryApplicationType": { + "type": "object", + "properties": { + "packageReferenceId": { + "type": "string", + "metadata": { + "description": "Required. Specifies the GalleryApplicationVersion resource id on the form of /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Compute/galleries/{galleryName}/applications/{application}/versions/{version}." + } + }, + "configurationReference": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the uri to an azure blob that will replace the default configuration for the package if provided." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If set to true, when a new Gallery Application version is available in PIR/SIG, it will be automatically updated for the VM/VMSS." + } + }, + "order": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the order in which the packages have to be installed." + } + }, + "tags": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies a passthrough value for more generic context." + } + }, + "treatFailureAsDeploymentFailure": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If true, any failure for any operation in the VmApplication will fail the deployment." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the gallery application that should be made available to the VM/VMSS." + } + }, + "additionalUnattendContentType": { + "type": "object", + "properties": { + "settingName": { + "type": "string", + "allowedValues": [ + "AutoLogon", + "FirstLogonCommands" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the name of the setting to which the content applies." + } + }, + "content": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the XML formatted content that is added to the unattend.xml file for the specified path and component. The XML must be less than 4KB and must include the root element for the setting or feature that is being inserted." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing additional base-64 encoded XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup." + } + }, + "winRMListenerType": { + "type": "object", + "properties": { + "certificateUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The URL of a certificate that has been uploaded to Key Vault as a secret." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "Http", + "Https" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the protocol of WinRM listener." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing a Windows Remote Management listener." + } + }, + "nicConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the NIC configuration." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" + }, + "metadata": { + "description": "Required. List of IP configurations of the NIC configuration." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the network interface configuration output." + } + }, + "_1.applicationGatewayBackendAddressPoolsType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddresses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. IP address of the backend address." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN of the backend address." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Backend addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application gateway backend address pool." + } + } + }, + "metadata": { + "description": "The type for the application gateway backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.applicationSecurityGroupType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application security group." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the application security group." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application security group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the application security group." + } + } + }, + "metadata": { + "description": "The type for the application security group.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.backendAddressPoolType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the backend address pool." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The properties of the backend address pool." + } + } + }, + "metadata": { + "description": "The type for a backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.inboundNatRuleType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the inbound NAT rule." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddressPool": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to backendAddressPool resource." + } + }, + "backendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." + } + }, + "enableFloatingIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." + } + }, + "enableTcpReset": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." + } + }, + "frontendIPConfiguration": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to frontend IP addresses." + } + }, + "frontendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeStart": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeEnd": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "All", + "Tcp", + "Udp" + ], + "nullable": true, + "metadata": { + "description": "Optional. The reference to the transport protocol used by the load balancing rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the inbound NAT rule." + } + } + }, + "metadata": { + "description": "The type for the inbound NAT rule.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.virtualNetworkTapType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the virtual network tap." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the virtual network tap." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the virtual network tap." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the virtual network tap." + } + } + }, + "metadata": { + "description": "The type for the virtual network tap.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_2.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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "_2.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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "_3.publicIPConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Public IP Address." + } + }, + "publicIPAddressResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the public IP address." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Diagnostic settings for the public IP address." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout in minutes." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the public IP address." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "ddosSettings": { + "$ref": "#/definitions/_2.ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "dnsSettings": { + "$ref": "#/definitions/_2.dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "publicIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address version." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "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." + } + }, + "publicIpNameSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name suffix of the public IP address resource." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "skuName": { + "type": "string", + "allowedValues": [ + "Basic", + "Standard" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU name of the public IP address." + } + }, + "skuTier": { + "type": "string", + "allowedValues": [ + "Global", + "Regional" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU tier of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "zones": { + "type": "array", + "allowedValues": [ + 1, + 2, + 3 + ], + "nullable": true, + "metadata": { + "description": "Optional. The zones of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "description": "The type for the public IP address configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "modules/nic-configuration.bicep" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "ipConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the IP configuration." + } + }, + "privateIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address allocation method." + } + }, + "privateIPAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The private IP address." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "loadBalancerBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.backendAddressPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer backend address pools." + } + }, + "applicationSecurityGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.applicationSecurityGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application security groups." + } + }, + "applicationGatewayBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.applicationGatewayBackendAddressPoolsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application gateway backend address pools." + } + }, + "gatewayLoadBalancer": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The gateway load balancer settings." + } + }, + "loadBalancerInboundNatRules": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.inboundNatRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer inbound NAT rules." + } + }, + "privateIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address version." + } + }, + "virtualNetworkTaps": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.virtualNetworkTapType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The virtual network taps." + } + }, + "pipConfiguration": { + "$ref": "#/definitions/_3.publicIPConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. The public IP address configuration." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the IP configuration." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "description": "The type for the IP configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "modules/nic-configuration.bicep" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "networkInterfaceIPConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the IP configuration." + } + }, + "privateIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The private IP address." + } + }, + "publicIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The public IP address." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "subResourceType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the sub resource." + } + } + }, + "metadata": { + "description": "The type for the sub resource.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine to be created. You should use a unique prefix to reduce name collisions in Active Directory." + } + }, + "computerName": { + "type": "string", + "defaultValue": "[parameters('name')]", + "metadata": { + "description": "Optional. Can be used if the computer name needs to be different from the Azure VM resource name. If not used, the resource name will be used as computer name." + } + }, + "vmSize": { + "type": "string", + "metadata": { + "description": "Required. Specifies the size for the VMs." + } + }, + "encryptionAtHost": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine. This will enable the encryption for all the disks including Resource/Temp disk at host itself. For security reasons, it is recommended to set encryptionAtHost to True. Restrictions: Cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." + } + }, + "securityType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "ConfidentialVM", + "TrustedLaunch" + ], + "metadata": { + "description": "Optional. Specifies the SecurityType of the virtual machine. It has to be set to any specified value to enable UefiSettings. The default behavior is: UefiSettings will not be enabled unless this property is set." + } + }, + "secureBootEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether secure boot should be enabled on the virtual machine. This parameter is part of the UefiSettings. SecurityType should be set to TrustedLaunch to enable UefiSettings." + } + }, + "vTpmEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether vTPM should be enabled on the virtual machine. This parameter is part of the UefiSettings. SecurityType should be set to TrustedLaunch to enable UefiSettings." + } + }, + "imageReference": { + "$ref": "#/definitions/imageReferenceType", + "metadata": { + "description": "Required. OS image reference. In case of marketplace images, it's the combination of the publisher, offer, sku, version attributes. In case of custom images it's the resource ID of the custom image." + } + }, + "plan": { + "$ref": "#/definitions/planType", + "nullable": true, + "metadata": { + "description": "Optional. Specifies information about the marketplace image used to create the virtual machine. This element is only used for marketplace images. Before you can use a marketplace image from an API, you must enable the image for programmatic use." + } + }, + "osDisk": { + "$ref": "#/definitions/osDiskType", + "metadata": { + "description": "Required. Specifies the OS disk. For security reasons, it is recommended to specify DiskEncryptionSet into the osDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." + } + }, + "dataDisks": { + "type": "array", + "items": { + "$ref": "#/definitions/dataDiskType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the data disks. For security reasons, it is recommended to specify DiskEncryptionSet into the dataDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." + } + }, + "ultraSSDEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The flag that enables or disables a capability to have one or more managed data disks with UltraSSD_LRS storage account type on the VM or VMSS. Managed disks with storage account type UltraSSD_LRS can be added to a virtual machine or virtual machine scale set only if this property is enabled." + } + }, + "hibernationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The flag that enables or disables hibernation capability on the VM." + } + }, + "adminUsername": { + "type": "securestring", + "metadata": { + "description": "Required. Administrator username." + } + }, + "adminPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. When specifying a Windows Virtual Machine, this value should be passed." + } + }, + "userData": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. UserData for the VM, which must be base-64 encoded. Customer should not pass any secrets in here." + } + }, + "customData": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom data associated to the VM, this value will be automatically converted into base64 to account for the expected VM format." + } + }, + "certificatesToBeInstalled": { + "type": "array", + "items": { + "$ref": "#/definitions/vaultSecretGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies set of certificates that should be installed onto the virtual machine." + } + }, + "priority": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Regular", + "Low", + "Spot" + ], + "metadata": { + "description": "Optional. Specifies the priority for the virtual machine." + } + }, + "evictionPolicy": { + "type": "string", + "defaultValue": "Deallocate", + "allowedValues": [ + "Deallocate", + "Delete" + ], + "metadata": { + "description": "Optional. Specifies the eviction policy for the low priority virtual machine." + } + }, + "maxPriceForLowPriorityVm": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the maximum price you are willing to pay for a low priority VM/VMSS. This price is in US Dollars." + } + }, + "dedicatedHostId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies resource ID about the dedicated host that the virtual machine resides in." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "RHEL_BYOS", + "SLES_BYOS", + "Windows_Client", + "Windows_Server", + "" + ], + "metadata": { + "description": "Optional. Specifies that the image or disk that is being used was licensed on-premises." + } + }, + "publicKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/publicKeyType" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. The list of SSH public keys used to authenticate with linux based VMs." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource. The system-assigned managed identity will automatically be enabled if extensionAadJoinConfig.enabled = \"True\"." + } + }, + "bootDiagnostics": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether boot diagnostics should be enabled on the Virtual Machine. Boot diagnostics will be enabled with a managed storage account if no bootDiagnosticsStorageAccountName value is provided. If bootDiagnostics and bootDiagnosticsStorageAccountName values are not provided, boot diagnostics will be disabled." + } + }, + "bootDiagnosticStorageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom storage account used to store boot diagnostic information. Boot diagnostics will be enabled with a custom storage account if a value is provided." + } + }, + "bootDiagnosticStorageAccountUri": { + "type": "string", + "defaultValue": "[format('.blob.{0}/', environment().suffixes.storage)]", + "metadata": { + "description": "Optional. Storage account boot diagnostic base URI." + } + }, + "proximityPlacementGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of a proximity placement group." + } + }, + "virtualMachineScaleSetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of a virtual machine scale set, where the VM should be added." + } + }, + "availabilitySetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of an availability set. Cannot be used in combination with availability zone nor scale set." + } + }, + "galleryApplications": { + "type": "array", + "items": { + "$ref": "#/definitions/vmGalleryApplicationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the gallery applications that should be made available to the VM/VMSS." + } + }, + "zone": { + "type": "int", + "allowedValues": [ + 0, + 1, + 2, + 3 + ], + "metadata": { + "description": "Required. If set to 1, 2 or 3, the availability zone for all VMs is hardcoded to that value. If zero, then availability zones is not used. Cannot be used in combination with availability set nor scale set." + } + }, + "nicConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/nicConfigurationType" + }, + "metadata": { + "description": "Required. Configures NICs and PIPs." + } + }, + "backupVaultName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Recovery service vault name to add VMs to backup." + } + }, + "backupVaultResourceGroup": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Optional. Resource group of the backup recovery service vault. If not provided the current resource group name is considered by default." + } + }, + "backupPolicyName": { + "type": "string", + "defaultValue": "DefaultPolicy", + "metadata": { + "description": "Optional. Backup policy the VMs should be using for backup. If not provided, it will use the DefaultPolicy from the backup recovery service vault." + } + }, + "autoShutdownConfig": { + "$ref": "#/definitions/autoShutDownConfigType", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for auto-shutdown." + } + }, + "maintenanceConfigurationResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource Id of a maintenance configuration for this VM." + } + }, + "allowExtensionOperations": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies whether extension operations should be allowed on the virtual machine. This may only be set to False when no extensions are present on the virtual machine." + } + }, + "extensionDomainJoinPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Required if name is specified. Password of the user specified in user parameter." + } + }, + "extensionDomainJoinConfig": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for the [Domain Join] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionAadJoinConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [AAD Join] extension. Must at least contain the [\"enabled\": true] property to be executed. To enroll in Intune, add the setting mdmId: \"0000000a-0000-0000-c000-000000000000\"." + } + }, + "extensionAntiMalwareConfig": { + "type": "object", + "defaultValue": "[if(equals(parameters('osType'), 'Windows'), createObject('enabled', true()), createObject('enabled', false()))]", + "metadata": { + "description": "Optional. The configuration for the [Anti Malware] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionMonitoringAgentConfig": { + "type": "object", + "defaultValue": { + "enabled": false, + "dataCollectionRuleAssociations": [] + }, + "metadata": { + "description": "Optional. The configuration for the [Monitoring Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionDependencyAgentConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Dependency Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionNetworkWatcherAgentConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Network Watcher Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionAzureDiskEncryptionConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Azure Disk Encryption] extension. Must at least contain the [\"enabled\": true] property to be executed. Restrictions: Cannot be enabled on disks that have encryption at host enabled. Managed disks encrypted using Azure Disk Encryption cannot be encrypted using customer-managed keys." + } + }, + "extensionDSCConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Desired State Configuration] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionCustomScriptConfig": { + "type": "object", + "defaultValue": { + "enabled": false, + "fileData": [] + }, + "metadata": { + "description": "Optional. The configuration for the [Custom Script] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionNvidiaGpuDriverWindows": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Nvidia Gpu Driver Windows] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionHostPoolRegistration": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Host Pool Registration] extension. Must at least contain the [\"enabled\": true] property to be executed. Needs a managed identy." + } + }, + "extensionGuestConfigurationExtension": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Guest Configuration] extension. Must at least contain the [\"enabled\": true] property to be executed. Needs a managed identy." + } + }, + "guestConfiguration": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The guest configuration for the virtual machine. Needs the Guest Configuration extension to be enabled." + } + }, + "extensionCustomScriptProtectedSetting": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. An object that contains the extension specific protected settings." + } + }, + "extensionGuestConfigurationExtensionProtectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. An object that contains the extension specific protected settings." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('u')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to generate a registration token." + } + }, + "sasTokenValidityLength": { + "type": "string", + "defaultValue": "PT8H", + "metadata": { + "description": "Optional. SAS token validity length to use to download files from storage accounts. Usage: 'PT8H' - valid for 8 hours; 'P5D' - valid for 5 days; 'P1Y' - valid for 1 year. When not provided, the SAS token will be valid for 8 hours." + } + }, + "osType": { + "type": "string", + "allowedValues": [ + "Windows", + "Linux" + ], + "metadata": { + "description": "Required. The chosen OS type." + } + }, + "disablePasswordAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether password authentication should be disabled." + } + }, + "provisionVMAgent": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether virtual machine agent should be provisioned on the virtual machine. When this property is not specified in the request body, default behavior is to set it to true. This will ensure that VM Agent is installed on the VM so that extensions can be added to the VM later." + } + }, + "enableAutomaticUpdates": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether Automatic Updates is enabled for the Windows virtual machine. Default value is true. When patchMode is set to Manual, this parameter must be set to false. For virtual machine scale sets, this property can be updated and updates will take effect on OS reprovisioning." + } + }, + "patchMode": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "AutomaticByPlatform", + "AutomaticByOS", + "Manual", + "ImageDefault", + "" + ], + "metadata": { + "description": "Optional. VM guest patching orchestration mode. 'AutomaticByOS' & 'Manual' are for Windows only, 'ImageDefault' for Linux only. Refer to 'https://learn.microsoft.com/en-us/azure/virtual-machines/automatic-vm-guest-patching'." + } + }, + "bypassPlatformSafetyChecksOnUserSchedule": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enables customer to schedule patching without accidental upgrades." + } + }, + "rebootSetting": { + "type": "string", + "defaultValue": "IfRequired", + "allowedValues": [ + "Always", + "IfRequired", + "Never", + "Unknown" + ], + "metadata": { + "description": "Optional. Specifies the reboot setting for all AutomaticByPlatform patch installation operations." + } + }, + "patchAssessmentMode": { + "type": "string", + "defaultValue": "ImageDefault", + "allowedValues": [ + "AutomaticByPlatform", + "ImageDefault" + ], + "metadata": { + "description": "Optional. VM guest patching assessment mode. Set it to 'AutomaticByPlatform' to enable automatically check for updates every 24 hours." + } + }, + "enableHotpatching": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables customers to patch their Azure VMs without requiring a reboot. For enableHotpatching, the 'provisionVMAgent' must be set to true and 'patchMode' must be set to 'AutomaticByPlatform'." + } + }, + "timeZone": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the time zone of the virtual machine. e.g. 'Pacific Standard Time'. Possible values can be `TimeZoneInfo.id` value from time zones returned by `TimeZoneInfo.GetSystemTimeZones`." + } + }, + "additionalUnattendContent": { + "type": "array", + "items": { + "$ref": "#/definitions/additionalUnattendContentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies additional XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup. Contents are defined by setting name, component name, and the pass in which the content is applied." + } + }, + "winRMListeners": { + "type": "array", + "items": { + "$ref": "#/definitions/winRMListenerType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the Windows Remote Management listeners. This enables remote Windows PowerShell." + } + }, + "configurationProfile": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The configuration profile of automanage. Either '/providers/Microsoft.Automanage/bestPractices/AzureBestPracticesProduction', 'providers/Microsoft.Automanage/bestPractices/AzureBestPracticesDevTest' or the resource Id of custom profile." + } + } + }, + "variables": { + "copy": [ + { + "name": "publicKeysFormatted", + "count": "[length(parameters('publicKeys'))]", + "input": { + "path": "[parameters('publicKeys')[copyIndex('publicKeysFormatted')].path]", + "keyData": "[parameters('publicKeys')[copyIndex('publicKeysFormatted')].keyData]" + } + }, + { + "name": "additionalUnattendContentFormatted", + "count": "[length(coalesce(parameters('additionalUnattendContent'), createArray()))]", + "input": { + "settingName": "[coalesce(parameters('additionalUnattendContent'), createArray())[copyIndex('additionalUnattendContentFormatted')].settingName]", + "content": "[coalesce(parameters('additionalUnattendContent'), createArray())[copyIndex('additionalUnattendContentFormatted')].content]", + "componentName": "Microsoft-Windows-Shell-Setup", + "passName": "OobeSystem" + } + }, + { + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "linuxConfiguration": { + "disablePasswordAuthentication": "[parameters('disablePasswordAuthentication')]", + "ssh": { + "publicKeys": "[variables('publicKeysFormatted')]" + }, + "provisionVMAgent": "[parameters('provisionVMAgent')]", + "patchSettings": "[if(and(parameters('provisionVMAgent'), or(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), equals(toLower(parameters('patchMode')), toLower('ImageDefault')))), createObject('patchMode', parameters('patchMode'), 'assessmentMode', parameters('patchAssessmentMode'), 'automaticByPlatformSettings', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), createObject('bypassPlatformSafetyChecksOnUserSchedule', parameters('bypassPlatformSafetyChecksOnUserSchedule'), 'rebootSetting', parameters('rebootSetting')), null())), null())]" + }, + "windowsConfiguration": { + "provisionVMAgent": "[parameters('provisionVMAgent')]", + "enableAutomaticUpdates": "[parameters('enableAutomaticUpdates')]", + "patchSettings": "[if(and(parameters('provisionVMAgent'), or(or(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), equals(toLower(parameters('patchMode')), toLower('AutomaticByOS'))), equals(toLower(parameters('patchMode')), toLower('Manual')))), createObject('patchMode', parameters('patchMode'), 'assessmentMode', parameters('patchAssessmentMode'), 'enableHotpatching', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), parameters('enableHotpatching'), false()), 'automaticByPlatformSettings', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), createObject('bypassPlatformSafetyChecksOnUserSchedule', parameters('bypassPlatformSafetyChecksOnUserSchedule'), 'rebootSetting', parameters('rebootSetting')), null())), null())]", + "timeZone": "[if(empty(parameters('timeZone')), null(), parameters('timeZone'))]", + "additionalUnattendContent": "[if(empty(parameters('additionalUnattendContent')), null(), variables('additionalUnattendContentFormatted'))]", + "winRM": "[if(not(empty(parameters('winRMListeners'))), createObject('listeners', parameters('winRMListeners')), null())]" + }, + "accountSasProperties": { + "signedServices": "b", + "signedPermission": "r", + "signedExpiry": "[dateTimeAdd(parameters('baseTime'), parameters('sasTokenValidityLength'))]", + "signedResourceTypes": "o", + "signedProtocol": "https" + }, + "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(if(parameters('extensionAadJoinConfig').enabled, true(), 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', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Data Operator for Managed Disks": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '959f8984-c045-4866-89c7-12bf9737be2e')]", + "Desktop Virtualization Power On Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '489581de-a3bd-480d-9518-53dea7416b33')]", + "Desktop Virtualization Power On Off Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '40c5ff49-9181-41f8-ae61-143b0e78555e')]", + "Desktop Virtualization Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c')]", + "DevTest Labs User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76283e04-6283-4c54-8f91-bcf1374a3c64')]", + "Disk Backup Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3e5e47e6-65f7-47ef-90b5-e5dd4d455f24')]", + "Disk Pool Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '60fc6e62-5479-42d4-8bf4-67625fcc2840')]", + "Disk Restore Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b50d9833-a0cb-478e-945f-707fcc997c13')]", + "Disk Snapshot Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7efff54f-a5b4-42b5-a1c5-5411624893ce')]", + "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')]", + "Virtual Machine Administrator Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1c0163c0-47e6-4577-8991-ea5c82e286e4')]", + "Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9980e02c-c2be-4d73-94e8-173b1dc7cf3c')]", + "Virtual Machine User Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb879df8-f326-4884-b1cf-06f3ad86be52')]", + "VM Scanner Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd24ecba3-c1f4-40fa-a7bb-4588a071e8fd')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.compute-virtualmachine.{0}.{1}', replace('0.15.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" + } + } + } + } + }, + "managedDataDisks": { + "copy": { + "name": "managedDataDisks", + "count": "[length(coalesce(parameters('dataDisks'), createArray()))]" + }, + "condition": "[empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'id'))]", + "type": "Microsoft.Compute/disks", + "apiVersion": "2024-03-02", + "name": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex(), 1), 2, '0')))]", + "location": "[parameters('location')]", + "sku": { + "name": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'storageAccountType')]" + }, + "properties": { + "diskSizeGB": "[coalesce(parameters('dataDisks'), createArray())[copyIndex()].diskSizeGB]", + "creationData": { + "createOption": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'createoption'), 'Empty')]" + }, + "diskIOPSReadWrite": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'diskIOPSReadWrite')]", + "diskMBpsReadWrite": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'diskMBpsReadWrite')]" + }, + "zones": "[if(and(not(equals(parameters('zone'), 0)), not(contains(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'storageAccountType'), 'ZRS'))), array(string(parameters('zone'))), null())]", + "tags": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "vm": { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2024-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "zones": "[if(not(equals(parameters('zone'), 0)), array(string(parameters('zone'))), null())]", + "plan": "[parameters('plan')]", + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "securityProfile": { + "encryptionAtHost": "[if(parameters('encryptionAtHost'), parameters('encryptionAtHost'), null())]", + "securityType": "[parameters('securityType')]", + "uefiSettings": "[if(equals(parameters('securityType'), 'TrustedLaunch'), createObject('secureBootEnabled', parameters('secureBootEnabled'), 'vTpmEnabled', parameters('vTpmEnabled')), null())]" + }, + "storageProfile": { + "copy": [ + { + "name": "dataDisks", + "count": "[length(coalesce(parameters('dataDisks'), createArray()))]", + "input": { + "lun": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'lun'), copyIndex('dataDisks'))]", + "name": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), last(split(coalesce(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.id, ''), '/')), coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0'))))]", + "createOption": "[if(or(not(equals(resourceId('Microsoft.Compute/disks', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))), null())), not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id')))), 'Attach', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'createoption'), 'Empty'))]", + "deleteOption": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), 'Detach', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'deleteOption'), 'Delete'))]", + "caching": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), 'None', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'caching'), 'ReadOnly'))]", + "managedDisk": { + "id": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'), resourceId('Microsoft.Compute/disks', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))))]", + "diskEncryptionSet": "[if(contains(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'diskEncryptionSet'), createObject('id', coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.diskEncryptionSet.id), null())]" + } + } + } + ], + "imageReference": "[parameters('imageReference')]", + "osDisk": { + "name": "[coalesce(tryGet(parameters('osDisk'), 'name'), format('{0}-disk-os-01', parameters('name')))]", + "createOption": "[coalesce(tryGet(parameters('osDisk'), 'createOption'), 'FromImage')]", + "deleteOption": "[coalesce(tryGet(parameters('osDisk'), 'deleteOption'), 'Delete')]", + "diffDiskSettings": "[if(empty(coalesce(tryGet(parameters('osDisk'), 'diffDiskSettings'), createObject())), null(), createObject('option', 'Local', 'placement', parameters('osDisk').diffDiskSettings.placement))]", + "diskSizeGB": "[tryGet(parameters('osDisk'), 'diskSizeGB')]", + "caching": "[coalesce(tryGet(parameters('osDisk'), 'caching'), 'ReadOnly')]", + "managedDisk": { + "storageAccountType": "[tryGet(parameters('osDisk').managedDisk, 'storageAccountType')]", + "diskEncryptionSet": { + "id": "[tryGet(parameters('osDisk').managedDisk, 'diskEncryptionSetResourceId')]" + } + } + } + }, + "additionalCapabilities": { + "ultraSSDEnabled": "[parameters('ultraSSDEnabled')]", + "hibernationEnabled": "[parameters('hibernationEnabled')]" + }, + "osProfile": { + "computerName": "[parameters('computerName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "customData": "[if(not(empty(parameters('customData'))), base64(parameters('customData')), null())]", + "windowsConfiguration": "[if(equals(parameters('osType'), 'Windows'), variables('windowsConfiguration'), null())]", + "linuxConfiguration": "[if(equals(parameters('osType'), 'Linux'), variables('linuxConfiguration'), null())]", + "secrets": "[parameters('certificatesToBeInstalled')]", + "allowExtensionOperations": "[parameters('allowExtensionOperations')]" + }, + "networkProfile": { + "copy": [ + { + "name": "networkInterfaces", + "count": "[length(parameters('nicConfigurations'))]", + "input": { + "properties": { + "deleteOption": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'deleteOption'), 'Delete')]", + "primary": "[if(equals(copyIndex('networkInterfaces'), 0), true(), false())]" + }, + "id": "[resourceId('Microsoft.Network/networkInterfaces', coalesce(tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'name'), format('{0}{1}', parameters('name'), tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'nicSuffix'))))]" + } + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": "[if(not(empty(parameters('bootDiagnosticStorageAccountName'))), true(), parameters('bootDiagnostics'))]", + "storageUri": "[if(not(empty(parameters('bootDiagnosticStorageAccountName'))), format('https://{0}{1}', parameters('bootDiagnosticStorageAccountName'), parameters('bootDiagnosticStorageAccountUri')), null())]" + } + }, + "applicationProfile": "[if(not(empty(parameters('galleryApplications'))), createObject('galleryApplications', parameters('galleryApplications')), null())]", + "availabilitySet": "[if(not(empty(parameters('availabilitySetResourceId'))), createObject('id', parameters('availabilitySetResourceId')), null())]", + "proximityPlacementGroup": "[if(not(empty(parameters('proximityPlacementGroupResourceId'))), createObject('id', parameters('proximityPlacementGroupResourceId')), null())]", + "virtualMachineScaleSet": "[if(not(empty(parameters('virtualMachineScaleSetResourceId'))), createObject('id', parameters('virtualMachineScaleSetResourceId')), null())]", + "priority": "[parameters('priority')]", + "evictionPolicy": "[if(and(not(empty(parameters('priority'))), not(equals(parameters('priority'), 'Regular'))), parameters('evictionPolicy'), null())]", + "billingProfile": "[if(and(not(empty(parameters('priority'))), not(empty(parameters('maxPriceForLowPriorityVm')))), createObject('maxPrice', json(parameters('maxPriceForLowPriorityVm'))), null())]", + "host": "[if(not(empty(parameters('dedicatedHostId'))), createObject('id', parameters('dedicatedHostId')), null())]", + "licenseType": "[if(not(empty(parameters('licenseType'))), parameters('licenseType'), null())]", + "userData": "[if(not(empty(parameters('userData'))), base64(parameters('userData')), null())]" + }, + "dependsOn": [ + "managedDataDisks", + "vm_nic" + ] + }, + "vm_configurationAssignment": { + "condition": "[not(empty(parameters('maintenanceConfigurationResourceId')))]", + "type": "Microsoft.Maintenance/configurationAssignments", + "apiVersion": "2023-04-01", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[format('{0}assignment', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]", + "resourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" + }, + "dependsOn": [ + "vm" + ] + }, + "vm_configurationProfileAssignment": { + "condition": "[not(empty(parameters('configurationProfile')))]", + "type": "Microsoft.Automanage/configurationProfileAssignments", + "apiVersion": "2022-05-04", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "default", + "properties": { + "configurationProfile": "[parameters('configurationProfile')]" + }, + "dependsOn": [ + "vm" + ] + }, + "vm_autoShutdownConfiguration": { + "condition": "[not(empty(parameters('autoShutdownConfig')))]", + "type": "Microsoft.DevTestLab/schedules", + "apiVersion": "2018-09-15", + "name": "[format('shutdown-computevm-{0}', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "status": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'status'), 'Disabled')]", + "targetResourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]", + "taskType": "ComputeVmShutdownTask", + "dailyRecurrence": { + "time": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'dailyRecurrenceTime'), '19:00')]" + }, + "timeZoneId": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'timeZone'), 'UTC')]", + "notificationSettings": "[if(contains(parameters('autoShutdownConfig'), 'notificationSettings'), createObject('status', coalesce(tryGet(parameters('autoShutdownConfig'), 'status'), 'Disabled'), 'emailRecipient', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'emailRecipient'), ''), 'notificationLocale', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'notificationLocale'), 'en'), 'webhookUrl', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'webhookUrl'), ''), 'timeInMinutes', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'timeInMinutes'), 30)), null())]" + }, + "dependsOn": [ + "vm" + ] + }, + "vm_dataCollectionRuleAssociations": { + "copy": { + "name": "vm_dataCollectionRuleAssociations", + "count": "[length(parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations)]" + }, + "condition": "[parameters('extensionMonitoringAgentConfig').enabled]", + "type": "Microsoft.Insights/dataCollectionRuleAssociations", + "apiVersion": "2023-03-11", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations[copyIndex()].name]", + "properties": { + "dataCollectionRuleId": "[parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations[copyIndex()].dataCollectionRuleResourceId]" + }, + "dependsOn": [ + "vm", + "vm_azureMonitorAgentExtension" + ] + }, + "AzureWindowsBaseline": { + "condition": "[not(empty(parameters('guestConfiguration')))]", + "type": "Microsoft.GuestConfiguration/guestConfigurationAssignments", + "apiVersion": "2020-06-25", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('guestConfiguration'), 'name'), 'AzureWindowsBaseline')]", + "location": "[parameters('location')]", + "properties": { + "guestConfiguration": "[parameters('guestConfiguration')]" + }, + "dependsOn": [ + "vm", + "vm_azureGuestConfigurationExtension" + ] + }, + "vm_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/virtualMachines/{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": [ + "vm" + ] + }, + "vm_roleAssignments": { + "copy": { + "name": "vm_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Compute/virtualMachines', 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": [ + "vm" + ] + }, + "vm_nic": { + "copy": { + "name": "vm_nic", + "count": "[length(parameters('nicConfigurations'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-Nic-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "networkInterfaceName": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'name'), format('{0}{1}', parameters('name'), tryGet(parameters('nicConfigurations')[copyIndex()], 'nicSuffix')))]" + }, + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "enableIPForwarding": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'enableIPForwarding'), false())]" + }, + "enableAcceleratedNetworking": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'enableAcceleratedNetworking'), true())]" + }, + "dnsServers": "[if(contains(parameters('nicConfigurations')[copyIndex()], 'dnsServers'), if(not(empty(tryGet(parameters('nicConfigurations')[copyIndex()], 'dnsServers'))), createObject('value', tryGet(parameters('nicConfigurations')[copyIndex()], 'dnsServers')), createObject('value', createArray())), createObject('value', createArray()))]", + "networkSecurityGroupResourceId": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'networkSecurityGroupResourceId'), '')]" + }, + "ipConfigurations": { + "value": "[parameters('nicConfigurations')[copyIndex()].ipConfigurations]" + }, + "lock": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'lock'), parameters('lock'))]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('nicConfigurations')[copyIndex()], 'diagnosticSettings')]" + }, + "roleAssignments": { + "value": "[tryGet(parameters('nicConfigurations')[copyIndex()], 'roleAssignments')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "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.34.44.8038", + "templateHash": "3333482934245501039" + } + }, + "definitions": { + "publicIPConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Public IP Address." + } + }, + "publicIPAddressResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the public IP address." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Diagnostic settings for the public IP address." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout in minutes." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the public IP address." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "ddosSettings": { + "$ref": "#/definitions/ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "dnsSettings": { + "$ref": "#/definitions/dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "publicIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address version." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "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." + } + }, + "publicIpNameSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name suffix of the public IP address resource." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "skuName": { + "type": "string", + "allowedValues": [ + "Basic", + "Standard" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU name of the public IP address." + } + }, + "skuTier": { + "type": "string", + "allowedValues": [ + "Global", + "Regional" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU tier of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "zones": { + "type": "array", + "allowedValues": [ + 1, + 2, + 3 + ], + "nullable": true, + "metadata": { + "description": "Optional. The zones of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the public IP address configuration." + } + }, + "ipConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the IP configuration." + } + }, + "privateIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address allocation method." + } + }, + "privateIPAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The private IP address." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "loadBalancerBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/backendAddressPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer backend address pools." + } + }, + "applicationSecurityGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationSecurityGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application security groups." + } + }, + "applicationGatewayBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationGatewayBackendAddressPoolsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application gateway backend address pools." + } + }, + "gatewayLoadBalancer": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The gateway load balancer settings." + } + }, + "loadBalancerInboundNatRules": { + "type": "array", + "items": { + "$ref": "#/definitions/inboundNatRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer inbound NAT rules." + } + }, + "privateIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address version." + } + }, + "virtualNetworkTaps": { + "type": "array", + "items": { + "$ref": "#/definitions/virtualNetworkTapType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The virtual network taps." + } + }, + "pipConfiguration": { + "$ref": "#/definitions/publicIPConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. The public IP address configuration." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the IP configuration." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the IP configuration." + } + }, + "applicationGatewayBackendAddressPoolsType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddresses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. IP address of the backend address." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN of the backend address." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Backend addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application gateway backend address pool." + } + } + }, + "metadata": { + "description": "The type for the application gateway backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "applicationSecurityGroupType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application security group." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the application security group." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application security group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the application security group." + } + } + }, + "metadata": { + "description": "The type for the application security group.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "backendAddressPoolType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the backend address pool." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The properties of the backend address pool." + } + } + }, + "metadata": { + "description": "The type for a backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "inboundNatRuleType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the inbound NAT rule." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddressPool": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to backendAddressPool resource." + } + }, + "backendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." + } + }, + "enableFloatingIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." + } + }, + "enableTcpReset": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." + } + }, + "frontendIPConfiguration": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to frontend IP addresses." + } + }, + "frontendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeStart": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeEnd": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "All", + "Tcp", + "Udp" + ], + "nullable": true, + "metadata": { + "description": "Optional. The reference to the transport protocol used by the load balancing rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the inbound NAT rule." + } + } + }, + "metadata": { + "description": "The type for the inbound NAT rule.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "ipTagType": { + "type": "object", + "properties": { + "ipTagType": { + "type": "string", + "metadata": { + "description": "Required. The IP tag type." + } + }, + "tag": { + "type": "string", + "metadata": { + "description": "Required. The IP tag." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "networkInterfaceIPConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the IP configuration." + } + }, + "privateIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The private IP address." + } + }, + "publicIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The public IP address." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "subResourceType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the sub resource." + } + } + }, + "metadata": { + "description": "The type for the sub resource.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "virtualNetworkTapType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the virtual network tap." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the virtual network tap." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the virtual network tap." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the virtual network tap." + } + } + }, + "metadata": { + "description": "The type for the virtual network tap.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + } + }, + "parameters": { + "networkInterfaceName": { + "type": "string" + }, + "virtualMachineName": { + "type": "string" + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableIPForwarding": { + "type": "bool", + "defaultValue": false + }, + "enableAcceleratedNetworking": { + "type": "bool", + "defaultValue": false + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [] + }, + "enableTelemetry": { + "type": "bool", + "metadata": { + "description": "Required. Enable telemetry via a Globally Unique Identifier (GUID)." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The network security group (NSG) to attach to the network interface." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "resources": { + "networkInterface_publicIPAddresses": { + "copy": { + "name": "networkInterface_publicIPAddresses", + "count": "[length(parameters('ipConfigurations'))]" + }, + "condition": "[and(not(empty(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'))), empty(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAddressResourceId')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-publicIP-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'name'), format('{0}{1}', parameters('virtualMachineName'), tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIpNameSuffix')))]" + }, + "diagnosticSettings": { + "value": "[coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'diagnosticSettings'), tryGet(parameters('ipConfigurations')[copyIndex()], 'diagnosticSettings'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "idleTimeoutInMinutes": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'idleTimeoutInMinutes')]" + }, + "ddosSettings": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'ddosSettings')]" + }, + "dnsSettings": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'dnsSettings')]" + }, + "publicIPAddressVersion": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAddressVersion')]" + }, + "publicIPAllocationMethod": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAllocationMethod')]" + }, + "publicIpPrefixResourceId": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIpPrefixResourceId')]" + }, + "roleAssignments": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'roleAssignments')]" + }, + "skuName": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'skuName')]" + }, + "skuTier": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'skuTier')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "zones": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'zones')]" + }, + "enableTelemetry": { + "value": "[coalesce(coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'enableTelemetry'), tryGet(parameters('ipConfigurations')[copyIndex()], '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.33.93.31351", + "templateHash": "5168739580767459761" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address." + }, + "definitions": { + "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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipTagType": { + "type": "object", + "properties": { + "ipTagType": { + "type": "string", + "metadata": { + "description": "Required. The IP tag type." + } + }, + "tag": { + "type": "string", + "metadata": { + "description": "Required. The IP tag." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "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." + } + }, + "ipTags": { + "type": "array", + "items": { + "$ref": "#/definitions/ipTagType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of tags associated with the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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": "[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.8.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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2024-05-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": "[parameters('ipTags')]" + } + }, + "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', '2024-05-01', 'full').location]" + } + } + } + } + }, + "networkInterface": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-NetworkInterface', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('networkInterfaceName')]" + }, + "ipConfigurations": { + "copy": [ + { + "name": "value", + "count": "[length(parameters('ipConfigurations'))]", + "input": "[createObject('name', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'name'), 'privateIPAllocationMethod', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAllocationMethod'), 'privateIPAddress', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAddress'), 'publicIPAddressResourceId', if(not(empty(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'))), if(not(contains(coalesce(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), createObject()), 'publicIPAddressResourceId')), resourceId('Microsoft.Network/publicIPAddresses', coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), 'name'), format('{0}{1}', parameters('virtualMachineName'), tryGet(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), 'publicIpNameSuffix')))), tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration', 'publicIPAddressResourceId')), null()), 'subnetResourceId', parameters('ipConfigurations')[copyIndex('value')].subnetResourceId, 'loadBalancerBackendAddressPools', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'loadBalancerBackendAddressPools'), 'applicationSecurityGroups', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'applicationSecurityGroups'), 'applicationGatewayBackendAddressPools', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'applicationGatewayBackendAddressPools'), 'gatewayLoadBalancer', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'gatewayLoadBalancer'), 'loadBalancerInboundNatRules', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'loadBalancerInboundNatRules'), 'privateIPAddressVersion', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAddressVersion'), 'virtualNetworkTaps', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'virtualNetworkTaps'))]" + } + ] + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "diagnosticSettings": { + "value": "[parameters('diagnosticSettings')]" + }, + "dnsServers": { + "value": "[parameters('dnsServers')]" + }, + "enableAcceleratedNetworking": { + "value": "[parameters('enableAcceleratedNetworking')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "enableIPForwarding": { + "value": "[parameters('enableIPForwarding')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "networkSecurityGroupResourceId": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('value', parameters('networkSecurityGroupResourceId')), createObject('value', ''))]", + "roleAssignments": { + "value": "[parameters('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.34.44.8038", + "templateHash": "8196054567469390015" + }, + "name": "Network Interface", + "description": "This module deploys a Network Interface." + }, + "definitions": { + "networkInterfaceIPConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the IP configuration." + } + }, + "privateIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address allocation method." + } + }, + "privateIPAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The private IP address." + } + }, + "publicIPAddressResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the public IP address." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "loadBalancerBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/backendAddressPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of load balancer backend address pools." + } + }, + "loadBalancerInboundNatRules": { + "type": "array", + "items": { + "$ref": "#/definitions/inboundNatRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of references of LoadBalancerInboundNatRules." + } + }, + "applicationSecurityGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationSecurityGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the IP configuration is included." + } + }, + "applicationGatewayBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationGatewayBackendAddressPoolsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The reference to Application Gateway Backend Address Pools." + } + }, + "gatewayLoadBalancer": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The reference to gateway load balancer frontend IP." + } + }, + "privateIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. Whether the specific IP configuration is IPv4 or IPv6." + } + }, + "virtualNetworkTaps": { + "type": "array", + "items": { + "$ref": "#/definitions/virtualNetworkTapType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The reference to Virtual Network Taps." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The resource ID of the deployed resource." + } + }, + "backendAddressPoolType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the backend address pool." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The properties of the backend address pool." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a backend address pool." + } + }, + "applicationSecurityGroupType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application security group." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the application security group." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application security group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the application security group." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the application security group." + } + }, + "applicationGatewayBackendAddressPoolsType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddresses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. IP address of the backend address." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN of the backend address." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Backend addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application gateway backend address pool." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the application gateway backend address pool." + } + }, + "subResourceType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the sub resource." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the sub resource." + } + }, + "inboundNatRuleType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the inbound NAT rule." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddressPool": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to backendAddressPool resource." + } + }, + "backendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." + } + }, + "enableFloatingIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." + } + }, + "enableTcpReset": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." + } + }, + "frontendIPConfiguration": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to frontend IP addresses." + } + }, + "frontendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeStart": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeEnd": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "All", + "Tcp", + "Udp" + ], + "nullable": true, + "metadata": { + "description": "Optional. The reference to the transport protocol used by the load balancing rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the inbound NAT rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the inbound NAT rule." + } + }, + "virtualNetworkTapType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the virtual network tap." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the virtual network tap." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the virtual network tap." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the virtual network tap." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the virtual network tap." + } + }, + "networkInterfaceIPConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the IP configuration." + } + }, + "privateIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The private IP address." + } + }, + "publicIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The public IP address." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network interface." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "enableIPForwarding": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether IP forwarding is enabled on this network interface." + } + }, + "enableAcceleratedNetworking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If the network interface is accelerated networking enabled." + } + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. List of DNS servers IP addresses. Use 'AzureProvidedDNS' to switch to azure provided DNS resolution. 'AzureProvidedDNS' value cannot be combined with other IPs, it must be the only value in dnsServers collection." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The network security group (NSG) to attach to the network interface." + } + }, + "auxiliaryMode": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "Floating", + "MaxConnections", + "None" + ], + "metadata": { + "description": "Optional. Auxiliary mode of Network Interface resource. Not all regions are enabled for Auxiliary Mode Nic." + } + }, + "auxiliarySku": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "A1", + "A2", + "A4", + "A8", + "None" + ], + "metadata": { + "description": "Optional. Auxiliary sku of Network Interface resource. Not all regions are enabled for Auxiliary Mode Nic." + } + }, + "disableTcpStateTracking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to disable tcp state tracking. Subscription must be registered for the Microsoft.Network/AllowDisableTcpStateTracking feature before this property can be set to true." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationType" + }, + "metadata": { + "description": "Required. A list of IPConfigurations of the network interface." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "publicIp": { + "copy": { + "name": "publicIp", + "count": "[length(parameters('ipConfigurations'))]" + }, + "condition": "[and(contains(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), not(equals(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), null())))]", + "existing": true, + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2024-05-01", + "resourceGroup": "[split(coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), ''), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), ''), '/'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-networkinterface.{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" + } + } + } + } + }, + "networkInterface": { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2024-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "ipConfigurations", + "count": "[length(parameters('ipConfigurations'))]", + "input": { + "name": "[coalesce(tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'name'), format('ipconfig0{0}', add(copyIndex('ipConfigurations'), 1)))]", + "properties": { + "primary": "[if(equals(copyIndex('ipConfigurations'), 0), true(), false())]", + "privateIPAllocationMethod": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAllocationMethod')]", + "privateIPAddress": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAddress')]", + "publicIPAddress": "[if(contains(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId'), if(not(equals(tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId'), null())), createObject('id', tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId')), null()), null())]", + "subnet": { + "id": "[parameters('ipConfigurations')[copyIndex('ipConfigurations')].subnetResourceId]" + }, + "loadBalancerBackendAddressPools": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'loadBalancerBackendAddressPools')]", + "applicationSecurityGroups": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'applicationSecurityGroups')]", + "applicationGatewayBackendAddressPools": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'applicationGatewayBackendAddressPools')]", + "gatewayLoadBalancer": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'gatewayLoadBalancer')]", + "loadBalancerInboundNatRules": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'loadBalancerInboundNatRules')]", + "privateIPAddressVersion": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAddressVersion')]", + "virtualNetworkTaps": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'virtualNetworkTaps')]" + } + } + } + ], + "auxiliaryMode": "[parameters('auxiliaryMode')]", + "auxiliarySku": "[parameters('auxiliarySku')]", + "disableTcpStateTracking": "[parameters('disableTcpStateTracking')]", + "dnsSettings": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', parameters('dnsServers')), null())]", + "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", + "enableIPForwarding": "[parameters('enableIPForwarding')]", + "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]" + } + }, + "networkInterface_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/networkInterfaces/{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": [ + "networkInterface" + ] + }, + "networkInterface_diagnosticSettings": { + "copy": { + "name": "networkInterface_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/networkInterfaces/{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": [ + "networkInterface" + ] + }, + "networkInterface_roleAssignments": { + "copy": { + "name": "networkInterface_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkInterfaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkInterfaces', 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": [ + "networkInterface" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed resource." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed resource." + }, + "value": "[resourceId('Microsoft.Network/networkInterfaces', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed resource." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('networkInterface', '2024-05-01', 'full').location]" + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" + }, + "metadata": { + "description": "The list of IP configurations of the network interface." + }, + "copy": { + "count": "[length(parameters('ipConfigurations'))]", + "input": { + "name": "[reference('networkInterface').ipConfigurations[copyIndex()].name]", + "privateIP": "[coalesce(tryGet(reference('networkInterface').ipConfigurations[copyIndex()].properties, 'privateIPAddress'), '')]", + "publicIP": "[if(and(contains(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), not(equals(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), null()))), coalesce(reference(format('publicIp[{0}]', copyIndex())).ipAddress, ''), '')]" + } + } + } + } + } + }, + "dependsOn": [ + "networkInterface_publicIPAddresses" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the network interface." + }, + "value": "[reference('networkInterface').outputs.name.value]" + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" + }, + "metadata": { + "description": "The list of IP configurations of the network interface." + }, + "value": "[reference('networkInterface').outputs.ipConfigurations.value]" + } + } + } + } + }, + "vm_domainJoinExtension": { + "condition": "[and(contains(parameters('extensionDomainJoinConfig'), 'enabled'), parameters('extensionDomainJoinConfig').enabled)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-DomainJoin', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'name'), 'DomainJoin')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Compute" + }, + "type": { + "value": "JsonADDomainExtension" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'typeHandlerVersion'), '1.3')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[parameters('extensionDomainJoinConfig').settings]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'tags'), parameters('tags'))]" + }, + "protectedSettings": { + "value": { + "Password": "[parameters('extensionDomainJoinPassword')]" + } + } + }, + "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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm" + ] + }, + "vm_aadJoinExtension": { + "condition": "[parameters('extensionAadJoinConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-AADLogin', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'name'), 'AADLogin')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.ActiveDirectory" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AADLoginForWindows'), createObject('value', 'AADSSHLoginforLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '2.0', '1.0'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_domainJoinExtension" + ] + }, + "vm_microsoftAntiMalwareExtension": { + "condition": "[parameters('extensionAntiMalwareConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-MicrosoftAntiMalware', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'name'), 'MicrosoftAntiMalware')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Security" + }, + "type": { + "value": "IaaSAntimalware" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'typeHandlerVersion'), '1.3')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'settings'), createObject('AntimalwareEnabled', 'true', 'Exclusions', createObject(), 'RealtimeProtectionEnabled', 'true', 'ScheduledScanSettings', createObject('day', '7', 'isEnabled', 'true', 'scanType', 'Quick', 'time', '120')))]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_aadJoinExtension" + ] + }, + "vm_azureMonitorAgentExtension": { + "condition": "[parameters('extensionMonitoringAgentConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-AzureMonitorAgent', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'name'), 'AzureMonitorAgent')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Monitor" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AzureMonitorWindowsAgent'), createObject('value', 'AzureMonitorLinuxAgent'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.22', '1.29'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_microsoftAntiMalwareExtension" + ] + }, + "vm_dependencyAgentExtension": { + "condition": "[parameters('extensionDependencyAgentConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-DependencyAgent', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'name'), 'DependencyAgent')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Monitoring.DependencyAgent" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'DependencyAgentWindows'), createObject('value', 'DependencyAgentLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'typeHandlerVersion'), '9.10')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'enableAutomaticUpgrade'), true())]" + }, + "settings": { + "value": { + "enableAMA": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'enableAMA'), true())]" + } + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_azureMonitorAgentExtension" + ] + }, + "vm_networkWatcherAgentExtension": { + "condition": "[parameters('extensionNetworkWatcherAgentConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-NetworkWatcherAgent', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'name'), 'NetworkWatcherAgent')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.NetworkWatcher" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'NetworkWatcherAgentWindows'), createObject('value', 'NetworkWatcherAgentLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'typeHandlerVersion'), '1.4')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_dependencyAgentExtension" + ] + }, + "vm_desiredStateConfigurationExtension": { + "condition": "[parameters('extensionDSCConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-DesiredStateConfiguration', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'name'), 'DesiredStateConfiguration')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Powershell" + }, + "type": { + "value": "DSC" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'typeHandlerVersion'), '2.77')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'tags'), parameters('tags'))]" + }, + "protectedSettings": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'protectedSettings'), 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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_networkWatcherAgentExtension" + ] + }, + "vm_customScriptExtension": { + "condition": "[parameters('extensionCustomScriptConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-CustomScriptExtension', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'name'), 'CustomScriptExtension')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'Microsoft.Compute'), createObject('value', 'Microsoft.Azure.Extensions'))]", + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'CustomScriptExtension'), createObject('value', 'CustomScript'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.10', '2.1'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": { + "copy": [ + { + "name": "fileUris", + "count": "[length(parameters('extensionCustomScriptConfig').fileData)]", + "input": "[if(contains(parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')], 'storageAccountId'), format('{0}?{1}', parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].uri, listAccountSas(parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].storageAccountId, '2019-04-01', variables('accountSasProperties')).accountSasToken), parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].uri)]" + } + ] + } + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'tags'), parameters('tags'))]" + }, + "protectedSettings": { + "value": "[parameters('extensionCustomScriptProtectedSetting')]" + } + }, + "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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_desiredStateConfigurationExtension" + ] + }, + "vm_azureDiskEncryptionExtension": { + "condition": "[parameters('extensionAzureDiskEncryptionConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-AzureDiskEncryption', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'name'), 'AzureDiskEncryption')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Security" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AzureDiskEncryption'), createObject('value', 'AzureDiskEncryptionForLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '2.2', '1.1'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "forceUpdateTag": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'forceUpdateTag'), '1.0')]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_customScriptExtension" + ] + }, + "vm_nvidiaGpuDriverWindowsExtension": { + "condition": "[parameters('extensionNvidiaGpuDriverWindows').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-NvidiaGpuDriverWindows', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'name'), 'NvidiaGpuDriverWindows')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.HpcCompute" + }, + "type": { + "value": "NvidiaGpuDriverWindows" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'typeHandlerVersion'), '1.4')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'enableAutomaticUpgrade'), false())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_azureDiskEncryptionExtension" + ] + }, + "vm_hostPoolRegistrationExtension": { + "condition": "[parameters('extensionHostPoolRegistration').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-HostPoolRegistration', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'name'), 'HostPoolRegistration')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.PowerShell" + }, + "type": { + "value": "DSC" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'typeHandlerVersion'), '2.77')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": { + "modulesUrl": "[parameters('extensionHostPoolRegistration').modulesUrl]", + "configurationFunction": "[parameters('extensionHostPoolRegistration').configurationFunction]", + "properties": { + "hostPoolName": "[parameters('extensionHostPoolRegistration').hostPoolName]", + "registrationInfoToken": "[parameters('extensionHostPoolRegistration').registrationInfoToken]", + "aadJoin": true + }, + "supressFailures": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'supressFailures'), false())]" + } + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_nvidiaGpuDriverWindowsExtension" + ] + }, + "vm_azureGuestConfigurationExtension": { + "condition": "[parameters('extensionGuestConfigurationExtension').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-GuestConfiguration', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": "[if(coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'name'), equals(parameters('osType'), 'Windows')), createObject('value', 'AzurePolicyforWindows'), createObject('value', 'AzurePolicyforLinux'))]", + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.GuestConfiguration" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'ConfigurationforWindows'), createObject('value', 'ConfigurationForLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.0', '1.0'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'enableAutomaticUpgrade'), true())]" + }, + "forceUpdateTag": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'forceUpdateTag'), '1.0')]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'supressFailures'), false())]" + }, + "protectedSettings": { + "value": "[parameters('extensionGuestConfigurationExtensionProtectedSettings')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_hostPoolRegistrationExtension" + ] + }, + "vm_backup": { + "condition": "[not(empty(parameters('backupVaultName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-Backup', uniqueString(deployment().name, parameters('location')))]", + "resourceGroup": "[parameters('backupVaultResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('vm;iaasvmcontainerv2;{0};{1}', resourceGroup().name, parameters('name'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "policyId": { + "value": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', parameters('backupVaultName'), parameters('backupPolicyName'))]" + }, + "protectedItemType": { + "value": "Microsoft.Compute/virtualMachines" + }, + "protectionContainerName": { + "value": "[format('iaasvmcontainer;iaasvmcontainerv2;{0};{1}', resourceGroup().name, parameters('name'))]" + }, + "recoveryVaultName": { + "value": "[parameters('backupVaultName')]" + }, + "sourceResourceId": { + "value": "[resourceId('Microsoft.Compute/virtualMachines', 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.34.44.8038", + "templateHash": "7743264001610407207" + }, + "name": "Recovery Service Vaults Protection Container Protected Item", + "description": "This module deploys a Recovery Services Vault Protection Container Protected Item." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the resource." + } + }, + "protectionContainerName": { + "type": "string", + "metadata": { + "description": "Conditional. Name of the Azure Recovery Service Vault Protection Container. Required if the template is used in a standalone deployment." + } + }, + "recoveryVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Azure Recovery Service Vault. Required if the template is used in a standalone deployment." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "protectedItemType": { + "type": "string", + "allowedValues": [ + "AzureFileShareProtectedItem", + "AzureVmWorkloadSAPAseDatabase", + "AzureVmWorkloadSAPHanaDatabase", + "AzureVmWorkloadSQLDatabase", + "DPMProtectedItem", + "GenericProtectedItem", + "MabFileFolderProtectedItem", + "Microsoft.ClassicCompute/virtualMachines", + "Microsoft.Compute/virtualMachines", + "Microsoft.Sql/servers/databases" + ], + "metadata": { + "description": "Required. The backup item type." + } + }, + "policyId": { + "type": "string", + "metadata": { + "description": "Required. ID of the backup policy with which this item is backed up." + } + }, + "sourceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource to back up." + } + } + }, + "resources": [ + { + "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems", + "apiVersion": "2023-01-01", + "name": "[format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "protectedItemType": "[parameters('protectedItemType')]", + "policyId": "[parameters('policyId')]", + "sourceResourceId": "[parameters('sourceResourceId')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the protected item was created in." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the protected item." + }, + "value": "[resourceId('Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems', split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[0], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[1], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[2], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[3])]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The Name of the protected item." + }, + "value": "[format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_azureGuestConfigurationExtension" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the VM." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the VM." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the VM was created in." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('vm', '2024-07-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('vm', '2024-07-01', 'full').location]" + }, + "nicConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/nicConfigurationOutputType" + }, + "metadata": { + "description": "The list of NIC configurations of the virtual machine." + }, + "copy": { + "count": "[length(parameters('nicConfigurations'))]", + "input": { + "name": "[reference(format('vm_nic[{0}]', copyIndex())).outputs.name.value]", + "ipConfigurations": "[reference(format('vm_nic[{0}]', copyIndex())).outputs.ipConfigurations.value]" + } + } + } + } + } + }, + "dependsOn": [ + "nsg", + "subnetResource" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "value": "[reference('vm').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "value": "[reference('vm').outputs.name.value]" + }, + "location": { + "type": "string", + "value": "[reference('vm').outputs.location.value]" + }, + "subnetId": { + "type": "string", + "value": "[reference('subnetResource').outputs.resourceId.value]" + }, + "subnetName": { + "type": "string", + "value": "[reference('subnetResource').outputs.name.value]" + }, + "nsgId": { + "type": "string", + "value": "[reference('nsg').outputs.resourceId.value]" + }, + "nsgName": { + "type": "string", + "value": "[reference('nsg').outputs.name.value]" + } + } + } + }, + "dependsOn": [ + "virtualNetwork" + ] } - ], + }, "outputs": { - "aiServicesPrincipalId": { + "vnetName": { "type": "string", - "metadata": { - "description": "Principal ID of the AI Services resource." - }, - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), '2025-04-01-preview', 'full').identity.principalId]" + "value": "[reference('virtualNetwork').outputs.name.value]" }, - "aiProjectPrincipalId": { + "vnetResourceId": { "type": "string", - "metadata": { - "description": "Principal ID of the AI Project resource if defined." + "value": "[reference('virtualNetwork').outputs.resourceId.value]" + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOutputType" }, - "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" + "value": "[reference('virtualNetwork').outputs.subnets.value]" + }, + "bastionSubnetId": { + "type": "string", + "value": "[reference('bastionHost').outputs.subnetId.value]" + }, + "bastionSubnetName": { + "type": "string", + "value": "[reference('bastionHost').outputs.subnetName.value]" + }, + "bastionHostId": { + "type": "string", + "value": "[reference('bastionHost').outputs.resourceId.value]" + }, + "bastionHostName": { + "type": "string", + "value": "[reference('bastionHost').outputs.name.value]" + }, + "jumpboxSubnetName": { + "type": "string", + "value": "[reference('jumpbox').outputs.subnetName.value]" + }, + "jumpboxSubnetId": { + "type": "string", + "value": "[reference('jumpbox').outputs.subnetId.value]" + }, + "jumpboxName": { + "type": "string", + "value": "[reference('jumpbox').outputs.name.value]" + }, + "jumpboxResourceId": { + "type": "string", + "value": "[reference('jumpbox').outputs.resourceId.value]" } } } - }, - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - ] + } } ], "outputs": { - "keyvaultName": { - "type": "string", - "metadata": { - "description": "Contains Name of KeyVault." - }, - "value": "[variables('keyvaultName')]" - }, - "keyvaultId": { - "type": "string", - "metadata": { - "description": "Contains KeyVault ID." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" - }, - "resourceGroupNameFoundry": { - "type": "string", - "metadata": { - "description": "Contains AI Foundry ResourceGroup Name" - }, - "value": "[if(not(empty(variables('existingAIServiceResourceGroup'))), variables('existingAIServiceResourceGroup'), resourceGroup().name)]" - }, - "aiFoundryProjectEndpoint": { - "type": "string", - "metadata": { - "description": "Contains Name of AI Foundry Project Endpoint." - }, - "value": "[if(not(empty(variables('existingProjEndpoint'))), variables('existingProjEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview').endpoints['AI Foundry API'])]" - }, - "aoaiEndpoint": { - "type": "string", - "metadata": { - "description": "Contains AI Endpoint." - }, - "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoints['OpenAI Language Model Instance API'])]" - }, - "aiFoundryName": { - "type": "string", - "metadata": { - "description": "Contains Name of AI Foundry." - }, - "value": "[if(not(empty(variables('existingAIFoundryName'))), variables('existingAIFoundryName'), variables('aiFoundryName'))]" - }, - "aiFoundryId": { - "type": "string", - "value": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', variables('existingAIFoundryName')), resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')))]" - }, - "aiSearchName": { - "type": "string", - "metadata": { - "description": "Contains AI Search Name." - }, - "value": "[variables('aiSearchName')]" - }, - "aiSearchId": { - "type": "string", - "metadata": { - "description": "Contains AI SearchID." - }, - "value": "[resourceId('Microsoft.Search/searchServices', variables('aiSearchName'))]" - }, - "aiSearchTarget": { - "type": "string", - "metadata": { - "description": "Contains AI Search Target." - }, - "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" - }, - "aiSearchService": { - "type": "string", - "metadata": { - "description": "Contains AI Search Service." - }, - "value": "[variables('aiSearchName')]" - }, - "aiFoundryProjectName": { - "type": "string", - "metadata": { - "description": "Contains Name of AI Foundry Project." - }, - "value": "[if(not(empty(variables('existingAIProjectName'))), variables('existingAIProjectName'), variables('aiProjectName'))]" - }, - "applicationInsightsId": { - "type": "string", - "metadata": { - "description": "Contains Application Insights ID." - }, - "value": "[resourceId('Microsoft.Insights/components', variables('applicationInsightsName'))]" - }, - "instrumentationKey": { + "vnetName": { "type": "string", "metadata": { - "description": "The Instrumentation Key for the Application Insights resource." + "description": "Name of the Virtual Network resource." }, - "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').InstrumentationKey]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.vnetName.value]" }, - "logAnalyticsWorkspaceResourceName": { + "vnetResourceId": { "type": "string", "metadata": { - "description": "Contains Log Analytics Workspace Resource Name." + "description": "Resource ID of the Virtual Network." }, - "value": "[if(variables('useExisting'), variables('existingLawName'), variables('workspaceName'))]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.vnetResourceId.value]" }, - "logAnalyticsWorkspaceResourceGroup": { + "subnetWebResourceId": { "type": "string", "metadata": { - "description": "Contains Log Analytics Workspace ResourceGroup Name." + "description": "Resource ID of the \"web\" subnet." }, - "value": "[if(variables('useExisting'), variables('existingLawResourceGroup'), resourceGroup().name)]" + "value": "[coalesce(tryGet(first(filter(reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.subnets.value, lambda('s', equals(lambdaVariables('s').name, 'web')))), 'resourceId'), '')]" }, - "applicationInsightsConnectionString": { + "subnetPrivateEndpointsResourceId": { "type": "string", "metadata": { - "description": "Contains Application Insights Connection String." + "description": "Resource ID of the \"peps\" subnet for Private Endpoints." }, - "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').ConnectionString]" + "value": "[coalesce(tryGet(first(filter(reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.subnets.value, lambda('s', equals(lambdaVariables('s').name, 'peps')))), 'resourceId'), '')]" }, - "aiSearchFoundryConnectionName": { + "bastionResourceId": { "type": "string", "metadata": { - "description": "Contains AI Search Foundry Connection Name." + "description": "Resource ID of the Bastion Host." }, - "value": "[variables('aiSearchConnectionName')]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.bastionHostId.value]" }, - "aiAppInsightsFoundryConnectionName": { + "jumpboxResourceId": { "type": "string", "metadata": { - "description": "Contains AI Foundry App Insights Connection Name." - }, - "value": "[variables('aiAppInsightConnectionName')]" - }, - "aiModelDeployments": { - "type": "array", - "metadata": { - "description": "Contains AI Model Deployments" + "description": "Resource ID of the Jumpbox VM." }, - "value": "[variables('aiModelDeployments')]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.jumpboxResourceId.value]" } } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault')]" + "logAnalyticsWorkspace" ] }, - { + "avmPrivateDnsZones": { + "copy": { + "name": "avmPrivateDnsZones", + "count": "[length(variables('privateDnsZones'))]", + "mode": "serial", + "batchSize": 5 + }, + "condition": "[and(parameters('enablePrivateNetworking'), or(empty(parameters('existingFoundryProjectResourceId')), not(contains(variables('aiRelatedDnsZoneIndices'), copyIndex()))))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "deploy_cosmos_db", - "resourceGroup": "[resourceGroup().name]", + "name": "[format('avm.res.network.private-dns-zone.{0}', if(contains(variables('privateDnsZones')[copyIndex()], 'azurecontainerapps.io'), 'containerappenv', split(variables('privateDnsZones')[copyIndex()], '.')[1]))]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "solutionLocation": { - "value": "[parameters('cosmosLocation')]" - }, - "cosmosDBName": { - "value": "[format('cosmos-{0}', variables('solutionSuffix'))]" + "name": { + "value": "[variables('privateDnsZones')[copyIndex()]]" }, "tags": { "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "virtualNetworkLinks": { + "value": [ + { + "name": "[take(format('vnetlink-{0}-{1}', reference('network').outputs.vnetName.value, split(variables('privateDnsZones')[copyIndex()], '.')[1]), 80)]", + "virtualNetworkResourceId": "[reference('network').outputs.subnetPrivateEndpointsResourceId.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.37.4.10188", - "templateHash": "7830346183750605706" - } - }, - "parameters": { - "solutionLocation": { - "type": "string", - "minLength": 3, - "maxLength": 20, - "metadata": { - "description": "Required. Solution location." - } - }, - "cosmosDBName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Azure Cosmos DB account." - } - }, - "databaseName": { - "type": "string", - "defaultValue": "db_conversation_history", - "metadata": { - "description": "Optional. Name of the Cosmos DB database." - } - }, - "collectionName": { - "type": "string", - "defaultValue": "conversations", - "metadata": { - "description": "Optional.Name of the Cosmos DB container (collection)." - } + "version": "0.34.44.8038", + "templateHash": "4533956061065498344" }, - "containers": { - "type": "array", - "defaultValue": [ - { - "name": "[parameters('collectionName')]", - "id": "[parameters('collectionName')]", - "partitionKey": "/userId" + "name": "Private DNS Zones", + "description": "This module deploys a Private DNS zone." + }, + "definitions": { + "aType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "aRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipv4Address": { + "type": "string", + "metadata": { + "description": "Required. The IPv4 address of this A record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of A records in the record set." + } } - ], + }, "metadata": { - "description": "Optional. List of Cosmos DB containers to be created." + "__bicep_export!": true, + "description": "The type for the A record." } }, - "kind": { - "type": "string", - "defaultValue": "GlobalDocumentDB", - "allowedValues": [ - "GlobalDocumentDB", - "MongoDB", - "Parse" - ], + "aaaaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "aaaaRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipv6Address": { + "type": "string", + "metadata": { + "description": "Required. The IPv6 address of this AAAA record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of AAAA records in the record set." + } + } + }, "metadata": { - "description": "Optional. The API kind of the Cosmos DB account." + "__bicep_export!": true, + "description": "The type for the AAAA record." } }, - "tags": { + "cnameType": { "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to be applied to the resources." - } - } - }, - "resources": [ - { - "copy": { - "name": "database::list", - "count": "[length(parameters('containers'))]" - }, - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "apiVersion": "2022-05-15", - "name": "[format('{0}/{1}/{2}', split(format('{0}/{1}', parameters('cosmosDBName'), parameters('databaseName')), '/')[0], split(format('{0}/{1}', parameters('cosmosDBName'), parameters('databaseName')), '/')[1], parameters('containers')[copyIndex()].name)]", "properties": { - "resource": { - "id": "[parameters('containers')[copyIndex()].id]", - "partitionKey": { - "paths": [ - "[parameters('containers')[copyIndex()].partitionKey]" - ] + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." } }, - "options": {} + "cnameRecord": { + "type": "object", + "properties": { + "cname": { + "type": "string", + "metadata": { + "description": "Required. The canonical name of the CNAME record." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The CNAME record in the record set." + } + } }, - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', split(format('{0}/{1}', parameters('cosmosDBName'), parameters('databaseName')), '/')[0], split(format('{0}/{1}', parameters('cosmosDBName'), parameters('databaseName')), '/')[1])]" - ] + "metadata": { + "__bicep_export!": true, + "description": "The type for the CNAME record." + } }, - { - "type": "Microsoft.DocumentDB/databaseAccounts", - "apiVersion": "2022-08-15", - "name": "[parameters('cosmosDBName')]", - "kind": "[parameters('kind')]", - "location": "[parameters('solutionLocation')]", - "tags": "[parameters('tags')]", + "mxType": { + "type": "object", "properties": { - "consistencyPolicy": { - "defaultConsistencyLevel": "Session" + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } }, - "locations": [ - { - "locationName": "[parameters('solutionLocation')]", - "failoverPriority": 0, - "isZoneRedundant": false + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." } - ], - "databaseAccountOfferType": "Standard", - "enableAutomaticFailover": false, - "enableMultipleWriteLocations": false, - "disableLocalAuth": true, - "apiProperties": "[if(equals(parameters('kind'), 'MongoDB'), createObject('serverVersion', '4.0'), createObject())]", - "capabilities": [ - { - "name": "EnableServerless" + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." } - ] + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "mxRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "exchange": { + "type": "string", + "metadata": { + "description": "Required. The domain name of the mail host for this MX record." + } + }, + "preference": { + "type": "int", + "metadata": { + "description": "Required. The preference value for this MX record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of MX records in the record set." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the MX record." } }, - { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", - "apiVersion": "2022-05-15", - "name": "[format('{0}/{1}', parameters('cosmosDBName'), parameters('databaseName'))]", + "ptrType": { + "type": "object", "properties": { - "resource": { - "id": "[parameters('databaseName')]" - } - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDBName'))]" - ] - } - ], - "outputs": { - "cosmosAccountName": { - "type": "string", + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "ptrRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ptrdname": { + "type": "string", + "metadata": { + "description": "Required. The PTR target domain name for this PTR record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of PTR records in the record set." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the PTR record." + } + }, + "soaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "soaRecord": { + "type": "object", + "properties": { + "email": { + "type": "string", + "metadata": { + "description": "Required. The email contact for this SOA record." + } + }, + "expireTime": { + "type": "int", + "metadata": { + "description": "Required. The expire time for this SOA record." + } + }, + "host": { + "type": "string", + "metadata": { + "description": "Required. The domain name of the authoritative name server for this SOA record." + } + }, + "minimumTtl": { + "type": "int", + "metadata": { + "description": "Required. The minimum value for this SOA record. By convention this is used to determine the negative caching duration." + } + }, + "refreshTime": { + "type": "int", + "metadata": { + "description": "Required. The refresh value for this SOA record." + } + }, + "retryTime": { + "type": "int", + "metadata": { + "description": "Required. The retry time for this SOA record." + } + }, + "serialNumber": { + "type": "int", + "metadata": { + "description": "Required. The serial number for this SOA record." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The SOA record in the record set." + } + } + }, "metadata": { - "description": "Name of the Cosmos DB account." + "__bicep_export!": true, + "description": "The type for the SOA record." + } + }, + "srvType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "srvRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "priority": { + "type": "int", + "metadata": { + "description": "Required. The priority value for this SRV record." + } + }, + "weight": { + "type": "int", + "metadata": { + "description": "Required. The weight value for this SRV record." + } + }, + "port": { + "type": "int", + "metadata": { + "description": "Required. The port value for this SRV record." + } + }, + "target": { + "type": "string", + "metadata": { + "description": "Required. The target domain name for this SRV record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of SRV records in the record set." + } + } }, - "value": "[parameters('cosmosDBName')]" + "metadata": { + "__bicep_export!": true, + "description": "The type for the SRV record." + } }, - "cosmosDatabaseName": { - "type": "string", + "txtType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata of the record." + } + }, + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "txtRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The text value of this TXT record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of TXT records in the record set." + } + } + }, "metadata": { - "description": "Name of the Cosmos DB database." + "__bicep_export!": true, + "description": "The type for the TXT record." + } + }, + "virtualNetworkLinkType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "minLength": 1, + "maxLength": 80, + "metadata": { + "description": "Optional. The resource name." + } + }, + "virtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the virtual network to link." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Azure Region where the resource lives." + } + }, + "registrationEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "resolutionPolicy": { + "type": "string", + "allowedValues": [ + "Default", + "NxDomainRedirect" + ], + "nullable": true, + "metadata": { + "description": "Optional. The resolution type of the private-dns-zone fallback machanism." + } + } }, - "value": "[parameters('databaseName')]" + "metadata": { + "__bicep_export!": true, + "description": "The type for the virtual network link." + } }, - "cosmosContainerName": { - "type": "string", + "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." + } + } + }, "metadata": { - "description": "Name of the Cosmos DB container." + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } }, - "value": "[parameters('collectionName')]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploy_storage_account", - "resourceGroup": "[resourceGroup().name]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "solutionLocation": { - "value": "[variables('solutionLocation')]" - }, - "managedIdentityObjectId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" - }, - "saName": { - "value": "[format('st{0}', variables('solutionSuffix'))]" - }, - "keyVaultName": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" - }, - "tags": { - "value": "[parameters('tags')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "3164769802823369019" + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } } }, "parameters": { - "solutionLocation": { + "name": { "type": "string", "metadata": { - "description": "Required. Deployment location for the solution." + "description": "Required. Private DNS zone name." } }, - "saName": { - "type": "string", + "a": { + "type": "array", + "items": { + "$ref": "#/definitions/aType" + }, + "nullable": true, "metadata": { - "description": "Required. Name of the storage account." + "description": "Optional. Array of A records." } }, - "managedIdentityObjectId": { - "type": "string", + "aaaa": { + "type": "array", + "items": { + "$ref": "#/definitions/aaaaType" + }, + "nullable": true, "metadata": { - "description": "Required. Object ID of the managed identity." + "description": "Optional. Array of AAAA records." } }, - "keyVaultName": { - "type": "string", + "cname": { + "type": "array", + "items": { + "$ref": "#/definitions/cnameType" + }, + "nullable": true, "metadata": { - "description": "Required. Name of the Azure Key Vault." + "description": "Optional. Array of CNAME records." } }, - "tags": { - "type": "object", - "defaultValue": {}, + "mx": { + "type": "array", + "items": { + "$ref": "#/definitions/mxType" + }, + "nullable": true, "metadata": { - "description": "Optional. Tags to be applied to the resources." + "description": "Optional. Array of MX records." } - } - }, - "resources": [ - { + }, + "ptr": { + "type": "array", + "items": { + "$ref": "#/definitions/ptrType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of PTR records." + } + }, + "soa": { + "type": "array", + "items": { + "$ref": "#/definitions/soaType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of SOA records." + } + }, + "srv": { + "type": "array", + "items": { + "$ref": "#/definitions/srvType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of SRV records." + } + }, + "txt": { + "type": "array", + "items": { + "$ref": "#/definitions/txtType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of TXT records." + } + }, + "virtualNetworkLinks": { + "type": "array", + "items": { + "$ref": "#/definitions/virtualNetworkLinkType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of custom objects describing vNet links of the DNS zone. Each object should contain properties 'virtualNetworkResourceId' and 'registrationEnabled'. The 'vnetResourceId' is a resource ID of a vNet to link, 'registrationEnabled' (bool) enables automatic DNS registration in the zone for the linked vNet." + } + }, + "location": { + "type": "string", + "defaultValue": "global", + "metadata": { + "description": "Optional. The location of the PrivateDNSZone. Should be global." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "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')]", + "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": "[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-privatednszone.{0}.{1}', replace('0.7.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" + } + } + } + } + }, + "privateDnsZone": { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + }, + "privateDnsZone_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/privateDnsZones/{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": [ + "privateDnsZone" + ] + }, + "privateDnsZone_roleAssignments": { + "copy": { + "name": "privateDnsZone_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones', 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": [ + "privateDnsZone" + ] + }, + "privateDnsZone_A": { + "copy": { + "name": "privateDnsZone_A", + "count": "[length(coalesce(parameters('a'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-ARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('a'), createArray())[copyIndex()].name]" + }, + "aRecords": { + "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'aRecords')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'metadata')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('a'), 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.34.44.8038", + "templateHash": "18243374258187942664" + }, + "name": "Private DNS Zone A record", + "description": "This module deploys a Private DNS Zone A record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the A record." + } + }, + "aRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of A records in the record set." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "A": { + "type": "Microsoft.Network/privateDnsZones/A", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "aRecords": "[parameters('aRecords')]", + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]" + } + }, + "A_roleAssignments": { + "copy": { + "name": "A_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/A/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), 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": [ + "A" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed A record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed A record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed A record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_AAAA": { + "copy": { + "name": "privateDnsZone_AAAA", + "count": "[length(coalesce(parameters('aaaa'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-AAAARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('aaaa'), createArray())[copyIndex()].name]" + }, + "aaaaRecords": { + "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'aaaaRecords')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'metadata')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('aaaa'), 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.34.44.8038", + "templateHash": "7322684246075092047" + }, + "name": "Private DNS Zone AAAA record", + "description": "This module deploys a Private DNS Zone AAAA record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the AAAA record." + } + }, + "aaaaRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of AAAA records in the record set." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "AAAA": { + "type": "Microsoft.Network/privateDnsZones/AAAA", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "aaaaRecords": "[parameters('aaaaRecords')]", + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]" + } + }, + "AAAA_roleAssignments": { + "copy": { + "name": "AAAA_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/AAAA/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), 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": [ + "AAAA" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed AAAA record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed AAAA record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed AAAA record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_CNAME": { + "copy": { + "name": "privateDnsZone_CNAME", + "count": "[length(coalesce(parameters('cname'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-CNAMERecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('cname'), createArray())[copyIndex()].name]" + }, + "cnameRecord": { + "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'cnameRecord')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'metadata')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('cname'), 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.34.44.8038", + "templateHash": "5264706240021075859" + }, + "name": "Private DNS Zone CNAME record", + "description": "This module deploys a Private DNS Zone CNAME record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the CNAME record." + } + }, + "cnameRecord": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. A CNAME record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "CNAME": { + "type": "Microsoft.Network/privateDnsZones/CNAME", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "cnameRecord": "[parameters('cnameRecord')]", + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]" + } + }, + "CNAME_roleAssignments": { + "copy": { + "name": "CNAME_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/CNAME/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), 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": [ + "CNAME" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed CNAME record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed CNAME record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed CNAME record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_MX": { + "copy": { + "name": "privateDnsZone_MX", + "count": "[length(coalesce(parameters('mx'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-MXRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('mx'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'metadata')]" + }, + "mxRecords": { + "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'mxRecords')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('mx'), 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.34.44.8038", + "templateHash": "13758189936483275969" + }, + "name": "Private DNS Zone MX record", + "description": "This module deploys a Private DNS Zone MX record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the MX record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "mxRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of MX records in the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "MX": { + "type": "Microsoft.Network/privateDnsZones/MX", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "mxRecords": "[parameters('mxRecords')]", + "ttl": "[parameters('ttl')]" + } + }, + "MX_roleAssignments": { + "copy": { + "name": "MX_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/MX/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), 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": [ + "MX" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed MX record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed MX record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed MX record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_PTR": { + "copy": { + "name": "privateDnsZone_PTR", + "count": "[length(coalesce(parameters('ptr'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-PTRRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('ptr'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'metadata')]" + }, + "ptrRecords": { + "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ptrRecords')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('ptr'), 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.34.44.8038", + "templateHash": "11955164584650609753" + }, + "name": "Private DNS Zone PTR record", + "description": "This module deploys a Private DNS Zone PTR record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the PTR record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ptrRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of PTR records in the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "PTR": { + "type": "Microsoft.Network/privateDnsZones/PTR", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "ptrRecords": "[parameters('ptrRecords')]", + "ttl": "[parameters('ttl')]" + } + }, + "PTR_roleAssignments": { + "copy": { + "name": "PTR_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/PTR/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), 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": [ + "PTR" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed PTR record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed PTR record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed PTR record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_SOA": { + "copy": { + "name": "privateDnsZone_SOA", + "count": "[length(coalesce(parameters('soa'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-SOARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('soa'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'metadata')]" + }, + "soaRecord": { + "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'soaRecord')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('soa'), 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.34.44.8038", + "templateHash": "14626715835033259725" + }, + "name": "Private DNS Zone SOA record", + "description": "This module deploys a Private DNS Zone SOA record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SOA record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "soaRecord": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. A SOA record." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "SOA": { + "type": "Microsoft.Network/privateDnsZones/SOA", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "soaRecord": "[parameters('soaRecord')]", + "ttl": "[parameters('ttl')]" + } + }, + "SOA_roleAssignments": { + "copy": { + "name": "SOA_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SOA/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), 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": [ + "SOA" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed SOA record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed SOA record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed SOA record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_SRV": { + "copy": { + "name": "privateDnsZone_SRV", + "count": "[length(coalesce(parameters('srv'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-SRVRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('srv'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'metadata')]" + }, + "srvRecords": { + "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'srvRecords')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('srv'), 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.34.44.8038", + "templateHash": "6510442308165042737" + }, + "name": "Private DNS Zone SRV record", + "description": "This module deploys a Private DNS Zone SRV record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SRV record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "srvRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of SRV records in the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "SRV": { + "type": "Microsoft.Network/privateDnsZones/SRV", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "srvRecords": "[parameters('srvRecords')]", + "ttl": "[parameters('ttl')]" + } + }, + "SRV_roleAssignments": { + "copy": { + "name": "SRV_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SRV/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), 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": [ + "SRV" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed SRV record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed SRV record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed SRV record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_TXT": { + "copy": { + "name": "privateDnsZone_TXT", + "count": "[length(coalesce(parameters('txt'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-TXTRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('txt'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'metadata')]" + }, + "txtRecords": { + "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'txtRecords')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('txt'), 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.34.44.8038", + "templateHash": "170623042781622569" + }, + "name": "Private DNS Zone TXT record", + "description": "This module deploys a Private DNS Zone TXT record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the TXT record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "txtRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of TXT records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "TXT": { + "type": "Microsoft.Network/privateDnsZones/TXT", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]", + "txtRecords": "[parameters('txtRecords')]" + } + }, + "TXT_roleAssignments": { + "copy": { + "name": "TXT_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/TXT/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), 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": [ + "TXT" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed TXT record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed TXT record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed TXT record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_virtualNetworkLinks": { + "copy": { + "name": "privateDnsZone_virtualNetworkLinks", + "count": "[length(coalesce(parameters('virtualNetworkLinks'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-VNetLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'name'), format('{0}-vnetlink', last(split(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId, '/'))))]" + }, + "virtualNetworkResourceId": { + "value": "[coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'location'), 'global')]" + }, + "registrationEnabled": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'registrationEnabled'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "resolutionPolicy": { + "value": "[tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'resolutionPolicy')]" + } + }, + "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.34.44.8038", + "templateHash": "725891200086243555" + }, + "name": "Private DNS Zone Virtual Network Link", + "description": "This module deploys a Private DNS Zone Virtual Network Link." + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('{0}-vnetlink', last(split(parameters('virtualNetworkResourceId'), '/')))]", + "metadata": { + "description": "Optional. The name of the virtual network link." + } + }, + "location": { + "type": "string", + "defaultValue": "global", + "metadata": { + "description": "Optional. The location of the PrivateDNSZone. Should be global." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "registrationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?." + } + }, + "virtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. Link to another virtual network resource ID." + } + }, + "resolutionPolicy": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resolution policy on the virtual network link. Only applicable for virtual network links to privatelink zones, and for A,AAAA,CNAME queries. When set to `NxDomainRedirect`, Azure DNS resolver falls back to public resolution if private dns query resolution results in non-existent domain response. `Default` is configured as the default option." + } + } + }, + "resources": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "virtualNetworkLink": { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "registrationEnabled": "[parameters('registrationEnabled')]", + "virtualNetwork": { + "id": "[parameters('virtualNetworkResourceId')]" + }, + "resolutionPolicy": "[parameters('resolutionPolicy')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed virtual network link." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed virtual network link." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed virtual network link." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('virtualNetworkLink', '2024-06-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private DNS zone was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private DNS zone." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private DNS zone." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('name'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateDnsZone', '2020-06-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "network" + ] + }, + "logAnalyticsWorkspace": { + "condition": "[parameters('enableMonitoring')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.operational-insights.workspace.{0}', variables('logAnalyticsWorkspaceResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('logAnalyticsWorkspaceResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "skuName": { + "value": "PerGB2018" + }, + "dataRetention": { + "value": 365 + }, + "features": { + "value": { + "enableLogAccessUsingOnlyResourcePermissions": true + } + }, + "diagnosticSettings": { + "value": [ + { + "useThisWorkspace": true + } + ] + }, + "dailyQuotaGb": "[if(parameters('enableRedundancy'), createObject('value', 10), createObject('value', null()))]", + "replication": "[if(parameters('enableRedundancy'), createObject('value', createObject('enabled', true(), 'location', variables('replicaLocation'))), createObject('value', null()))]", + "publicNetworkAccessForIngestion": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "publicNetworkAccessForQuery": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "dataSources": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('tags', parameters('tags'), 'eventLogName', 'Application', 'eventTypes', createArray(createObject('eventType', 'Error'), createObject('eventType', 'Warning'), createObject('eventType', 'Information')), 'kind', 'WindowsEvent', 'name', 'applicationEvent'), createObject('counterName', '% Processor Time', 'instanceName', '*', 'intervalSeconds', 60, 'kind', 'WindowsPerformanceCounter', 'name', 'windowsPerfCounter1', 'objectName', 'Processor'), createObject('kind', 'IISLogs', 'name', 'sampleIISLog1', 'state', 'OnPremiseEnabled'))), createObject('value', null()))]" + }, + "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.36.1.42791", + "templateHash": "1749032521457140145" + }, + "name": "Log Analytics Workspaces", + "description": "This module deploys a Log Analytics Workspace." + }, + "definitions": { + "diagnosticSettingType": { + "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." + } + }, + "useThisWorkspace": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. If set to `true`, the `workspaceResourceId` property is ignored." + } + }, + "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." + } + } + } + }, + "gallerySolutionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + } + }, + "plan": { + "$ref": "#/definitions/solutionPlanType", + "metadata": { + "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the gallery solutions to be created in the log analytics workspace." + } + }, + "storageInsightsConfigType": { + "type": "object", + "properties": { + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the storage account to be linked." + } + }, + "containers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the blob containers that the workspace should read." + } + }, + "tables": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of tables to be read by the workspace." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the storage insights configuration." + } + }, + "linkedServiceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the linked service." + } + }, + "resourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require read access." + } + }, + "writeAccessResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require write access." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the linked service." + } + }, + "linkedStorageAccountType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the link." + } + }, + "storageAccountIds": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. Linked storage accounts resources Ids." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the linked storage account." + } + }, + "savedSearchType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the saved search." + } + }, + "etag": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. The category of the saved search. This helps the user to find a saved search faster." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name for the search." + } + }, + "functionAlias": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The function alias if query serves as a function." + } + }, + "functionParameters": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: 'param-name1:type1 = default_value1, param-name2:type2 = default_value2'. For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." + } + }, + "query": { + "type": "string", + "metadata": { + "description": "Required. The query expression for the saved search." + } + }, + "tags": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The tags attached to the saved search." + } + }, + "version": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The version number of the query language. The current version is 2 and is the default." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the saved search." + } + }, + "dataExportType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data export." + } + }, + "destination": { + "$ref": "#/definitions/destinationType", + "nullable": true, + "metadata": { + "description": "Optional. The destination of the data export." + } + }, + "enable": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the data export." + } + }, + "tableNames": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of table names to export." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the data export." + } + }, + "dataSourceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data source." + } + }, + "kind": { + "type": "string", + "metadata": { + "description": "Required. The kind of data source." + } + }, + "linkedResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace." + } + }, + "eventLogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the event log to configure when kind is WindowsEvent." + } + }, + "eventTypes": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The event types to configure when kind is WindowsEvent." + } + }, + "objectName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "instanceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "intervalSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "performanceCounters": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." + } + }, + "counterName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." + } + }, + "state": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." + } + }, + "syslogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. System log to configure when kind is LinuxSyslog." + } + }, + "syslogSeverities": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the data source." + } + }, + "tableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } + }, + "plan": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The plan for the table." + } + }, + "restoredLogs": { + "$ref": "#/definitions/restoredLogsType", + "nullable": true, + "metadata": { + "description": "Optional. The restored logs for the table." + } + }, + "schema": { + "$ref": "#/definitions/schemaType", + "nullable": true, + "metadata": { + "description": "Optional. The schema for the table." + } + }, + "searchResults": { + "$ref": "#/definitions/searchResultsType", + "nullable": true, + "metadata": { + "description": "Optional. The search results for the table." + } + }, + "retentionInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The retention in days for the table." + } + }, + "totalRetentionInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The total retention in days for the table." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The role assignments for the table." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the custom table." + } + }, + "workspaceFeaturesType": { + "type": "object", + "properties": { + "disableLocalAuth": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Disable Non-EntraID based Auth. Default is true." + } + }, + "enableDataExport": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag that indicate if data should be exported." + } + }, + "enableLogAccessUsingOnlyResourcePermissions": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable log access using only resource permissions. Default is false." + } + }, + "immediatePurgeDataOn30Days": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag that describes if we want to remove the data after 30 days." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Features of the workspace." + } + }, + "workspaceReplicationType": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether the replication is enabled or not. When true, workspace configuration and data is replicated to the specified location." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The location to which the workspace is replicated. Required if replication is enabled." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Replication properties of the workspace." + } + }, + "_1.columnType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The column name." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "boolean", + "dateTime", + "dynamic", + "guid", + "int", + "long", + "real", + "string" + ], + "metadata": { + "description": "Required. The column type." + } + }, + "dataTypeHint": { + "type": "string", + "allowedValues": [ + "armPath", + "guid", + "ip", + "uri" + ], + "nullable": true, + "metadata": { + "description": "Optional. The column data type logical hint." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The column description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Column display name." + } + } + }, + "metadata": { + "description": "The parameters of the table column.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "destinationType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The destination resource ID." + } + }, + "metaData": { + "type": "object", + "properties": { + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination metadata." + } + } + }, + "metadata": { + "description": "The data export destination properties.", + "__bicep_imported_from!": { + "sourceTemplate": "data-export/main.bicep" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "restoredLogsType": { + "type": "object", + "properties": { + "sourceTable": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table to restore data from." + } + }, + "startRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the restore from (UTC)." + } + }, + "endRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the restore by (UTC)." + } + } + }, + "metadata": { + "description": "The parameters of the restore operation that initiated the table.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "schemaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The table name." + } + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.columnType" + }, + "metadata": { + "description": "Required. A list of table custom columns." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table display name." + } + } + }, + "metadata": { + "description": "The table schema.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "searchResultsType": { + "type": "object", + "properties": { + "query": { + "type": "string", + "metadata": { + "description": "Required. The search job query." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The search description." + } + }, + "limit": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Limit the search job to return up to specified number of rows." + } + }, + "startSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the search from (UTC)." + } + }, + "endSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the search by (UTC)." + } + } + }, + "metadata": { + "description": "The parameters of the search job that initiated the table.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "solutionPlanType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + } + }, + "product": { + "type": "string", + "metadata": { + "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/operations-management/solution:0.3.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Log Analytics workspace." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "skuName": { + "type": "string", + "defaultValue": "PerGB2018", + "allowedValues": [ + "CapacityReservation", + "Free", + "LACluster", + "PerGB2018", + "PerNode", + "Premium", + "Standalone", + "Standard" + ], + "metadata": { + "description": "Optional. The name of the SKU." + } + }, + "skuCapacityReservationLevel": { + "type": "int", + "defaultValue": 100, + "minValue": 100, + "maxValue": 5000, + "metadata": { + "description": "Optional. The capacity reservation level in GB for this workspace, when CapacityReservation sku is selected. Must be in increments of 100 between 100 and 5000." + } + }, + "storageInsightsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/storageInsightsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of storage accounts to be read by the workspace." + } + }, + "linkedServices": { + "type": "array", + "items": { + "$ref": "#/definitions/linkedServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of services to be linked." + } + }, + "linkedStorageAccounts": { + "type": "array", + "items": { + "$ref": "#/definitions/linkedStorageAccountType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of Storage Accounts to be linked. Required if 'forceCmkForQuery' is set to 'true' and 'savedSearches' is not empty." + } + }, + "savedSearches": { + "type": "array", + "items": { + "$ref": "#/definitions/savedSearchType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Kusto Query Language searches to save." + } + }, + "dataExports": { + "type": "array", + "items": { + "$ref": "#/definitions/dataExportType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW data export instances to be deployed." + } + }, + "dataSources": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSourceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW data sources to configure." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW custom tables to be deployed." + } + }, + "gallerySolutions": { + "type": "array", + "items": { + "$ref": "#/definitions/gallerySolutionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of gallerySolutions to be created in the log analytics workspace." + } + }, + "onboardWorkspaceToSentinel": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Onboard the Log Analytics Workspace to Sentinel. Requires 'SecurityInsights' solution to be in gallerySolutions." + } + }, + "dataRetention": { + "type": "int", + "defaultValue": 365, + "minValue": 0, + "maxValue": 730, + "metadata": { + "description": "Optional. Number of days data will be retained for." + } + }, + "dailyQuotaGb": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "metadata": { + "description": "Optional. The workspace daily quota for ingestion." + } + }, + "publicNetworkAccessForIngestion": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The network access type for accessing Log Analytics ingestion." + } + }, + "publicNetworkAccessForQuery": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The network access type for accessing Log Analytics query." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource. Only one type of identity is supported: system-assigned or user-assigned, but not both." + } + }, + "features": { + "$ref": "#/definitions/workspaceFeaturesType", + "nullable": true, + "metadata": { + "description": "Optional. The workspace features." + } + }, + "replication": { + "$ref": "#/definitions/workspaceReplicationType", + "nullable": true, + "metadata": { + "description": "Optional. The workspace replication properties." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "forceCmkForQuery": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether customer managed storage is mandatory for query management." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces@2025-02-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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()), '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')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "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": "[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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.12.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" + } + } + } + } + }, + "logAnalyticsWorkspace": { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "features": { + "searchVersion": 1, + "enableLogAccessUsingOnlyResourcePermissions": "[coalesce(tryGet(parameters('features'), 'enableLogAccessUsingOnlyResourcePermissions'), false())]", + "disableLocalAuth": "[coalesce(tryGet(parameters('features'), 'disableLocalAuth'), true())]", + "enableDataExport": "[tryGet(parameters('features'), 'enableDataExport')]", + "immediatePurgeDataOn30Days": "[tryGet(parameters('features'), 'immediatePurgeDataOn30Days')]" + }, + "sku": { + "name": "[parameters('skuName')]", + "capacityReservationLevel": "[if(equals(parameters('skuName'), 'CapacityReservation'), parameters('skuCapacityReservationLevel'), null())]" + }, + "retentionInDays": "[parameters('dataRetention')]", + "workspaceCapping": { + "dailyQuotaGb": "[parameters('dailyQuotaGb')]" + }, + "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]", + "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]", + "forceCmkForQuery": "[parameters('forceCmkForQuery')]", + "replication": "[parameters('replication')]" + }, + "identity": "[variables('identity')]" + }, + "logAnalyticsWorkspace_diagnosticSettings": { + "copy": { + "name": "logAnalyticsWorkspace_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{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": "[if(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'useThisWorkspace'), false()), resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), 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": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_sentinelOnboarding": { + "condition": "[and(not(empty(filter(coalesce(parameters('gallerySolutions'), createArray()), lambda('item', startsWith(lambdaVariables('item').name, 'SecurityInsights'))))), parameters('onboardWorkspaceToSentinel'))]", + "type": "Microsoft.SecurityInsights/onboardingStates", + "apiVersion": "2024-03-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "default", + "properties": {}, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_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.OperationalInsights/workspaces/{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": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_roleAssignments": { + "copy": { + "name": "logAnalyticsWorkspace_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces', 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": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_storageInsightConfigs": { + "copy": { + "name": "logAnalyticsWorkspace_storageInsightConfigs", + "count": "[length(coalesce(parameters('storageInsightsConfigs'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-StorageInsightsConfig-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'containers')]" + }, + "tables": { + "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'tables')]" + }, + "storageAccountResourceId": { + "value": "[coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()].storageAccountResourceId]" + } + }, + "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.36.1.42791", + "templateHash": "1306323182548882150" + }, + "name": "Log Analytics Workspace Storage Insight Configs", + "description": "This module deploys a Log Analytics Workspace Storage Insight Config." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('{0}-stinsconfig', last(split(parameters('storageAccountResourceId'), '/')))]", + "metadata": { + "description": "Optional. The name of the storage insights config." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Azure Resource Manager ID of the storage account resource." + } + }, + "containers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the blob containers that the workspace should read." + } + }, + "tables": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the Azure tables that the workspace should read." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "storageinsightconfig": { + "type": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "containers": "[parameters('containers')]", + "tables": "[parameters('tables')]", + "storageAccount": { + "id": "[parameters('storageAccountResourceId')]", + "key": "[listKeys('storageAccount', '2024-01-01').keys[0].value]" + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage insights configuration." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/storageInsightConfigs', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the storage insight configuration is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the storage insights configuration." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_linkedServices": { + "copy": { + "name": "logAnalyticsWorkspace_linkedServices", + "count": "[length(coalesce(parameters('linkedServices'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-LinkedService-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('linkedServices'), createArray())[copyIndex()].name]" + }, + "resourceId": { + "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'resourceId')]" + }, + "writeAccessResourceId": { + "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'writeAccessResourceId')]" + } + }, + "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.36.1.42791", + "templateHash": "5230241501765697269" + }, + "name": "Log Analytics Workspace Linked Services", + "description": "This module deploys a Log Analytics Workspace Linked Service." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the link." + } + }, + "resourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require read access." + } + }, + "writeAccessResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require write access." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "linkedService": { + "type": "Microsoft.OperationalInsights/workspaces/linkedServices", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resourceId": "[parameters('resourceId')]", + "writeAccessResourceId": "[parameters('writeAccessResourceId')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed linked service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed linked service." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedServices', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the linked service is deployed." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_linkedStorageAccounts": { + "copy": { + "name": "logAnalyticsWorkspace_linkedStorageAccounts", + "count": "[length(coalesce(parameters('linkedStorageAccounts'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-LinkedStorageAccount-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].name]" + }, + "storageAccountIds": { + "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].storageAccountIds]" + } + }, + "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.36.1.42791", + "templateHash": "10372135754202496594" + }, + "name": "Log Analytics Workspace Linked Storage Accounts", + "description": "This module deploys a Log Analytics Workspace Linked Storage Account." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "allowedValues": [ + "Query", + "Alerts", + "CustomLogs", + "AzureWatson" + ], + "metadata": { + "description": "Required. Name of the link." + } + }, + "storageAccountIds": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. Linked storage accounts resources Ids." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "linkedStorageAccount": { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "properties": { + "storageAccountIds": "[parameters('storageAccountIds')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed linked storage account." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed linked storage account." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedStorageAccounts', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the linked storage account is deployed." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_savedSearches": { + "copy": { + "name": "logAnalyticsWorkspace_savedSearches", + "count": "[length(coalesce(parameters('savedSearches'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-SavedSearch-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[format('{0}{1}', coalesce(parameters('savedSearches'), createArray())[copyIndex()].name, uniqueString(deployment().name))]" + }, + "etag": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'etag')]" + }, + "displayName": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].displayName]" + }, + "category": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].category]" + }, + "query": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].query]" + }, + "functionAlias": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionAlias')]" + }, + "functionParameters": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionParameters')]" + }, + "tags": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'tags')]" + }, + "version": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'version')]" + } + }, + "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.36.1.42791", + "templateHash": "9015459905306126128" + }, + "name": "Log Analytics Workspace Saved Searches", + "description": "This module deploys a Log Analytics Workspace Saved Search." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the saved search." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name for the search." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. Query category." + } + }, + "query": { + "type": "string", + "metadata": { + "description": "Required. Kusto Query to be stored." + } + }, + "tags": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-02-01#properties/properties/properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + }, + "functionAlias": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The function alias if query serves as a function." + } + }, + "functionParameters": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: \"param-name1:type1 = default_value1, param-name2:type2 = default_value2\". For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." + } + }, + "version": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The version number of the query language." + } + }, + "etag": { + "type": "string", + "defaultValue": "*", + "metadata": { + "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "savedSearch": { + "type": "Microsoft.OperationalInsights/workspaces/savedSearches", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "properties": { + "etag": "[parameters('etag')]", + "tags": "[coalesce(parameters('tags'), createArray())]", + "displayName": "[parameters('displayName')]", + "category": "[parameters('category')]", + "query": "[parameters('query')]", + "functionAlias": "[parameters('functionAlias')]", + "functionParameters": "[parameters('functionParameters')]", + "version": "[parameters('version')]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed saved search." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the saved search is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed saved search." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace", + "logAnalyticsWorkspace_linkedStorageAccounts" + ] + }, + "logAnalyticsWorkspace_dataExports": { + "copy": { + "name": "logAnalyticsWorkspace_dataExports", + "count": "[length(coalesce(parameters('dataExports'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-DataExport-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "workspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('dataExports'), createArray())[copyIndex()].name]" + }, + "destination": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'destination')]" + }, + "enable": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'enable')]" + }, + "tableNames": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'tableNames')]" + } + }, + "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.36.1.42791", + "templateHash": "8586520532175356447" + }, + "name": "Log Analytics Workspace Data Exports", + "description": "This module deploys a Log Analytics Workspace Data Export." + }, + "definitions": { + "destinationType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The destination resource ID." + } + }, + "metaData": { + "type": "object", + "properties": { + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination metadata." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The data export destination properties." + } + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 4, + "maxLength": 63, + "metadata": { + "description": "Required. The data export rule name." + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + } + }, + "destination": { + "$ref": "#/definitions/destinationType", + "nullable": true, + "metadata": { + "description": "Optional. Destination properties." + } + }, + "enable": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Active when enabled." + } + }, + "tableNames": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. An array of tables to export, for example: ['Heartbeat', 'SecurityEvent']." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('workspaceName')]" + }, + "dataExport": { + "type": "Microsoft.OperationalInsights/workspaces/dataExports", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "properties": { + "destination": "[parameters('destination')]", + "enable": "[parameters('enable')]", + "tableNames": "[parameters('tableNames')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the data export." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the data export." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataExports', parameters('workspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the data export was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_dataSources": { + "copy": { + "name": "logAnalyticsWorkspace_dataSources", + "count": "[length(coalesce(parameters('dataSources'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-DataSource-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].name]" + }, + "kind": { + "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].kind]" + }, + "linkedResourceId": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'linkedResourceId')]" + }, + "eventLogName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventLogName')]" + }, + "eventTypes": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventTypes')]" + }, + "objectName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'objectName')]" + }, + "instanceName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'instanceName')]" + }, + "intervalSeconds": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'intervalSeconds')]" + }, + "counterName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'counterName')]" + }, + "state": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'state')]" + }, + "syslogName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogName')]" + }, + "syslogSeverities": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogSeverities')]" + }, + "performanceCounters": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'performanceCounters')]" + }, + "tags": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], '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.36.1.42791", + "templateHash": "8336916453932906250" + }, + "name": "Log Analytics Workspace Datasources", + "description": "This module deploys a Log Analytics Workspace Data Source." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data source." + } + }, + "kind": { + "type": "string", + "defaultValue": "AzureActivityLog", + "allowedValues": [ + "AzureActivityLog", + "WindowsEvent", + "WindowsPerformanceCounter", + "IISLogs", + "LinuxSyslog", + "LinuxSyslogCollection", + "LinuxPerformanceObject", + "LinuxPerformanceCollection" + ], + "metadata": { + "description": "Optional. The kind of the data source." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + }, + "linkedResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the resource to be linked." + } + }, + "eventLogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Windows event log name to configure when kind is WindowsEvent." + } + }, + "eventTypes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Windows event types to configure when kind is WindowsEvent." + } + }, + "objectName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "instanceName": { + "type": "string", + "defaultValue": "*", + "metadata": { + "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "intervalSeconds": { + "type": "int", + "defaultValue": 60, + "metadata": { + "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "performanceCounters": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." + } + }, + "counterName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." + } + }, + "state": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." + } + }, + "syslogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. System log to configure when kind is LinuxSyslog." + } + }, + "syslogSeverities": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "dataSource": { + "type": "Microsoft.OperationalInsights/workspaces/dataSources", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "kind": "[parameters('kind')]", + "tags": "[parameters('tags')]", + "properties": { + "linkedResourceId": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'AzureActivityLog')), parameters('linkedResourceId'), null())]", + "eventLogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventLogName'), null())]", + "eventTypes": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventTypes'), null())]", + "objectName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('objectName'), null())]", + "instanceName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('instanceName'), null())]", + "intervalSeconds": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('intervalSeconds'), null())]", + "counterName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsPerformanceCounter')), parameters('counterName'), null())]", + "state": "[if(and(not(empty(parameters('kind'))), or(or(equals(parameters('kind'), 'IISLogs'), equals(parameters('kind'), 'LinuxSyslogCollection')), equals(parameters('kind'), 'LinuxPerformanceCollection'))), parameters('state'), null())]", + "syslogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxSyslog')), parameters('syslogName'), null())]", + "syslogSeverities": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'LinuxSyslog'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('syslogSeverities'), null())]", + "performanceCounters": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxPerformanceObject')), parameters('performanceCounters'), null())]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed data source." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataSources', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the data source is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed data source." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_tables": { + "copy": { + "name": "logAnalyticsWorkspace_tables", + "count": "[length(coalesce(parameters('tables'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-Table-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "workspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" + }, + "plan": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'plan')]" + }, + "schema": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'schema')]" + }, + "retentionInDays": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'retentionInDays')]" + }, + "totalRetentionInDays": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'totalRetentionInDays')]" + }, + "restoredLogs": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'restoredLogs')]" + }, + "searchResults": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'searchResults')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('tables'), 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.36.1.42791", + "templateHash": "315390662258960765" + }, + "name": "Log Analytics Workspace Tables", + "description": "This module deploys a Log Analytics Workspace Table." + }, + "definitions": { + "restoredLogsType": { + "type": "object", + "properties": { + "sourceTable": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table to restore data from." + } + }, + "startRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the restore from (UTC)." + } + }, + "endRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the restore by (UTC)." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the restore operation that initiated the table." + } + }, + "schemaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The table name." + } + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/columnType" + }, + "metadata": { + "description": "Required. A list of table custom columns." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table display name." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The table schema." + } + }, + "columnType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The column name." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "boolean", + "dateTime", + "dynamic", + "guid", + "int", + "long", + "real", + "string" + ], + "metadata": { + "description": "Required. The column type." + } + }, + "dataTypeHint": { + "type": "string", + "allowedValues": [ + "armPath", + "guid", + "ip", + "uri" + ], + "nullable": true, + "metadata": { + "description": "Optional. The column data type logical hint." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The column description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Column display name." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the table column." + } + }, + "searchResultsType": { + "type": "object", + "properties": { + "query": { + "type": "string", + "metadata": { + "description": "Required. The search job query." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The search description." + } + }, + "limit": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Limit the search job to return up to specified number of rows." + } + }, + "startSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the search from (UTC)." + } + }, + "endSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the search by (UTC)." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the search job that initiated the table." + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + } + }, + "plan": { + "type": "string", + "defaultValue": "Analytics", + "allowedValues": [ + "Basic", + "Analytics" + ], + "metadata": { + "description": "Optional. Instruct the system how to handle and charge the logs ingested to this table." + } + }, + "restoredLogs": { + "$ref": "#/definitions/restoredLogsType", + "nullable": true, + "metadata": { + "description": "Optional. Restore parameters." + } + }, + "retentionInDays": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "maxValue": 730, + "metadata": { + "description": "Optional. The table retention in days, between 4 and 730. Setting this property to -1 will default to the workspace retention." + } + }, + "schema": { + "$ref": "#/definitions/schemaType", + "nullable": true, + "metadata": { + "description": "Optional. Table's schema." + } + }, + "searchResults": { + "$ref": "#/definitions/searchResultsType", + "nullable": true, + "metadata": { + "description": "Optional. Parameters of the search job that initiated this table." + } + }, + "totalRetentionInDays": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "maxValue": 2555, + "metadata": { + "description": "Optional. The table total retention in days, between 4 and 2555. Setting this property to -1 will default to table retention." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('workspaceName')]" + }, + "table": { + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "properties": { + "plan": "[parameters('plan')]", + "restoredLogs": "[parameters('restoredLogs')]", + "retentionInDays": "[parameters('retentionInDays')]", + "schema": "[parameters('schema')]", + "searchResults": "[parameters('searchResults')]", + "totalRetentionInDays": "[parameters('totalRetentionInDays')]" + } + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}/tables/{1}', parameters('workspaceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), 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": [ + "table" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the table." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the table was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_solutions": { + "copy": { + "name": "logAnalyticsWorkspace_solutions", + "count": "[length(coalesce(parameters('gallerySolutions'), createArray()))]" + }, + "condition": "[not(empty(parameters('gallerySolutions')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-Solution-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].name]" + }, + "location": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "plan": { + "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].plan]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "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.32.4.45862", + "templateHash": "10255889523646649592" + }, + "name": "Operations Management Solutions", + "description": "This module deploys an Operations Management Solution.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "solutionPlanType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + } + }, + "product": { + "type": "string", + "metadata": { + "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + } + }, + "plan": { + "$ref": "#/definitions/solutionPlanType", + "metadata": { + "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Log Analytics workspace where the solution will be deployed/enabled." + } + }, + "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." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.operationsmanagement-solution.{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" + } + } + } + } + }, + "logAnalyticsWorkspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-06-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "solution": { + "type": "Microsoft.OperationsManagement/solutions", + "apiVersion": "2015-11-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "properties": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]" + }, + "plan": { + "name": "[coalesce(tryGet(parameters('plan'), 'name'), parameters('name'))]", + "promotionCode": "", + "product": "[parameters('plan').product]", + "publisher": "[coalesce(tryGet(parameters('plan'), 'publisher'), 'Microsoft')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed solution." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed solution." + }, + "value": "[resourceId('Microsoft.OperationsManagement/solutions', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the solution is deployed." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('solution', '2015-11-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed log analytics workspace." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed log analytics workspace." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed log analytics workspace." + }, + "value": "[parameters('name')]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "The ID associated with the workspace." + }, + "value": "[reference('logAnalyticsWorkspace').customerId]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('logAnalyticsWorkspace', '2025-02-01', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('logAnalyticsWorkspace', '2025-02-01', 'full'), 'identity'), 'principalId')]" + }, + "primarySharedKey": { + "type": "securestring", + "metadata": { + "description": "The primary shared key of the log analytics workspace." + }, + "value": "[listKeys('logAnalyticsWorkspace', '2025-02-01').primarySharedKey]" + }, + "secondarySharedKey": { + "type": "securestring", + "metadata": { + "description": "The secondary shared key of the log analytics workspace." + }, + "value": "[listKeys('logAnalyticsWorkspace', '2025-02-01').secondarySharedKey]" + } + } + } + } + }, + "applicationInsights": { + "condition": "[parameters('enableMonitoring')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.insights.component.{0}', variables('applicationInsightsResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('applicationInsightsResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "retentionInDays": { + "value": 365 + }, + "kind": { + "value": "web" + }, + "disableIpMasking": { + "value": false + }, + "flowType": { + "value": "Bluefield" + }, + "workspaceResourceId": "[if(parameters('enableMonitoring'), if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value)), createObject('value', ''))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]" + }, + "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.33.93.31351", + "templateHash": "5735496719243704506" + }, + "name": "Application Insights", + "description": "This component deploys an Application Insights instance." + }, + "definitions": { + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.3.0" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.3.0" + } + } + } + }, + "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", + "nullable": true, + "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." + } + }, + "flowType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Used by the Application Insights system to determine what kind of flow this component was created by. This is to be set to 'Bluefield' when creating/updating a component via the REST API." + } + }, + "requestSource": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Describes what tool created this Application Insights component. Customers using this API should set this to the default 'rest'." + } + }, + "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." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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.6.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" + } + } + } + } + }, + "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')]", + "Flow_Type": "[parameters('flowType')]", + "Request_Source": "[parameters('requestSource')]" + } + }, + "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_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.Insights/components/{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": [ + "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": "[coalesce(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.33.93.31351", + "templateHash": "10861379689695100897" + }, + "name": "Application Insights Linked Storage Account", + "description": "This component deploys an Application Insights Linked Storage Account." + }, + "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]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "keyvault": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.key-vault.vault.{0}', variables('keyVaultName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('keyVaultName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "sku": { + "value": "premium" + }, + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "networkAcls": { + "value": { + "defaultAction": "Allow" + } + }, + "enableVaultForDeployment": { + "value": true + }, + "enableVaultForDiskEncryption": { + "value": true + }, + "enableVaultForTemplateDeployment": { + "value": true + }, + "enableRbacAuthorization": { + "value": true + }, + "enableSoftDelete": { + "value": true + }, + "softDeleteRetentionInDays": { + "value": 7 + }, + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', createArray()))]", + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('keyVaultName')), 'customNetworkInterfaceName', format('nic-{0}', variables('keyVaultName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').keyVault)).outputs.resourceId.value))), 'service', 'vault', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]", + "roleAssignments": { + "value": [ + { + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Key Vault Administrator" + } + ] + }, + "secrets": { + "value": [ + { + "name": "SQLDB-SERVER", + "value": "[variables('sqlServerFqdn')]" + }, + { + "name": "SQLDB-DATABASE", + "value": "[variables('sqlDbName')]" + } + ] + } + }, + "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.33.93.31351", + "templateHash": "17553975707245390963" + }, + "name": "Key Vaults", + "description": "This module deploys a Key Vault." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "credentialOutputType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The item's resourceId." + } + }, + "uri": { + "type": "string", + "metadata": { + "description": "The item's uri." + } + }, + "uriWithVersion": { + "type": "string", + "metadata": { + "description": "The item's uri with version." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a credential output." + } + }, + "accessPolicyType": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an access policy." + } + }, + "secretType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributes": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Defines whether the secret is enabled or disabled." + } + }, + "exp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Defines when the secret will become invalid. Defined in seconds since 1970-01-01T00:00:00Z." + } + }, + "nbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. If set, defines the date from which onwards the secret becomes valid. Defined in seconds since 1970-01-01T00:00:00Z." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Contains attributes of the secret." + } + }, + "contentType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a secret output." + } + }, + "keyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributes": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Defines whether the key is enabled or disabled." + } + }, + "exp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Defines when the key will become invalid. Defined in seconds since 1970-01-01T00:00:00Z." + } + }, + "nbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. If set, defines the date from which onwards the key becomes valid. Defined in seconds since 1970-01-01T00:00:00Z." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Contains attributes of the key." + } + }, + "curveName": { + "type": "string", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "nullable": true, + "metadata": { + "description": "Optional. The elliptic curve name. Only works if \"keySize\" equals \"EC\" or \"EC-HSM\". Default is \"P-256\"." + } + }, + "keyOps": { + "type": "array", + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "release", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. The allowed operations on this key." + } + }, + "keySize": { + "type": "int", + "allowedValues": [ + 2048, + 3072, + 4096 + ], + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. Only works if \"keySize\" equals \"RSA\" or \"RSA-HSM\". Default is \"4096\"." + } + }, + "kty": { + "type": "string", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of the key. Default is \"EC\"." + } + }, + "releasePolicy": { + "type": "object", + "properties": { + "contentType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Content type and version of key release policy." + } + }, + "data": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Blob encoding the policy rules under which the key can be released." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, + "rotationPolicy": { + "$ref": "#/definitions/rotationPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a key." + } + }, + "rotationPolicyType": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "expiryTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The expiration time for the new key version. It should be in ISO8601 format. Eg: \"P90D\", \"P1Y\"." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The attributes of key rotation policy." + } + }, + "lifetimeActions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "Notify", + "Rotate" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of action." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The action of key rotation policy lifetimeAction." + } + }, + "trigger": { + "type": "object", + "properties": { + "timeAfterCreate": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time duration after key creation to rotate the key. It only applies to rotate. It will be in ISO 8601 duration format. Eg: \"P90D\", \"P1Y\"." + } + }, + "timeBeforeExpiry": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time duration before key expiring to rotate or notify. It will be in ISO 8601 duration format. Eg: \"P90D\", \"P1Y\"." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The trigger of key rotation policy lifetimeAction." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The lifetimeActions for key rotation action." + } + } + }, + "metadata": { + "description": "The type for a rotation policy." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Key Vault. Must be globally unique." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "accessPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/accessPolicyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All access policies to create." + } + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/secretType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All secrets to create." + } + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/keyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All keys to create." + } + }, + "enableVaultForDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for deployment by script or compute." + } + }, + "enableVaultForTemplateDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for a template deployment." + } + }, + "enableVaultForDiskEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the azure platform has access to the vault for enabling disk encryption scenarios." + } + }, + "enableSoftDelete": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Switch to enable/disable Key Vault's soft delete feature." + } + }, + "softDeleteRetentionInDays": { + "type": "int", + "defaultValue": 90, + "metadata": { + "description": "Optional. softDelete data retention days. It accepts >=7 and <=90." + } + }, + "enableRbacAuthorization": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC." + } + }, + "createMode": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The vault's create mode to indicate whether the vault need to be recovered or not. - recover or default." + } + }, + "enablePurgeProtection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Provide 'true' to enable Key Vault's purge protection feature." + } + }, + "sku": { + "type": "string", + "defaultValue": "premium", + "allowedValues": [ + "premium", + "standard" + ], + "metadata": { + "description": "Optional. Specifies the SKU for the vault." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Rules governing the accessibility of the resource from specific network locations." + } + }, + "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." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "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": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ], + "enableReferencedModulesTelemetry": false, + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "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.keyvault-vault.{0}.{1}', replace('0.12.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" + } + } + } + } + }, + "keyVault": { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "enabledForDeployment": "[parameters('enableVaultForDeployment')]", + "enabledForTemplateDeployment": "[parameters('enableVaultForTemplateDeployment')]", + "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]", + "enableSoftDelete": "[parameters('enableSoftDelete')]", + "softDeleteRetentionInDays": "[parameters('softDeleteRetentionInDays')]", + "enableRbacAuthorization": "[parameters('enableRbacAuthorization')]", + "createMode": "[parameters('createMode')]", + "enablePurgeProtection": "[if(parameters('enablePurgeProtection'), parameters('enablePurgeProtection'), null())]", + "tenantId": "[subscription().tenantId]", + "accessPolicies": "[variables('formattedAccessPolicies')]", + "sku": { + "name": "[parameters('sku')]", + "family": "A" + }, + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(coalesce(parameters('privateEndpoints'), createArray()))), empty(coalesce(parameters('networkAcls'), createObject()))), 'Disabled', null()))]" + } + }, + "keyVault_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.KeyVault/vaults/{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": [ + "keyVault" + ] + }, + "keyVault_diagnosticSettings": { + "copy": { + "name": "keyVault_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{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": [ + "keyVault" + ] + }, + "keyVault_roleAssignments": { + "copy": { + "name": "keyVault_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.KeyVault/vaults', 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": [ + "keyVault" + ] + }, + "keyVault_accessPolicies": { + "condition": "[not(empty(parameters('accessPolicies')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-AccessPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('name')]" + }, + "accessPolicies": { + "value": "[parameters('accessPolicies')]" + } + }, + "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.33.93.31351", + "templateHash": "6321524620984159084" + }, + "name": "Key Vault Access Policies", + "description": "This module deploys a Key Vault Access Policy." + }, + "definitions": { + "accessPoliciesType": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an access policy." + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "accessPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/accessPoliciesType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of 0 to 16 identities that have access to the key vault. All identities in the array must use the same tenant ID as the key vault's tenant ID." + } + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "policies": { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), 'add')]", + "properties": { + "copy": [ + { + "name": "accessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ] + } + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the access policies assignment was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the access policies assignment." + }, + "value": "add" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the access policies assignment." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/accessPolicies', parameters('keyVaultName'), 'add')]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_secrets": { + "copy": { + "name": "keyVault_secrets", + "count": "[length(coalesce(parameters('secrets'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Secret-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('secrets'), createArray())[copyIndex()].name]" + }, + "value": { + "value": "[coalesce(parameters('secrets'), createArray())[copyIndex()].value]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributes'), 'enabled')]" + }, + "attributesExp": { + "value": "[tryGet(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributes'), 'exp')]" + }, + "attributesNbf": { + "value": "[tryGet(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributes'), 'nbf')]" + }, + "contentType": { + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'contentType')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('secrets'), 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.33.93.31351", + "templateHash": "4741547827723795923" + }, + "name": "Key Vault Secrets", + "description": "This module deploys a Key Vault Secret." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "contentType": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "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": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "secret": { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "contentType": "[parameters('contentType')]", + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "value": "[parameters('value')]" + } + }, + "secret_roleAssignments": { + "copy": { + "name": "secret_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/secrets/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), 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": [ + "secret" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the secret." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the secret." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name'))]" + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The uri of the secret." + }, + "value": "[reference('secret').secretUri]" + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The uri with version of the secret." + }, + "value": "[reference('secret').secretUriWithVersion]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the secret was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_keys": { + "copy": { + "name": "keyVault_keys", + "count": "[length(coalesce(parameters('keys'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('keys'), createArray())[copyIndex()].name]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributes'), 'enabled')]" + }, + "attributesExp": { + "value": "[tryGet(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributes'), 'exp')]" + }, + "attributesNbf": { + "value": "[tryGet(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributes'), 'nbf')]" + }, + "curveName": "[if(and(not(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA')), not(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA-HSM'))), createObject('value', coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')), createObject('value', null()))]", + "keyOps": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keyOps')]" + }, + "keySize": "[if(or(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA'), equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA-HSM')), createObject('value', coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize'), 4096)), createObject('value', null()))]", + "releasePolicy": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'releasePolicy'), createObject())]" + }, + "kty": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'EC')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "rotationPolicy": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'rotationPolicy')]" + } + }, + "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.33.93.31351", + "templateHash": "12000970886778046699" + }, + "name": "Key Vault Keys", + "description": "This module deploys a Key Vault Key." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "curveName": { + "type": "string", + "defaultValue": "P-256", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "metadata": { + "description": "Optional. The elliptic curve name." + } + }, + "keyOps": { + "type": "array", + "nullable": true, + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "metadata": { + "description": "Optional. Array of JsonWebKeyOperation." + } + }, + "keySize": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. For example: 2048, 3072, or 4096 for RSA." + } + }, + "kty": { + "type": "string", + "defaultValue": "EC", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "metadata": { + "description": "Optional. The type of the key." + } + }, + "releasePolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "rotationPolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy properties object." + } + } + }, + "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')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "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": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "key": { + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": "[shallowMerge(createArray(createObject('attributes', createObject('enabled', parameters('attributesEnabled'), 'exp', parameters('attributesExp'), 'nbf', parameters('attributesNbf')), 'curveName', parameters('curveName'), 'keyOps', parameters('keyOps'), 'keySize', parameters('keySize'), 'kty', parameters('kty'), 'release_policy', coalesce(parameters('releasePolicy'), createObject())), if(not(empty(parameters('rotationPolicy'))), createObject('rotationPolicy', parameters('rotationPolicy')), createObject())))]" + }, + "key_roleAssignments": { + "copy": { + "name": "key_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/keys/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), 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": [ + "key" + ] + } + }, + "outputs": { + "keyUri": { + "type": "string", + "metadata": { + "description": "The uri of the key." + }, + "value": "[reference('key').keyUri]" + }, + "keyUriWithVersion": { + "type": "string", + "metadata": { + "description": "The uri with version of the key." + }, + "value": "[reference('key').keyUriWithVersion]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_privateEndpoints": { + "copy": { + "name": "keyVault_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')), '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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.33.13.18514", + "templateHash": "15954548978129725136" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.10.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-11-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.33.13.18514", + "templateHash": "5440815542537978381" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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-11-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key vault." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key vault was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key vault." + }, + "value": "[parameters('name')]" + }, + "uri": { + "type": "string", + "metadata": { + "description": "The URI of the key vault." + }, + "value": "[reference('keyVault').vaultUri]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('keyVault', '2022-07-01', 'full').location]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the key vault." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/credentialOutputType" + }, + "metadata": { + "description": "The properties of the created secrets." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('secrets'), createArray()))))]", + "input": { + "resourceId": "[reference(format('keyVault_secrets[{0}]', range(0, length(coalesce(parameters('secrets'), createArray())))[copyIndex()])).outputs.resourceId.value]", + "uri": "[reference(format('keyVault_secrets[{0}]', range(0, length(coalesce(parameters('secrets'), createArray())))[copyIndex()])).outputs.secretUri.value]", + "uriWithVersion": "[reference(format('keyVault_secrets[{0}]', range(0, length(coalesce(parameters('secrets'), createArray())))[copyIndex()])).outputs.secretUriWithVersion.value]" + } + } + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/credentialOutputType" + }, + "metadata": { + "description": "The properties of the created keys." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('keys'), createArray()))))]", + "input": { + "resourceId": "[reference(format('keyVault_keys[{0}]', range(0, length(coalesce(parameters('keys'), createArray())))[copyIndex()])).outputs.resourceId.value]", + "uri": "[reference(format('keyVault_keys[{0}]', range(0, length(coalesce(parameters('keys'), createArray())))[copyIndex()])).outputs.keyUri.value]", + "uriWithVersion": "[reference(format('keyVault_keys[{0}]', range(0, length(coalesce(parameters('keys'), createArray())))[copyIndex()])).outputs.keyUriWithVersion.value]" + } + } + } + } + } + }, + "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').keyVault)]", + "logAnalyticsWorkspace", + "network", + "userAssignedIdentity" + ] + }, + "aiFoundryAiServices": { + "condition": "[variables('aiFoundryAIservicesEnabled')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.cognitive-services.account.{0}', variables('aiFoundryAiServicesResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('aiFoundryAiServicesResourceName')]" + }, + "location": { + "value": "[parameters('azureAiServiceLocation')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "existingFoundryProjectResourceId": { + "value": "[parameters('existingFoundryProjectResourceId')]" + }, + "projectName": { + "value": "[variables('aiFoundryAiServicesAiProjectResourceName')]" + }, + "projectDescription": { + "value": "AI Foundry Project" + }, + "sku": { + "value": "S0" + }, + "kind": { + "value": "AIServices" + }, + "disableLocalAuth": { + "value": true + }, + "customSubDomainName": { + "value": "[variables('aiFoundryAiServicesResourceName')]" + }, + "apiProperties": { + "value": {} + }, + "networkAcls": { + "value": { + "defaultAction": "Allow", + "virtualNetworkRules": [], + "ipRules": [] + } + }, + "managedIdentities": { + "value": { + "userAssignedResourceIds": [ + "[reference('userAssignedIdentity').outputs.resourceId.value]" + ] + } + }, + "roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "53ca6127-db72-4b80-b1b0-d745d6d5456d", + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "64702f94-c441-49e6-a78b-ef80e0188fee", + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + } + ] + }, + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "privateEndpoints": "[if(and(parameters('enablePrivateNetworking'), empty(parameters('existingFoundryProjectResourceId'))), createObject('value', createArray(createObject('name', format('pep-{0}', variables('aiFoundryAiServicesResourceName')), 'customNetworkInterfaceName', format('nic-{0}', variables('aiFoundryAiServicesResourceName')), 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value, 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('name', 'ai-services-dns-zone-cognitiveservices', 'privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)).outputs.resourceId.value), createObject('name', 'ai-services-dns-zone-openai', 'privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)).outputs.resourceId.value), createObject('name', 'ai-services-dns-zone-aiservices', 'privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)).outputs.resourceId.value)))))), createObject('value', createArray()))]", + "deployments": { + "value": [ + { + "name": "[variables('aiFoundryAiServicesModelDeployment').name]", + "model": { + "format": "[variables('aiFoundryAiServicesModelDeployment').format]", + "name": "[variables('aiFoundryAiServicesModelDeployment').name]", + "version": "[variables('aiFoundryAiServicesModelDeployment').version]" + }, + "raiPolicyName": "[variables('aiFoundryAiServicesModelDeployment').raiPolicyName]", + "sku": { + "name": "[variables('aiFoundryAiServicesModelDeployment').sku.name]", + "capacity": "[variables('aiFoundryAiServicesModelDeployment').sku.capacity]" + } + }, + { + "name": "[variables('aiFoundryAiServicesEmbeddingModel').name]", + "model": { + "format": "OpenAI", + "name": "[variables('aiFoundryAiServicesEmbeddingModel').name]", + "version": "[variables('aiFoundryAiServicesEmbeddingModel').version]" + }, + "raiPolicyName": "[variables('aiFoundryAiServicesEmbeddingModel').raiPolicyName]", + "sku": { + "name": "[variables('aiFoundryAiServicesEmbeddingModel').sku.name]", + "capacity": "[variables('aiFoundryAiServicesEmbeddingModel').sku.capacity]" + } + } + ] + } + }, + "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.37.4.10188", + "templateHash": "14489342617241779499" + }, + "name": "Cognitive Services", + "description": "This module deploys a Cognitive Service." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoint output." + } + }, + "deploymentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of cognitive service account deployment." + } + }, + "model": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account deployment model." + } + }, + "format": { + "type": "string", + "metadata": { + "description": "Required. The format of Cognitive Services account deployment model." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. The version of Cognitive Services account deployment model." + } + } + }, + "metadata": { + "description": "Required. Properties of Cognitive Services account deployment model." + } + }, + "sku": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource model definition representing SKU." + } + }, + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the resource model definition representing SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier of the resource model definition representing SKU." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The size of the resource model definition representing SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The family of the resource model definition representing SKU." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource model definition representing SKU." + } + }, + "raiPolicyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of RAI policy." + } + }, + "versionUpgradeOption": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version upgrade option." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account deployment." + } + }, + "endpointType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Type of the endpoint." + } + }, + "endpoint": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The endpoint URI." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account endpoint." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of the secrets exported to the provided Key Vault." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "aiProjectOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI project." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the AI project." + } + }, + "apiEndpoint": { + "type": "string", + "metadata": { + "description": "Required. API endpoint for the AI project." + } + } + }, + "metadata": { + "description": "Output type representing AI project information.", + "__bicep_imported_from!": { + "sourceTemplate": "project.bicep" + } + } + }, + "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, the deployment will use the latest version available at deployment time." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type does not support auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_1.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AIServices", + "AnomalyDetector", + "CognitiveServices", + "ComputerVision", + "ContentModerator", + "ContentSafety", + "ConversationalLanguageUnderstanding", + "CustomVision.Prediction", + "CustomVision.Training", + "Face", + "FormRecognizer", + "HealthInsights", + "ImmersiveReader", + "Internal.AllInOne", + "LUIS", + "LUIS.Authoring", + "LanguageAuthoring", + "MetricsAdvisor", + "OpenAI", + "Personalizer", + "QnAMaker.v2", + "SpeechServices", + "TextAnalytics", + "TextTranslation" + ], + "metadata": { + "description": "Required. Kind of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Required. The name of the AI Foundry project to create." + } + }, + "projectDescription": { + "type": "string", + "metadata": { + "description": "Required. The description of the AI Foundry project to create." + } + }, + "sku": { + "type": "string", + "defaultValue": "S0", + "allowedValues": [ + "C2", + "C3", + "C4", + "F0", + "F1", + "S", + "S0", + "S1", + "S10", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S8", + "S9" + ], + "metadata": { + "description": "Optional. SKU of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "publicNetworkAccess": { + "type": "string", + "nullable": true, + "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." + } + }, + "customSubDomainName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. Subdomain name used for token-based authentication. Required if 'networkAcls' or 'privateEndpoints' are set." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. A collection of rules governing the accessibility from specific network locations." + } + }, + "networkInjectionSubnetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The network injection subnet resource Id for the Cognitive Services account. This allows to use the AI Services account with a virtual network." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "allowedFqdnList": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of allowed FQDN." + } + }, + "apiProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The API properties for special APIs." + } + }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allow only Azure AD authentication. Should be enabled for security reasons." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "dynamicThrottlingEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The flag to enable dynamic throttling." + } + }, + "migrationToken": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. Resource migration token." + } + }, + "restore": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Restore a soft-deleted cognitive service at deployment time. Will fail if no such soft-deleted resource exists." + } + }, + "restrictOutboundNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Restrict outbound network access." + } + }, + "userOwnedStorage": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The storage accounts for this resource." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "deployments": { + "type": "array", + "items": { + "$ref": "#/definitions/deploymentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of deployments about cognitive service accounts to create." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource ID of an existing Foundry project to use." + } + }, + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Key vault reference and secret settings for the module's secrets export." + } + } + }, + "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', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "useExistingService": "[not(empty(parameters('existingFoundryProjectResourceId')))]", + "existingCognitiveServiceDetails": "[split(parameters('existingFoundryProjectResourceId'), '/')]" + }, + "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": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2024-11-30", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]" + }, + "cognitiveServiceNew": { + "condition": "[not(variables('useExistingService'))]", + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[parameters('name')]", + "kind": "[parameters('kind')]", + "identity": "[variables('identity')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('sku')]" + }, + "properties": { + "customSubDomainName": "[parameters('customSubDomainName')]", + "allowProjectManagement": true, + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "publicNetworkAccess": "[if(not(equals(parameters('publicNetworkAccess'), null())), parameters('publicNetworkAccess'), if(not(empty(parameters('networkAcls'))), 'Enabled', 'Disabled'))]", + "allowedFqdnList": "[parameters('allowedFqdnList')]", + "apiProperties": "[parameters('apiProperties')]", + "disableLocalAuth": "[parameters('disableLocalAuth')]", + "networkInjections": "[if(not(equals(parameters('networkInjectionSubnetResourceId'), null())), createArray(createObject('scenario', 'agent', 'subnetArmId', parameters('networkInjectionSubnetResourceId'), 'useMicrosoftManagedNetwork', false())), null())]", + "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.KeyVault', 'keyVaultProperties', createObject('identityClientId', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), ''))), reference('cMKUserAssignedIdentity').clientId, null()), 'keyVaultUri', reference('cMKKeyVault').vaultUri, 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'keyVersion'), ''))), tryGet(parameters('customerManagedKey'), 'keyVersion'), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/'))))), null())]", + "migrationToken": "[parameters('migrationToken')]", + "restore": "[parameters('restore')]", + "restrictOutboundNetworkAccess": "[parameters('restrictOutboundNetworkAccess')]", + "userOwnedStorage": "[parameters('userOwnedStorage')]", + "dynamicThrottlingEnabled": "[parameters('dynamicThrottlingEnabled')]" + }, + "dependsOn": [ + "cMKKeyVault", + "cMKKeyVault::cMKKey", + "cMKUserAssignedIdentity" + ] + }, + "cognitiveServiceExisting": { + "condition": "[variables('useExistingService')]", + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-04-01-preview", + "subscriptionId": "[variables('existingCognitiveServiceDetails')[2]]", + "resourceGroup": "[variables('existingCognitiveServiceDetails')[4]]", + "name": "[variables('existingCognitiveServiceDetails')[8]]" + }, + "cognitive_service_dependencies": { + "condition": "[not(variables('useExistingService'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('cognitive_service_dependencies-{0}', uniqueString('cognitive_service_dependencies', deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "projectName": { + "value": "[parameters('projectName')]" + }, + "projectDescription": { + "value": "[parameters('projectDescription')]" + }, + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "deployments": { + "value": "[parameters('deployments')]" + }, + "diagnosticSettings": { + "value": "[parameters('diagnosticSettings')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "privateEndpoints": { + "value": "[parameters('privateEndpoints')]" + }, + "roleAssignments": { + "value": "[parameters('roleAssignments')]" + }, + "secretsExportConfiguration": { + "value": "[parameters('secretsExportConfiguration')]" + }, + "sku": { + "value": "[parameters('sku')]" + }, + "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.37.4.10188", + "templateHash": "15000514671917656766" + } + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoint output." + } + }, + "deploymentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of cognitive service account deployment." + } + }, + "model": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account deployment model." + } + }, + "format": { + "type": "string", + "metadata": { + "description": "Required. The format of Cognitive Services account deployment model." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. The version of Cognitive Services account deployment model." + } + } + }, + "metadata": { + "description": "Required. Properties of Cognitive Services account deployment model." + } + }, + "sku": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource model definition representing SKU." + } + }, + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the resource model definition representing SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier of the resource model definition representing SKU." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The size of the resource model definition representing SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The family of the resource model definition representing SKU." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource model definition representing SKU." + } + }, + "raiPolicyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of RAI policy." + } + }, + "versionUpgradeOption": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version upgrade option." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account deployment." + } + }, + "endpointType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Type of the endpoint." + } + }, + "endpoint": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The endpoint URI." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account endpoint." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of the secrets exported to the provided Key Vault." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "aiProjectOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI project." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the AI project." + } + }, + "apiEndpoint": { + "type": "string", + "metadata": { + "description": "Required. API endpoint for the AI project." + } + } + }, + "metadata": { + "description": "Output type representing AI project information.", + "__bicep_imported_from!": { + "sourceTemplate": "project.bicep" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_1.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account." + } + }, + "sku": { + "type": "string", + "defaultValue": "S0", + "allowedValues": [ + "C2", + "C3", + "C4", + "F0", + "F1", + "S", + "S0", + "S1", + "S10", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S8", + "S9" + ], + "metadata": { + "description": "Optional. SKU of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "deployments": { + "type": "array", + "items": { + "$ref": "#/definitions/deploymentType" + }, + "nullable": true, + "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." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Optional. Name for the project which needs to be created." + } + }, + "projectDescription": { + "type": "string", + "metadata": { + "description": "Optional. Description for the project which needs to be created." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Provide the existing project resource id in case if it needs to be reused" + } + } + }, + "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": { + "Cognitive Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "Cognitive Services Custom Vision Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3')]", + "Cognitive Services Custom Vision Deployment": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5c4089e1-6d96-4d2f-b296-c1bc7137275f')]", + "Cognitive Services Custom Vision Labeler": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '88424f51-ebe7-446f-bc41-7fa16989e96c')]", + "Cognitive Services Custom Vision Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '93586559-c37d-4a6b-ba08-b9f0940c2d73')]", + "Cognitive Services Custom Vision Trainer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a5ae4ab-0d65-4eeb-be61-29fc9b54394b')]", + "Cognitive Services Data Reader (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b59867f0-fa02-499b-be73-45a86b5b3e1c')]", + "Cognitive Services Face Recognizer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9894cab4-e18a-44aa-828b-cb588cd6f2d7')]", + "Cognitive Services Immersive Reader User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b2de6794-95db-4659-8781-7e080d3f2b9d')]", + "Cognitive Services Language Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f07febfe-79bc-46b1-8b37-790e26e6e498')]", + "Cognitive Services Language Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7628b7b8-a8b2-4cdc-b46f-e9b35248918e')]", + "Cognitive Services Language Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8')]", + "Cognitive Services LUIS Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f72c8140-2111-481c-87ff-72b910f6e3f8')]", + "Cognitive Services LUIS Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18e81cdc-4e98-4e29-a639-e7d10c5a6226')]", + "Cognitive Services LUIS Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6322a993-d5c9-4bed-b113-e49bbea25b27')]", + "Cognitive Services Metrics Advisor Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'cb43c632-a144-4ec5-977c-e80c4affc34a')]", + "Cognitive Services Metrics Advisor User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3b20f47b-3825-43cb-8114-4bd2201156a8')]", + "Cognitive Services OpenAI Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')]", + "Cognitive Services OpenAI User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "Cognitive Services QnA Maker Editor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f4cc2bf9-21be-47a1-bdf1-5c5804381025')]", + "Cognitive Services QnA Maker Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '466ccd10-b268-4a11-b098-b4849f024126')]", + "Cognitive Services Speech Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0e75ca1e-0464-4b4d-8b93-68208a576181')]", + "Cognitive Services Speech User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2dc8367-1007-4938-bd23-fe263f013447')]", + "Cognitive Services User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "Azure AI Developer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]", + "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')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "cognitiveService": { + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[parameters('name')]" + }, + "cognitiveService_deployments": { + "copy": { + "name": "cognitiveService_deployments", + "count": "[length(coalesce(parameters('deployments'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('name'), coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'name'), format('{0}-deployments', parameters('name'))))]", + "properties": { + "model": "[coalesce(parameters('deployments'), createArray())[copyIndex()].model]", + "raiPolicyName": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'raiPolicyName')]", + "versionUpgradeOption": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'versionUpgradeOption')]" + }, + "sku": "[coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'sku'), createObject('name', parameters('sku'), 'capacity', tryGet(parameters('sku'), 'capacity'), 'tier', tryGet(parameters('sku'), 'tier'), 'size', tryGet(parameters('sku'), 'size'), 'family', tryGet(parameters('sku'), 'family')))]" + }, + "cognitiveService_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.CognitiveServices/accounts/{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.')]" + } + }, + "cognitiveService_diagnosticSettings": { + "copy": { + "name": "cognitiveService_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{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')]" + } + }, + "cognitiveService_roleAssignments": { + "copy": { + "name": "cognitiveService_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.CognitiveServices/accounts', 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')]" + } + }, + "cognitiveService_privateEndpoints": { + "copy": { + "name": "cognitiveService_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')), '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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.34.44.8038", + "templateHash": "12389807800450456797" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.11.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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.34.44.8038", + "templateHash": "13997305779829540948" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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', '2024-05-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + } + }, + "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(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('cognitiveService', '2025-06-01').key1)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('cognitiveService', '2025-06-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.37.4.10188", + "templateHash": "2491273843075489892" + } + }, + "definitions": { + "secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the secret to set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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": "2023-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]" + } + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetOutputType" + }, + "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]", + "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]" + } + } + } + } + } + } + }, + "aiProject": { + "condition": "[or(not(empty(parameters('projectName'))), not(empty(parameters('existingFoundryProjectResourceId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-ai-project-{1}-deployment', parameters('name'), parameters('projectName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('projectName')]" + }, + "desc": { + "value": "[parameters('projectDescription')]" + }, + "aiServicesName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "existingFoundryProjectResourceId": { + "value": "[parameters('existingFoundryProjectResourceId')]" + } + }, + "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.37.4.10188", + "templateHash": "8500501911204164532" + } + }, + "definitions": { + "aiProjectOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI project." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the AI project." + } + }, + "apiEndpoint": { + "type": "string", + "metadata": { + "description": "Required. API endpoint for the AI project." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Output type representing AI project information." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI Services project." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Required. The location of the Project resource." + } + }, + "desc": { + "type": "string", + "defaultValue": "[parameters('name')]", + "metadata": { + "description": "Optional. The description of the AI Foundry project to create. Defaults to the project name." + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Required. Name of the existing Cognitive Services resource to create the AI Foundry project in." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Use this parameter to use an existing AI project resource ID from different resource group" + } + } + }, + "variables": { + "useExistingProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", + "existingProjName": "[if(variables('useExistingProject'), last(split(parameters('existingFoundryProjectResourceId'), '/')), '')]", + "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('aiServicesName'), variables('existingProjName')), '')]" + }, + "resources": { + "cogServiceReference": { + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[parameters('aiServicesName')]" + }, + "aiProject": { + "condition": "[not(variables('useExistingProject'))]", + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "description": "[parameters('desc')]", + "displayName": "[parameters('name')]" + } + } + }, + "outputs": { + "aiProjectInfo": { + "$ref": "#/definitions/aiProjectOutputType", + "metadata": { + "description": "AI Project metadata including name, resource ID, and API endpoint." + }, + "value": { + "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", + "resourceId": "[if(variables('useExistingProject'), parameters('existingFoundryProjectResourceId'), resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')))]", + "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]" + } + } + } + } + } + } + }, + "outputs": { + "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())]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the congitive services account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "aiProjectInfo": { + "$ref": "#/definitions/aiProjectOutputType", + "value": "[reference('aiProject').outputs.aiProjectInfo.value]" + } + } + } + }, + "dependsOn": [ + "cognitiveServiceNew" + ] + }, + "existing_cognitive_service_dependencies": { + "condition": "[variables('useExistingService')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('existing_cognitive_service_dependencies-{0}', uniqueString('existing_cognitive_service_dependencies', deployment().name))]", + "subscriptionId": "[variables('existingCognitiveServiceDetails')[2]]", + "resourceGroup": "[variables('existingCognitiveServiceDetails')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('existingCognitiveServiceDetails')[8]]" + }, + "projectName": { + "value": "[parameters('projectName')]" + }, + "projectDescription": { + "value": "[parameters('projectDescription')]" + }, + "existingFoundryProjectResourceId": { + "value": "[parameters('existingFoundryProjectResourceId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "deployments": { + "value": "[parameters('deployments')]" + }, + "diagnosticSettings": { + "value": "[parameters('diagnosticSettings')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "privateEndpoints": { + "value": "[parameters('privateEndpoints')]" + }, + "roleAssignments": { + "value": "[parameters('roleAssignments')]" + }, + "secretsExportConfiguration": { + "value": "[parameters('secretsExportConfiguration')]" + }, + "sku": { + "value": "[parameters('sku')]" + }, + "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.37.4.10188", + "templateHash": "15000514671917656766" + } + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoint output." + } + }, + "deploymentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of cognitive service account deployment." + } + }, + "model": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account deployment model." + } + }, + "format": { + "type": "string", + "metadata": { + "description": "Required. The format of Cognitive Services account deployment model." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. The version of Cognitive Services account deployment model." + } + } + }, + "metadata": { + "description": "Required. Properties of Cognitive Services account deployment model." + } + }, + "sku": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource model definition representing SKU." + } + }, + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the resource model definition representing SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier of the resource model definition representing SKU." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The size of the resource model definition representing SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The family of the resource model definition representing SKU." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource model definition representing SKU." + } + }, + "raiPolicyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of RAI policy." + } + }, + "versionUpgradeOption": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version upgrade option." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account deployment." + } + }, + "endpointType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Type of the endpoint." + } + }, + "endpoint": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The endpoint URI." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account endpoint." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of the secrets exported to the provided Key Vault." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "aiProjectOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI project." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the AI project." + } + }, + "apiEndpoint": { + "type": "string", + "metadata": { + "description": "Required. API endpoint for the AI project." + } + } + }, + "metadata": { + "description": "Output type representing AI project information.", + "__bicep_imported_from!": { + "sourceTemplate": "project.bicep" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_1.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account." + } + }, + "sku": { + "type": "string", + "defaultValue": "S0", + "allowedValues": [ + "C2", + "C3", + "C4", + "F0", + "F1", + "S", + "S0", + "S1", + "S10", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S8", + "S9" + ], + "metadata": { + "description": "Optional. SKU of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "deployments": { + "type": "array", + "items": { + "$ref": "#/definitions/deploymentType" + }, + "nullable": true, + "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." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Optional. Name for the project which needs to be created." + } + }, + "projectDescription": { + "type": "string", + "metadata": { + "description": "Optional. Description for the project which needs to be created." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Provide the existing project resource id in case if it needs to be reused" + } + } + }, + "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": { + "Cognitive Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "Cognitive Services Custom Vision Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3')]", + "Cognitive Services Custom Vision Deployment": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5c4089e1-6d96-4d2f-b296-c1bc7137275f')]", + "Cognitive Services Custom Vision Labeler": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '88424f51-ebe7-446f-bc41-7fa16989e96c')]", + "Cognitive Services Custom Vision Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '93586559-c37d-4a6b-ba08-b9f0940c2d73')]", + "Cognitive Services Custom Vision Trainer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a5ae4ab-0d65-4eeb-be61-29fc9b54394b')]", + "Cognitive Services Data Reader (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b59867f0-fa02-499b-be73-45a86b5b3e1c')]", + "Cognitive Services Face Recognizer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9894cab4-e18a-44aa-828b-cb588cd6f2d7')]", + "Cognitive Services Immersive Reader User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b2de6794-95db-4659-8781-7e080d3f2b9d')]", + "Cognitive Services Language Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f07febfe-79bc-46b1-8b37-790e26e6e498')]", + "Cognitive Services Language Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7628b7b8-a8b2-4cdc-b46f-e9b35248918e')]", + "Cognitive Services Language Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8')]", + "Cognitive Services LUIS Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f72c8140-2111-481c-87ff-72b910f6e3f8')]", + "Cognitive Services LUIS Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18e81cdc-4e98-4e29-a639-e7d10c5a6226')]", + "Cognitive Services LUIS Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6322a993-d5c9-4bed-b113-e49bbea25b27')]", + "Cognitive Services Metrics Advisor Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'cb43c632-a144-4ec5-977c-e80c4affc34a')]", + "Cognitive Services Metrics Advisor User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3b20f47b-3825-43cb-8114-4bd2201156a8')]", + "Cognitive Services OpenAI Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')]", + "Cognitive Services OpenAI User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "Cognitive Services QnA Maker Editor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f4cc2bf9-21be-47a1-bdf1-5c5804381025')]", + "Cognitive Services QnA Maker Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '466ccd10-b268-4a11-b098-b4849f024126')]", + "Cognitive Services Speech Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0e75ca1e-0464-4b4d-8b93-68208a576181')]", + "Cognitive Services Speech User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2dc8367-1007-4938-bd23-fe263f013447')]", + "Cognitive Services User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "Azure AI Developer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]", + "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')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "cognitiveService": { + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[parameters('name')]" + }, + "cognitiveService_deployments": { + "copy": { + "name": "cognitiveService_deployments", + "count": "[length(coalesce(parameters('deployments'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('name'), coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'name'), format('{0}-deployments', parameters('name'))))]", + "properties": { + "model": "[coalesce(parameters('deployments'), createArray())[copyIndex()].model]", + "raiPolicyName": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'raiPolicyName')]", + "versionUpgradeOption": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'versionUpgradeOption')]" + }, + "sku": "[coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'sku'), createObject('name', parameters('sku'), 'capacity', tryGet(parameters('sku'), 'capacity'), 'tier', tryGet(parameters('sku'), 'tier'), 'size', tryGet(parameters('sku'), 'size'), 'family', tryGet(parameters('sku'), 'family')))]" + }, + "cognitiveService_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.CognitiveServices/accounts/{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.')]" + } + }, + "cognitiveService_diagnosticSettings": { + "copy": { + "name": "cognitiveService_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{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')]" + } + }, + "cognitiveService_roleAssignments": { + "copy": { + "name": "cognitiveService_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.CognitiveServices/accounts', 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')]" + } + }, + "cognitiveService_privateEndpoints": { + "copy": { + "name": "cognitiveService_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')), '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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.34.44.8038", + "templateHash": "12389807800450456797" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.11.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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.34.44.8038", + "templateHash": "13997305779829540948" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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', '2024-05-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + } + }, + "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(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('cognitiveService', '2025-06-01').key1)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('cognitiveService', '2025-06-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.37.4.10188", + "templateHash": "2491273843075489892" + } + }, + "definitions": { + "secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the secret to set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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": "2023-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]" + } + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetOutputType" + }, + "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]", + "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]" + } + } + } + } + } + } + }, + "aiProject": { + "condition": "[or(not(empty(parameters('projectName'))), not(empty(parameters('existingFoundryProjectResourceId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-ai-project-{1}-deployment', parameters('name'), parameters('projectName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('projectName')]" + }, + "desc": { + "value": "[parameters('projectDescription')]" + }, + "aiServicesName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "existingFoundryProjectResourceId": { + "value": "[parameters('existingFoundryProjectResourceId')]" + } + }, + "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.37.4.10188", + "templateHash": "8500501911204164532" + } + }, + "definitions": { + "aiProjectOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI project." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the AI project." + } + }, + "apiEndpoint": { + "type": "string", + "metadata": { + "description": "Required. API endpoint for the AI project." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Output type representing AI project information." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI Services project." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Required. The location of the Project resource." + } + }, + "desc": { + "type": "string", + "defaultValue": "[parameters('name')]", + "metadata": { + "description": "Optional. The description of the AI Foundry project to create. Defaults to the project name." + } + }, + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Required. Name of the existing Cognitive Services resource to create the AI Foundry project in." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Use this parameter to use an existing AI project resource ID from different resource group" + } + } + }, + "variables": { + "useExistingProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", + "existingProjName": "[if(variables('useExistingProject'), last(split(parameters('existingFoundryProjectResourceId'), '/')), '')]", + "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('aiServicesName'), variables('existingProjName')), '')]" + }, + "resources": { + "cogServiceReference": { + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[parameters('aiServicesName')]" + }, + "aiProject": { + "condition": "[not(variables('useExistingProject'))]", + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "description": "[parameters('desc')]", + "displayName": "[parameters('name')]" + } + } + }, + "outputs": { + "aiProjectInfo": { + "$ref": "#/definitions/aiProjectOutputType", + "metadata": { + "description": "AI Project metadata including name, resource ID, and API endpoint." + }, + "value": { + "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", + "resourceId": "[if(variables('useExistingProject'), parameters('existingFoundryProjectResourceId'), resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')))]", + "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]" + } + } + } + } + } + } + }, + "outputs": { + "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())]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the congitive services account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "aiProjectInfo": { + "$ref": "#/definitions/aiProjectOutputType", + "value": "[reference('aiProject').outputs.aiProjectInfo.value]" + } + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the cognitive services account." + }, + "value": "[if(variables('useExistingService'), variables('existingCognitiveServiceDetails')[8], parameters('name'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cognitive services account." + }, + "value": "[if(variables('useExistingService'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingCognitiveServiceDetails')[2], variables('existingCognitiveServiceDetails')[4]), 'Microsoft.CognitiveServices/accounts', variables('existingCognitiveServiceDetails')[8]), resourceId('Microsoft.CognitiveServices/accounts', parameters('name')))]" + }, + "subscriptionId": { + "type": "string", + "metadata": { + "description": "The resource group the cognitive services account was deployed into." + }, + "value": "[if(variables('useExistingService'), variables('existingCognitiveServiceDetails')[2], subscription().subscriptionId)]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the cognitive services account was deployed into." + }, + "value": "[if(variables('useExistingService'), variables('existingCognitiveServiceDetails')[4], resourceGroup().name)]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "The service endpoint of the cognitive services account." + }, + "value": "[if(variables('useExistingService'), reference('cognitiveServiceExisting').endpoint, if(variables('useExistingService'), reference('cognitiveServiceExisting', '2025-04-01-preview', 'full'), reference('cognitiveServiceNew', '2025-06-01', 'full')).properties.endpoint)]" + }, + "endpoints": { + "$ref": "#/definitions/endpointType", + "metadata": { + "description": "All endpoints available for the cognitive services account, types depends on the cognitive service kind." + }, + "value": "[if(variables('useExistingService'), reference('cognitiveServiceExisting').endpoints, if(variables('useExistingService'), reference('cognitiveServiceExisting', '2025-04-01-preview', 'full'), reference('cognitiveServiceNew', '2025-06-01', 'full')).properties.endpoints)]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[if(variables('useExistingService'), reference('cognitiveServiceExisting', '2025-04-01-preview', 'full').identity.principalId, tryGet(tryGet(if(variables('useExistingService'), reference('cognitiveServiceExisting', '2025-04-01-preview', 'full'), reference('cognitiveServiceNew', '2025-06-01', 'full')), 'identity'), 'principalId'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[if(variables('useExistingService'), reference('cognitiveServiceExisting', '2025-04-01-preview', 'full').location, if(variables('useExistingService'), reference('cognitiveServiceExisting', '2025-04-01-preview', 'full'), reference('cognitiveServiceNew', '2025-06-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(variables('useExistingService'), reference('existing_cognitive_service_dependencies').outputs.exportedSecrets.value, reference('cognitive_service_dependencies').outputs.exportedSecrets.value)]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the congitive services account." + }, + "value": "[if(variables('useExistingService'), reference('existing_cognitive_service_dependencies').outputs.privateEndpoints.value, reference('cognitive_service_dependencies').outputs.privateEndpoints.value)]" + }, + "aiProjectInfo": { + "$ref": "#/definitions/aiProjectOutputType", + "value": "[if(variables('useExistingService'), reference('existing_cognitive_service_dependencies').outputs.aiProjectInfo.value, reference('cognitive_service_dependencies').outputs.aiProjectInfo.value)]" + } + } + } + }, + "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "logAnalyticsWorkspace", + "network", + "userAssignedIdentity" + ] + }, + "cosmosDb": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.document-db.database-account.{0}', variables('cosmosDbResourceName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('cosmosDbResourceName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "sqlDatabases": { + "value": [ + { + "name": "[variables('cosmosDbDatabaseName')]", + "containers": [ + { + "name": "[variables('collectionName')]", + "paths": [ + "/userId" + ] + } + ] + } + ] + }, + "dataPlaneRoleDefinitions": { + "value": [ + { + "roleName": "Cosmos DB SQL Data Contributor", + "dataActions": [ + "Microsoft.DocumentDB/databaseAccounts/readMetadata", + "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*", + "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*" + ], + "assignments": [ + { + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]" + } + ] + } + ] + }, + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "networkRestrictions": { + "value": { + "networkAclBypass": "None", + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), 'Disabled', 'Enabled')]" + } + }, + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('cosmosDbResourceName')), 'customNetworkInterfaceName', format('nic-{0}', variables('cosmosDbResourceName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cosmosDB)).outputs.resourceId.value))), 'service', 'Sql', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]", + "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]", + "capabilitiesToAdd": "[if(parameters('enableRedundancy'), createObject('value', null()), createObject('value', createArray('EnableServerless')))]", + "automaticFailover": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]", + "failoverLocations": "[if(parameters('enableRedundancy'), createObject('value', createArray(createObject('failoverPriority', 0, 'isZoneRedundant', true(), 'locationName', variables('solutionLocation')), createObject('failoverPriority', 1, 'isZoneRedundant', true(), 'locationName', variables('cosmosDbHaLocation')))), createObject('value', createArray(createObject('locationName', variables('solutionLocation'), 'failoverPriority', 0))))]" + }, + "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.35.1.17967", + "templateHash": "8020152823352819436" + }, + "name": "Azure Cosmos DB account", + "description": "This module deploys an Azure Cosmos DB account. The API used for the account is determined by the child resources that are deployed." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group ID for the private endpoint group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "fully-qualified domain name (FQDN) that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses for the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoint output." + } + }, + "failoverLocationType": { + "type": "object", + "properties": { + "failoverPriority": { + "type": "int", + "metadata": { + "description": "Required. The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists." + } + }, + "isZoneRedundant": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag to indicate whether or not this region is an AvailabilityZone region. Defaults to true." + } + }, + "locationName": { + "type": "string", + "metadata": { + "description": "Required. The name of the region." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the failover location." + } + }, + "dataPlaneRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the Azure Cosmos DB for NoSQL native role-based access control definition." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated Microsoft Entra ID principal to which access is being granted through this role-based access control assignment. The tenant ID for the principal is inferred using the tenant associated with the subscription." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for NoSQL native role-based access control assignment." + } + }, + "dataPlaneRoleDefinitionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the role-based access control definition." + } + }, + "roleName": { + "type": "string", + "metadata": { + "description": "Required. A user-friendly name for the role-based access control definition. This must be unique within the database account." + } + }, + "dataActions": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of data actions that are allowed." + } + }, + "assignableScopes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A set of fully-qualified scopes at or below which role-based access control assignments may be created using this definition. This setting allows application of this definition on the entire account or any underlying resource. This setting must have at least one element. Scopes higher than the account level are not enforceable as assignable scopes. Resources referenced in assignable scopes do not need to exist at creation. Defaults to the current account scope." + } + }, + "assignments": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of role-based access control assignments to be created for the definition." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for NoSQL or Table native role-based access control definition." + } + }, + "sqlDatabaseType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the database ." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Will be ignored if `autoscaleSettingsMaxThroughput` is used. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level. Defaults to 400." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the autoscale settings and represents maximum throughput the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If the value is not set, then autoscale will be disabled. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "containers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the container." + } + }, + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "maxLength": 3, + "metadata": { + "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1." + } + }, + "analyticalStorageTtl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "maxValue": 1000000, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level." + } + }, + "conflictResolutionPolicy": { + "type": "object", + "properties": { + "conflictResolutionPath": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The conflict resolution path in the case of LastWriterWins mode. Required if `mode` is set to 'LastWriterWins'." + } + }, + "conflictResolutionProcedure": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The procedure to resolve conflicts in the case of custom mode. Required if `mode` is set to 'Custom'." + } + }, + "mode": { + "type": "string", + "allowedValues": [ + "Custom", + "LastWriterWins" + ], + "metadata": { + "description": "Required. Indicates the conflict resolution mode." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions." + } + }, + "defaultTtl": { + "type": "int", + "nullable": true, + "minValue": -1, + "maxValue": 2147483647, + "metadata": { + "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default." + } + }, + "indexingPolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Indexing policy of the container." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "Hash", + "MultiHash" + ], + "nullable": true, + "metadata": { + "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning." + } + }, + "version": { + "type": "int", + "allowedValues": [ + 1, + 2 + ], + "nullable": true, + "metadata": { + "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used." + } + }, + "uniqueKeyPolicyKeys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of paths must be unique for each document in the Azure Cosmos DB service." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Set of containers to deploy in the database." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for NoSQL database." + } + }, + "networkRestrictionType": { + "type": "object", + "properties": { + "ipRules": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A single IPv4 address or a single IPv4 address range in Classless Inter-Domain Routing (CIDR) format. Provided IPs must be well-formatted and cannot be contained in one of the following ranges: `10.0.0.0/8`, `100.64.0.0/10`, `172.16.0.0/12`, `192.168.0.0/16`, since these are not enforceable by the IP address filter. Example of valid inputs: `23.40.210.245` or `23.40.210.0/8`." + } + }, + "networkAclBypass": { + "type": "string", + "allowedValues": [ + "AzureServices", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the network ACL bypass for Azure services. Default to \"None\"." + } + }, + "publicNetworkAccess": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Whether requests from the public network are allowed. Default to \"Disabled\"." + } + }, + "virtualNetworkRules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of a subnet." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. List of virtual network access control list (ACL) rules configured for the account." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the network restriction." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointMultiServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\" for a Storage Account's Private Endpoints." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can NOT be assumed (i.e., for services that have more than one subresource, like Storage Account with Blob (blob, table, queue, file, ...).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "sqlRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + } + }, + "metadata": { + "description": "The type for the SQL Role Assignments.", + "__bicep_imported_from!": { + "sourceTemplate": "sql-role-definition/main.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the account." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Defaults to the current resource group scope location. Location for all resources." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts@2024-11-15#properties/tags" + }, + "description": "Optional. Tags for the resource." + }, + "nullable": true + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "databaseAccountOfferType": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Standard" + ], + "metadata": { + "description": "Optional. The offer type for the account. Defaults to \"Standard\"." + } + }, + "failoverLocations": { + "type": "array", + "items": { + "$ref": "#/definitions/failoverLocationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The set of locations enabled for the account. Defaults to the location where the account is deployed." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether the single-region account is zone redundant. Defaults to true. This property is ignored for multi-region accounts." + } + }, + "defaultConsistencyLevel": { + "type": "string", + "defaultValue": "Session", + "allowedValues": [ + "Eventual", + "ConsistentPrefix", + "Session", + "BoundedStaleness", + "Strong" + ], + "metadata": { + "description": "Optional. The default consistency level of the account. Defaults to \"Session\"." + } + }, + "disableLocalAuthentication": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Opt-out of local authentication and ensure that only Microsoft Entra can be used exclusively for authentication. Defaults to true." + } + }, + "enableAnalyticalStorage": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag to indicate whether to enable storage analytics. Defaults to false." + } + }, + "automaticFailover": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable automatic failover for regions. Defaults to true." + } + }, + "enableFreeTier": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag to indicate whether \"Free Tier\" is enabled. Defaults to false." + } + }, + "enableMultipleWriteLocations": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables the account to write in multiple locations. Periodic backup must be used if enabled. Defaults to false." + } + }, + "disableKeyBasedMetadataWriteAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Disable write operations on metadata resources (databases, containers, throughput) via account keys. Defaults to true." + } + }, + "maxStalenessPrefix": { + "type": "int", + "defaultValue": 100000, + "minValue": 1, + "maxValue": 2147483647, + "metadata": { + "description": "Optional. The maximum stale requests. Required for \"BoundedStaleness\" consistency level. Valid ranges, Single Region: 10 to 1000000. Multi Region: 100000 to 1000000. Defaults to 100000." + } + }, + "maxIntervalInSeconds": { + "type": "int", + "defaultValue": 300, + "minValue": 5, + "maxValue": 86400, + "metadata": { + "description": "Optional. The maximum lag time in minutes. Required for \"BoundedStaleness\" consistency level. Valid ranges, Single Region: 5 to 84600. Multi Region: 300 to 86400. Defaults to 300." + } + }, + "serverVersion": { + "type": "string", + "defaultValue": "4.2", + "allowedValues": [ + "3.2", + "3.6", + "4.0", + "4.2", + "5.0", + "6.0", + "7.0" + ], + "metadata": { + "description": "Optional. Specifies the MongoDB server version to use if using Azure Cosmos DB for MongoDB RU. Defaults to \"4.2\"." + } + }, + "sqlDatabases": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlDatabaseType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for NoSQL." + } + }, + "mongodbDatabases": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for MongoDB RU." + } + }, + "gremlinDatabases": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for Apache Gremlin." + } + }, + "tables": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for Table." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "totalThroughputLimit": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. The total throughput limit imposed on this account in request units per second (RU/s). Default to unlimited throughput." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of control plane Azure role-based access control assignments." + } + }, + "dataPlaneRoleDefinitions": { + "type": "array", + "items": { + "$ref": "#/definitions/dataPlaneRoleDefinitionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configurations for Azure Cosmos DB for NoSQL native role-based access control definitions. Allows the creations of custom role definitions." + } + }, + "dataPlaneRoleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/dataPlaneRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configurations for Azure Cosmos DB for NoSQL native role-based access control assignments." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings for the service." + } + }, + "capabilitiesToAdd": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "allowedValues": [ + "EnableCassandra", + "EnableTable", + "EnableGremlin", + "EnableMongo", + "DisableRateLimitingResponses", + "EnableServerless", + "EnableNoSQLVectorSearch", + "EnableNoSQLFullTextSearch", + "EnableMaterializedViews", + "DeleteAllItemsByPartitionKey" + ], + "metadata": { + "description": "Optional. A list of Azure Cosmos DB specific capabilities for the account." + } + }, + "backupPolicyType": { + "type": "string", + "defaultValue": "Continuous", + "allowedValues": [ + "Periodic", + "Continuous" + ], + "metadata": { + "description": "Optional. Configures the backup mode. Periodic backup must be used if multiple write locations are used. Defaults to \"Continuous\"." + } + }, + "backupPolicyContinuousTier": { + "type": "string", + "defaultValue": "Continuous30Days", + "allowedValues": [ + "Continuous30Days", + "Continuous7Days" + ], + "metadata": { + "description": "Optional. Configuration values to specify the retention period for continuous mode backup. Default to \"Continuous30Days\"." + } + }, + "backupIntervalInMinutes": { + "type": "int", + "defaultValue": 240, + "minValue": 60, + "maxValue": 1440, + "metadata": { + "description": "Optional. An integer representing the interval in minutes between two backups. This setting only applies to the periodic backup type. Defaults to 240." + } + }, + "backupRetentionIntervalInHours": { + "type": "int", + "defaultValue": 8, + "minValue": 2, + "maxValue": 720, + "metadata": { + "description": "Optional. An integer representing the time (in hours) that each backup is retained. This setting only applies to the periodic backup type. Defaults to 8." + } + }, + "backupStorageRedundancy": { + "type": "string", + "defaultValue": "Local", + "allowedValues": [ + "Geo", + "Local", + "Zone" + ], + "metadata": { + "description": "Optional. Setting that indicates the type of backup residency. This setting only applies to the periodic backup type. Defaults to \"Local\"." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointMultiServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is advised to use private endpoints whenever possible." + } + }, + "networkRestrictions": { + "$ref": "#/definitions/networkRestrictionType", + "defaultValue": { + "ipRules": [], + "virtualNetworkRules": [], + "publicNetworkAccess": "Disabled" + }, + "metadata": { + "description": "Optional. The network configuration of this module. Defaults to `{ ipRules: [], virtualNetworkRules: [], publicNetworkAccess: 'Disabled' }`." + } + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "Tls12", + "allowedValues": [ + "Tls12" + ], + "metadata": { + "description": "Optional. Setting that indicates the minimum allowed TLS version. Azure Cosmos DB for MongoDB RU and Apache Cassandra only work with TLS 1.2 or later. Defaults to \"Tls12\" (TLS 1.2)." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInControlPlaneRoleNames'), 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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInControlPlaneRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Cosmos DB Account Reader Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fbdf93bf-df7d-467e-a4d2-9458aa1360c8')]", + "Cosmos DB Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]", + "CosmosBackupOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db7b14f2-5adf-42da-9f96-f2ee17bab5cb')]", + "CosmosRestoreOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5432c526-bc82-444a-b7ba-57c5b0b5b34f')]", + "DocumentDB Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5bd9cd88-fe45-4216-938b-f97437e15450')]", + "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-07-01", + "name": "[format('46d3xbcp.res.documentdb-databaseaccount.{0}.{1}', replace('0.15.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" + } + } + } + } + }, + "databaseAccount": { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[if(not(empty(parameters('mongodbDatabases'))), 'MongoDB', 'GlobalDocumentDB')]", + "properties": "[shallowMerge(createArray(createObject('databaseAccountOfferType', parameters('databaseAccountOfferType'), 'backupPolicy', shallowMerge(createArray(createObject('type', parameters('backupPolicyType')), if(equals(parameters('backupPolicyType'), 'Continuous'), createObject('continuousModeProperties', createObject('tier', parameters('backupPolicyContinuousTier'))), createObject()), if(equals(parameters('backupPolicyType'), 'Periodic'), createObject('periodicModeProperties', createObject('backupIntervalInMinutes', parameters('backupIntervalInMinutes'), 'backupRetentionIntervalInHours', parameters('backupRetentionIntervalInHours'), 'backupStorageRedundancy', parameters('backupStorageRedundancy'))), createObject()))), 'capabilities', map(coalesce(parameters('capabilitiesToAdd'), createArray()), lambda('capability', createObject('name', lambdaVariables('capability')))), 'minimalTlsVersion', parameters('minimumTlsVersion'), 'capacity', createObject('totalThroughputLimit', parameters('totalThroughputLimit')), 'publicNetworkAccess', coalesce(tryGet(parameters('networkRestrictions'), 'publicNetworkAccess'), 'Disabled')), if(or(or(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('mongodbDatabases')))), not(empty(parameters('gremlinDatabases')))), not(empty(parameters('tables')))), createObject('consistencyPolicy', shallowMerge(createArray(createObject('defaultConsistencyLevel', parameters('defaultConsistencyLevel')), if(equals(parameters('defaultConsistencyLevel'), 'BoundedStaleness'), createObject('maxStalenessPrefix', parameters('maxStalenessPrefix'), 'maxIntervalInSeconds', parameters('maxIntervalInSeconds')), createObject()))), 'enableMultipleWriteLocations', parameters('enableMultipleWriteLocations'), 'locations', if(not(empty(parameters('failoverLocations'))), map(parameters('failoverLocations'), lambda('failoverLocation', createObject('failoverPriority', lambdaVariables('failoverLocation').failoverPriority, 'locationName', lambdaVariables('failoverLocation').locationName, 'isZoneRedundant', coalesce(tryGet(lambdaVariables('failoverLocation'), 'isZoneRedundant'), true())))), createArray(createObject('failoverPriority', 0, 'locationName', parameters('location'), 'isZoneRedundant', parameters('zoneRedundant')))), 'ipRules', map(coalesce(tryGet(parameters('networkRestrictions'), 'ipRules'), createArray()), lambda('ipRule', createObject('ipAddressOrRange', lambdaVariables('ipRule')))), 'virtualNetworkRules', map(coalesce(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules'), createArray()), lambda('rule', createObject('id', lambdaVariables('rule').subnetResourceId, 'ignoreMissingVNetServiceEndpoint', false()))), 'networkAclBypass', coalesce(tryGet(parameters('networkRestrictions'), 'networkAclBypass'), 'None'), 'isVirtualNetworkFilterEnabled', or(not(empty(tryGet(parameters('networkRestrictions'), 'ipRules'))), not(empty(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules')))), 'enableFreeTier', parameters('enableFreeTier'), 'enableAutomaticFailover', parameters('automaticFailover'), 'enableAnalyticalStorage', parameters('enableAnalyticalStorage')), createObject()), if(or(not(empty(parameters('mongodbDatabases'))), not(empty(parameters('gremlinDatabases')))), createObject('disableLocalAuth', false(), 'disableKeyBasedMetadataWriteAccess', false()), createObject('disableLocalAuth', parameters('disableLocalAuthentication'), 'disableKeyBasedMetadataWriteAccess', parameters('disableKeyBasedMetadataWriteAccess'))), if(not(empty(parameters('mongodbDatabases'))), createObject('apiProperties', createObject('serverVersion', parameters('serverVersion'))), createObject())))]" + }, + "databaseAccount_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.DocumentDB/databaseAccounts/{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": [ + "databaseAccount" + ] + }, + "databaseAccount_diagnosticSettings": { + "copy": { + "name": "databaseAccount_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{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": [ + "databaseAccount" + ] + }, + "databaseAccount_roleAssignments": { + "copy": { + "name": "databaseAccount_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', 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": [ + "databaseAccount" + ] + }, + "databaseAccount_sqlDatabases": { + "copy": { + "name": "databaseAccount_sqlDatabases", + "count": "[length(coalesce(parameters('sqlDatabases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('sqlDatabases'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('sqlDatabases'), createArray())[copyIndex()].name]" + }, + "containers": { + "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'containers')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'throughput')]" + }, + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + } + }, + "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.35.1.17967", + "templateHash": "6801379641184405078" + }, + "name": "DocumentDB Database Account SQL Databases", + "description": "This module deploys a SQL Database in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the SQL database ." + } + }, + "containers": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of containers to deploy in the SQL database." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the SQL database resource." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlDatabase": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('name')]" + }, + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(equals(parameters('autoscaleSettingsMaxThroughput'), null()), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]" + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "container": { + "copy": { + "name": "container", + "count": "[length(coalesce(parameters('containers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('containers'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "sqlDatabaseName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" + }, + "analyticalStorageTtl": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'analyticalStorageTtl')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + }, + "conflictResolutionPolicy": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'conflictResolutionPolicy')]" + }, + "defaultTtl": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultTtl')]" + }, + "indexingPolicy": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'indexingPolicy')]" + }, + "kind": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'kind')]" + }, + "version": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'version')]" + }, + "paths": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'paths')]" + }, + "throughput": "[if(and(or(not(equals(parameters('throughput'), null())), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), equals(tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'throughput'), null())), createObject('value', -1), createObject('value', tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'throughput')))]", + "uniqueKeyPolicyKeys": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'uniqueKeyPolicyKeys')]" + } + }, + "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.35.1.17967", + "templateHash": "5467755913632158534" + }, + "name": "DocumentDB Database Account SQL Database Containers", + "description": "This module deploys a SQL Database Container in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "sqlDatabaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Database. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the container." + } + }, + "analyticalStorageTtl": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store." + } + }, + "conflictResolutionPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions." + } + }, + "defaultTtl": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "maxValue": 2147483647, + "metadata": { + "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default." + } + }, + "throughput": { + "type": "int", + "defaultValue": 400, + "metadata": { + "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "maxValue": 1000000, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the SQL Database resource." + } + }, + "paths": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "maxLength": 3, + "metadata": { + "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1." + } + }, + "indexingPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Indexing policy of the container." + } + }, + "uniqueKeyPolicyKeys": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service." + } + }, + "kind": { + "type": "string", + "defaultValue": "Hash", + "allowedValues": [ + "Hash", + "MultiHash" + ], + "metadata": { + "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning." + } + }, + "version": { + "type": "int", + "defaultValue": 1, + "allowedValues": [ + 1, + 2 + ], + "metadata": { + "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition." + } + } + }, + "variables": { + "copy": [ + { + "name": "partitionKeyPaths", + "count": "[length(parameters('paths'))]", + "input": "[if(startsWith(parameters('paths')[copyIndex('partitionKeyPaths')], '/'), parameters('paths')[copyIndex('partitionKeyPaths')], format('/{0}', parameters('paths')[copyIndex('partitionKeyPaths')]))]" + } + ], + "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, parameters('version'))), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]" + }, + "resources": { + "databaseAccount::sqlDatabase": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('sqlDatabaseName'))]" + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "container": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": "[variables('containerResourceParams')]", + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(and(equals(parameters('autoscaleSettingsMaxThroughput'), null()), not(equals(parameters('throughput'), -1))), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]" + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the container." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the container was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "sqlDatabase" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL database." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL database was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_sqlRoleDefinitions": { + "copy": { + "name": "databaseAccount_sqlRoleDefinitions", + "count": "[length(coalesce(parameters('dataPlaneRoleDefinitions'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sqlrd-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('dataPlaneRoleDefinitions'), createArray())[copyIndex()], 'name')]" + }, + "dataActions": { + "value": "[tryGet(coalesce(parameters('dataPlaneRoleDefinitions'), createArray())[copyIndex()], 'dataActions')]" + }, + "roleName": { + "value": "[coalesce(parameters('dataPlaneRoleDefinitions'), createArray())[copyIndex()].roleName]" + }, + "assignableScopes": { + "value": "[tryGet(coalesce(parameters('dataPlaneRoleDefinitions'), createArray())[copyIndex()], 'assignableScopes')]" + }, + "sqlRoleAssignments": { + "value": "[tryGet(coalesce(parameters('dataPlaneRoleDefinitions'), createArray())[copyIndex()], 'assignments')]" + } + }, + "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.35.1.17967", + "templateHash": "12119240119487993734" + }, + "name": "DocumentDB Database Account SQL Role Definitions.", + "description": "This module deploys a SQL Role Definision in a CosmosDB Account." + }, + "definitions": { + "sqlRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the SQL Role Assignments." + } + } + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the Role Definition." + } + }, + "roleName": { + "type": "string", + "metadata": { + "description": "Required. A user-friendly name for the Role Definition. Must be unique for the database account." + } + }, + "dataActions": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. An array of data actions that are allowed." + } + }, + "assignableScopes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition. This will allow application of this Role Definition on the entire database account or any underlying Database / Collection. Must have at least one element. Scopes higher than Database account are not enforceable as assignable Scopes. Note that resources referenced in assignable Scopes need not exist. Defaults to the current account." + } + }, + "sqlRoleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of SQL Role Assignments to be created for the SQL Role Definition." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlRoleDefinition": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role')))]", + "properties": { + "assignableScopes": "[coalesce(parameters('assignableScopes'), createArray(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]", + "permissions": [ + { + "dataActions": "[parameters('dataActions')]" + } + ], + "roleName": "[parameters('roleName')]", + "type": "CustomRole" + } + }, + "databaseAccount_sqlRoleAssignments": { + "copy": { + "name": "databaseAccount_sqlRoleAssignments", + "count": "[length(coalesce(parameters('sqlRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sqlra-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "roleDefinitionId": { + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role')))]" + }, + "principalId": { + "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'name')]" + } + }, + "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.35.1.17967", + "templateHash": "11941443499827753966" + }, + "name": "DocumentDB Database Account SQL Role Assignments.", + "description": "This module deploys a SQL Role Assignment in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the associated SQL Role Definition." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlRoleAssignment": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL Role Assignment." + }, + "value": "[coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL Role Assignment." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL Role Definition was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "sqlRoleDefinition" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL Role Definition." + }, + "value": "[coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL Role Definition." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL Role Definition was created in." + }, + "value": "[resourceGroup().name]" + }, + "roleName": { + "type": "string", + "metadata": { + "description": "The role name of the SQL Role Definition." + }, + "value": "[reference('sqlRoleDefinition').roleName]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_sqlRoleAssignments": { + "copy": { + "name": "databaseAccount_sqlRoleAssignments", + "count": "[length(coalesce(parameters('dataPlaneRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sqlra-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "roleDefinitionId": { + "value": "[coalesce(parameters('dataPlaneRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]" + }, + "principalId": { + "value": "[coalesce(parameters('dataPlaneRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('dataPlaneRoleAssignments'), createArray())[copyIndex()], 'name')]" + } + }, + "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.35.1.17967", + "templateHash": "11941443499827753966" + }, + "name": "DocumentDB Database Account SQL Role Assignments.", + "description": "This module deploys a SQL Role Assignment in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the associated SQL Role Definition." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlRoleAssignment": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL Role Assignment." + }, + "value": "[coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL Role Assignment." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL Role Definition was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_mongodbDatabases": { + "copy": { + "name": "databaseAccount_mongodbDatabases", + "count": "[length(coalesce(parameters('mongodbDatabases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-mongodb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "collections": { + "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'collections')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'throughput')]" + } + }, + "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.35.1.17967", + "templateHash": "16911349070369924403" + }, + "name": "DocumentDB Database Account MongoDB Databases", + "description": "This module deploys a MongoDB Database within a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the mongodb database." + } + }, + "throughput": { + "type": "int", + "defaultValue": 400, + "metadata": { + "description": "Optional. Request Units per second. Setting throughput at the database level is only recommended for development/test or when workload across all collections in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "collections": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Collections in the mongodb database." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "mongodbDatabase": { + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('name')]" + }, + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', parameters('throughput')))]" + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "mongodbDatabase_collections": { + "copy": { + "name": "mongodbDatabase_collections", + "count": "[length(coalesce(parameters('collections'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-collection-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('collections'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "mongodbDatabaseName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].name]" + }, + "indexes": { + "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].indexes]" + }, + "shardKey": { + "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].shardKey]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('collections'), createArray())[copyIndex()], 'throughput')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.35.1.17967", + "templateHash": "7802955893269337475" + }, + "name": "DocumentDB Database Account MongoDB Database Collections", + "description": "This module deploys a MongoDB Database Collection." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment." + } + }, + "mongodbDatabaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent mongodb database. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the collection." + } + }, + "throughput": { + "type": "int", + "defaultValue": 400, + "metadata": { + "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "indexes": { + "type": "array", + "metadata": { + "description": "Required. Indexes for the collection." + } + }, + "shardKey": { + "type": "object", + "metadata": { + "description": "Required. ShardKey for the collection." + } + } + }, + "resources": [ + { + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]", + "properties": { + "options": "[if(contains(reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), '2024-11-15').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', parameters('throughput')))]", + "resource": { + "id": "[parameters('name')]", + "indexes": "[parameters('indexes')]", + "shardKey": "[parameters('shardKey')]" + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the mongodb database collection." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the mongodb database collection." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the mongodb database collection was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "mongodbDatabase" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the mongodb database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the mongodb database." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the mongodb database was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_gremlinDatabases": { + "copy": { + "name": "databaseAccount_gremlinDatabases", + "count": "[length(coalesce(parameters('gremlinDatabases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-gremlin-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "graphs": { + "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'graphs')]" + }, + "maxThroughput": { + "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'maxThroughput')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'throughput')]" + } + }, + "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.35.1.17967", + "templateHash": "4743052544503629108" + }, + "name": "DocumentDB Database Account Gremlin Databases", + "description": "This module deploys a Gremlin Database within a CosmosDB Account." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Gremlin database." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the Gremlin database resource." + } + }, + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Gremlin database. Required if the template is used in a standalone deployment." + } + }, + "graphs": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of graphs to deploy in the Gremlin database." + } + }, + "maxThroughput": { + "type": "int", + "defaultValue": 4000, + "metadata": { + "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "gremlinDatabase": { + "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('maxThroughput')), null()), 'throughput', parameters('throughput')))]", + "resource": { + "id": "[parameters('name')]" + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "gremlinDatabase_gremlinGraphs": { + "copy": { + "name": "gremlinDatabase_gremlinGraphs", + "count": "[length(parameters('graphs'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-gremlindb-{1}', uniqueString(deployment().name, parameters('name')), parameters('graphs')[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('graphs')[copyIndex()].name]" + }, + "gremlinDatabaseName": { + "value": "[parameters('name')]" + }, + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "indexingPolicy": { + "value": "[tryGet(parameters('graphs')[copyIndex()], 'indexingPolicy')]" + }, + "partitionKeyPaths": "[if(not(empty(parameters('graphs')[copyIndex()].partitionKeyPaths)), createObject('value', parameters('graphs')[copyIndex()].partitionKeyPaths), 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.35.1.17967", + "templateHash": "9587717186996793648" + }, + "name": "DocumentDB Database Accounts Gremlin Databases Graphs", + "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the graph." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the Gremlin graph resource." + } + }, + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "gremlinDatabaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Gremlin Database. Required if the template is used in a standalone deployment." + } + }, + "indexingPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Indexing policy of the graph." + } + }, + "partitionKeyPaths": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of paths using which data within the container can be partitioned." + } + } + }, + "resources": { + "databaseAccount::gremlinDatabase": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'))]" + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "gremlinGraph": { + "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('name')]", + "indexingPolicy": "[if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null())]", + "partitionKey": { + "paths": "[if(not(empty(parameters('partitionKeyPaths'))), parameters('partitionKeyPaths'), null())]" + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the graph." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the graph." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the graph was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "gremlinDatabase" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Gremlin database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Gremlin database." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Gremlin database was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_tables": { + "copy": { + "name": "databaseAccount_tables", + "count": "[length(coalesce(parameters('tables'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-table-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('tables'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "maxThroughput": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'maxThroughput')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'throughput')]" + } + }, + "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.35.1.17967", + "templateHash": "14106261468136691896" + }, + "name": "Azure Cosmos DB account tables", + "description": "This module deploys a table within an Azure Cosmos DB Account." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags for the table." + } + }, + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Azure Cosmos DB account. Required if the template is used in a standalone deployment." + } + }, + "maxThroughput": { + "type": "int", + "defaultValue": 4000, + "metadata": { + "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "table": { + "type": "Microsoft.DocumentDB/databaseAccounts/tables", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('maxThroughput')), null()), 'throughput', parameters('throughput')))]", + "resource": { + "id": "[parameters('name')]" + } + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the table." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/tables', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the table was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_privateEndpoints": { + "copy": { + "name": "databaseAccount_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-dbAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', 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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.33.13.18514", + "templateHash": "15954548978129725136" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.10.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-11-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.33.13.18514", + "templateHash": "5440815542537978381" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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-11-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the database account." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the database account." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the database account was created in." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('databaseAccount', '2024-11-15', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('databaseAccount', '2024-11-15', 'full').location]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "The endpoint of the database account." + }, + "value": "[reference('databaseAccount').documentEndpoint]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the database account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "primaryReadWriteKey": { + "type": "securestring", + "metadata": { + "description": "The primary read-write key." + }, + "value": "[listKeys('databaseAccount', '2024-11-15').primaryMasterKey]" + }, + "primaryReadOnlyKey": { + "type": "securestring", + "metadata": { + "description": "The primary read-only key." + }, + "value": "[listKeys('databaseAccount', '2024-11-15').primaryReadonlyMasterKey]" + }, + "primaryReadWriteConnectionString": { + "type": "securestring", + "metadata": { + "description": "The primary read-write connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2024-11-15').connectionStrings[0].connectionString]" + }, + "primaryReadOnlyConnectionString": { + "type": "securestring", + "metadata": { + "description": "The primary read-only connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2024-11-15').connectionStrings[2].connectionString]" + }, + "secondaryReadWriteKey": { + "type": "securestring", + "metadata": { + "description": "The secondary read-write key." + }, + "value": "[listKeys('databaseAccount', '2024-11-15').secondaryMasterKey]" + }, + "secondaryReadOnlyKey": { + "type": "securestring", + "metadata": { + "description": "The secondary read-only key." + }, + "value": "[listKeys('databaseAccount', '2024-11-15').secondaryReadonlyMasterKey]" + }, + "secondaryReadWriteConnectionString": { + "type": "securestring", + "metadata": { + "description": "The secondary read-write connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2024-11-15').connectionStrings[1].connectionString]" + }, + "secondaryReadOnlyConnectionString": { + "type": "securestring", + "metadata": { + "description": "The secondary read-only connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2024-11-15').connectionStrings[3].connectionString]" + } + } + } + }, + "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cosmosDB)]", + "avmStorageAccount", + "keyvault", + "logAnalyticsWorkspace", + "network", + "userAssignedIdentity" + ] + }, + "avmStorageAccount": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.storage.storage-account.{0}', variables('storageAccountName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('storageAccountName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "managedIdentities": { + "value": { + "systemAssigned": true + } + }, + "minimumTlsVersion": { + "value": "TLS1_2" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "accessTier": { + "value": "Hot" + }, + "supportsHttpsTrafficOnly": { + "value": true + }, + "roleAssignments": { + "value": [ + { + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "roleDefinitionIdOrName": "Storage Blob Data Contributor", + "principalType": "ServicePrincipal" + } + ] + }, + "networkAcls": { + "value": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('enablePrivateNetworking'), 'Deny', 'Allow')]" + } + }, + "allowBlobPublicAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-blob-{0}', variables('solutionSuffix')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('name', 'storage-dns-zone-group-blob', 'privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)).outputs.resourceId.value))), 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value, 'service', 'blob'), createObject('name', format('pep-queue-{0}', variables('solutionSuffix')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('name', 'storage-dns-zone-group-queue', 'privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)).outputs.resourceId.value))), 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value, 'service', 'queue'))), createObject('value', createArray()))]", + "blobServices": { + "value": { + "corsRules": [], + "deleteRetentionPolicyEnabled": false, + "containers": [ + { + "name": "data", + "publicAccess": "None" + } + ] + } + } + }, + "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.35.1.17967", + "templateHash": "13086360467000063396" + }, + "name": "Storage Accounts", + "description": "This module deploys a Storage Account." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "__bicep_export!": 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 accessKey1 secret name to create." + } + }, + "connectionString1Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The connectionString1 secret name to create." + } + }, + "accessKey2Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The accessKey2 secret name to create." + } + }, + "connectionString2Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The connectionString2 secret name to create." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "localUserType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the local user used for SFTP Authentication." + } + }, + "hasSharedKey": { + "type": "bool", + "nullable": true, + "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", + "nullable": true, + "metadata": { + "description": "Optional. The local user home directory." + } + }, + "permissionScopes": { + "type": "array", + "items": { + "$ref": "#/definitions/permissionScopeType" + }, + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/sshAuthorizedKeyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "customerManagedKeyWithAutoRotateType": { + "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 version as per 'autoRotationEnabled' setting." + } + }, + "autoRotationEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable auto-rotating to the latest key version. Default is `true`. If set to `false`, the latest key version at the time of the deployment is used." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type supports auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "permissionScopeType": { + "type": "object", + "properties": { + "permissions": { + "type": "string", + "metadata": { + "description": "Required. The permissions for the local user. Possible values include: Read (r), Write (w), Delete (d), List (l), and Create (c)." + } + }, + "resourceName": { + "type": "string", + "metadata": { + "description": "Required. The name of resource, normally the container name or the file share name, used by the local user." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service used by the local user, e.g. blob, file." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "local-user/main.bicep" + } + } + }, + "privateEndpointMultiServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\" for a Storage Account's Private Endpoints." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can NOT be assumed (i.e., for services that have more than one subresource, like Storage Account with Blob (blob, table, queue, file, ...).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_1.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "sshAuthorizedKeyType": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "securestring", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "local-user/main.bicep" + } + } + } + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "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", + "Cold" + ], + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts@2024-01-01#properties/properties/properties/azureFilesIdentityBasedAuthentication" + }, + "description": "Optional. Provides the identity based authentication settings for Azure Files." + }, + "nullable": true + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointMultiServiceType" + }, + "nullable": true, + "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": false, + "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", + "nullable": true, + "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_2" + ], + "metadata": { + "description": "Optional. Set the minimum TLS version on request to storage. The TLS versions 1.0 and 1.1 are deprecated and not supported anymore." + } + }, + "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", + "items": { + "$ref": "#/definitions/localUserType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts@2024-01-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "allowedCopyScope": { + "type": "string", + "nullable": true, + "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", + "nullable": true, + "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/customerManagedKeyWithAutoRotateType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "sasExpirationPeriod": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The SAS expiration period. DD.HH:MM:SS." + } + }, + "sasExpirationAction": { + "type": "string", + "defaultValue": "Log", + "allowedValues": [ + "Block", + "Log" + ], + "metadata": { + "description": "Optional. The SAS expiration action. Allowed values are Block and Log." + } + }, + "keyType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Account", + "Service" + ], + "metadata": { + "description": "Optional. The keyType to use with Queue & Table services." + } + }, + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Key vault reference and secret settings for the module's secrets export." + } + } + }, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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": "[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 Privileged Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '69566ab7-960f-475b-8e7c-b3118f30c6bd')]", + "Storage File Data Privileged Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b8eda974-7b85-4f76-af95-65846b26df6d')]", + "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": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.20.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" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2024-11-30", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]" + }, + "storageAccount": { "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "[parameters('kind')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "properties": "[shallowMerge(createArray(createObject('allowSharedKeyAccess', parameters('allowSharedKeyAccess'), 'defaultToOAuthAuthentication', parameters('defaultToOAuthAuthentication'), 'allowCrossTenantReplication', parameters('allowCrossTenantReplication'), 'allowedCopyScope', parameters('allowedCopyScope'), 'customDomain', createObject('name', parameters('customDomainName'), 'useSubDomainName', parameters('customDomainUseSubDomainName')), 'dnsEndpointType', parameters('dnsEndpointType'), '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(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(coalesce(tryGet(parameters('customerManagedKey'), 'autoRotationEnabled'), true()), null(), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/'))))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2], split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))), 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', parameters('sasExpirationAction'), 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null()), 'supportsHttpsTrafficOnly', parameters('supportsHttpsTrafficOnly'), 'isHnsEnabled', parameters('enableHierarchicalNamespace'), '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()))), if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), createObject('azureFilesIdentityBasedAuthentication', parameters('azureFilesIdentityBasedAuthentication')), createObject())))]", + "dependsOn": [ + "cMKKeyVault", + "cMKKeyVault::cMKKey" + ] + }, + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts', 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": [ + "storageAccount" + ] + }, + "storageAccount_privateEndpoints": { + "copy": { + "name": "storageAccount_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sa-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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()], 'isManualConnection'), 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()], 'isManualConnection'), 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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.34.44.8038", + "templateHash": "12389807800450456797" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.11.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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.34.44.8038", + "templateHash": "13997305779829540948" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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', '2024-05-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(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": "[parameters('managementPolicyRules')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.35.1.17967", + "templateHash": "11585123047105458062" + }, + "name": "Storage Account Management Policies", + "description": "This module deploys a Storage Account Management Policy." + }, + "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": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/managementPolicies@2024-01-01#properties/properties/properties/policy/properties/rules" + }, + "description": "Required. The Storage Account ManagementPolicies Rules." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts/managementPolicies", + "apiVersion": "2024-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(coalesce(parameters('localUsers'), createArray()))]" + }, + "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": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].name]" + }, + "hasSshKey": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].hasSshKey]" + }, + "hasSshPassword": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].hasSshPassword]" + }, + "permissionScopes": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].permissionScopes]" + }, + "hasSharedKey": { + "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'hasSharedKey')]" + }, + "homeDirectory": { + "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'homeDirectory')]" + }, + "sshAuthorizedKeys": { + "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[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.35.1.17967", + "templateHash": "18350684375691178826" + }, + "name": "Storage Account Local Users", + "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication." + }, + "definitions": { + "sshAuthorizedKeyType": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "securestring", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "permissionScopeType": { + "type": "object", + "properties": { + "permissions": { + "type": "string", + "metadata": { + "description": "Required. The permissions for the local user. Possible values include: Read (r), Write (w), Delete (d), List (l), and Create (c)." + } + }, + "resourceName": { + "type": "string", + "metadata": { + "description": "Required. The name of resource, normally the container name or the file share name, used by the local user." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service used by the local user, e.g. blob, file." + } + } + }, + "metadata": { + "__bicep_export!": 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", + "items": { + "$ref": "#/definitions/permissionScopeType" + }, + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/sshAuthorizedKeyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "localUsers": { + "type": "Microsoft.Storage/storageAccounts/localUsers", + "apiVersion": "2024-01-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": "[parameters('sshAuthorizedKeys')]" + } + } + }, + "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.35.1.17967", + "templateHash": "6864791231608714221" + }, + "name": "Storage Account blob Services", + "description": "This module deploys a Storage Account Blob Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "defaultServiceVersion": { + "type": "string", + "nullable": true, + "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": 7, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "blobServices": { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2024-01-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": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]", + "defaultServiceVersion": "[parameters('defaultServiceVersion')]", + "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', '2024-01-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')]" + }, + "blobServiceName": { + "value": "[variables('name')]" + }, + "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.35.1.17967", + "templateHash": "16608863835956278253" + }, + "name": "Storage Account Blob Containers", + "description": "This module deploys a Storage Account Blob Container." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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." + } + }, + "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": { + "description": "Required. The name of the storage container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "nullable": true, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. A name-value pair to associate with the container as metadata." + }, + "defaultValue": {} + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "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": "[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": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('blobServiceName'))]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "container": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "properties": { + "defaultEncryptionScope": "[parameters('defaultEncryptionScope')]", + "denyEncryptionScopeOverride": "[parameters('denyEncryptionScopeOverride')]", + "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')]" + } + }, + "container_roleAssignments": { + "copy": { + "name": "container_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "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]", + "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": [ + "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.35.1.17967", + "templateHash": "16507112099495773673" + }, + "name": "Storage Account Blob Container Immutability Policies", + "description": "This module deploys a Storage Account Blob Container Immutability Policy." + }, + "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": "2024-01-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" + ] + } + }, + "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'), parameters('blobServiceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed container." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "blobServices" + ] + } + }, + "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')]" + }, + "corsRules": { + "value": "[tryGet(parameters('queueServices'), 'corsRules')]" + } + }, + "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.35.1.17967", + "templateHash": "16585885324390135986" + }, + "name": "Storage Account File Share Services", + "description": "This module deploys a Storage Account File Share Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/protocolSettings" + }, + "description": "Optional. Protocol settings for file service." + }, + "defaultValue": {} + }, + "shareDeleteRetentionPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/shareDeleteRetentionPolicy" + }, + "description": "Optional. The service properties for soft delete." + }, + "defaultValue": { + "enabled": true, + "days": 7 + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "fileServices": { + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]", + "protocolSettings": "[parameters('protocolSettings')]", + "shareDeleteRetentionPolicy": "[parameters('shareDeleteRetentionPolicy')]" + } + }, + "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', '2024-01-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.35.1.17967", + "templateHash": "190690872747761309" + }, + "name": "Storage Account File Shares", + "description": "This module deploys a Storage Account File Share." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "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": "[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": { + "storageAccount::fileService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "fileShare": { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2024-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')]" + } + }, + "fileShare_roleAssignments": { + "copy": { + "name": "fileShare_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Share-Rbac-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "scope": { + "value": "[replace(resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name')), '/shares/', '/fileshares/')]" + }, + "name": { + "value": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]" + }, + "roleDefinitionId": { + "value": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]" + }, + "principalId": { + "value": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "principalType": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]" + }, + "condition": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]" + }, + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), createObject('value', coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0')), createObject('value', null()))]", + "delegatedManagedIdentityResourceId": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "description": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]" + } + }, + "template": { + "$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())]" + } + } + ] + } + }, + "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')]" + }, + "corsRules": { + "value": "[tryGet(parameters('queueServices'), 'corsRules')]" + } + }, + "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.35.1.17967", + "templateHash": "15089132876669102729" + }, + "name": "Storage Account Queue Services", + "description": "This module deploys a Storage Account Queue Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "queueServices": { + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]" + } + }, + "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.35.1.17967", + "templateHash": "9203389950224823099" + }, + "name": "Storage Account Queues", + "description": "This module deploys a Storage Account Queue." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. A name-value pair that represents queue metadata." + }, + "defaultValue": {} + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "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": "[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": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "queue": { + "type": "Microsoft.Storage/storageAccounts/queueServices/queues", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]" + } + }, + "queue_roleAssignments": { + "copy": { + "name": "queue_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), 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": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', 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": [ + "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]" + } + } + } + } + } + }, + "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')]" + }, + "corsRules": { + "value": "[tryGet(parameters('tableServices'), 'corsRules')]" + } + }, + "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.35.1.17967", + "templateHash": "17345564162551993063" + }, + "name": "Storage Account Table Services", + "description": "This module deploys a Storage Account Table Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "tableServices": { + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]" + } + }, + "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.35.1.17967", + "templateHash": "6286190839827082273" + }, + "name": "Storage Account Table", + "description": "This module deploys a Storage Account Table." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + } + }, + "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')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "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')]", + "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": "2024-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[parameters('storageAccountName')]" + }, + "table": { + "type": "Microsoft.Storage/storageAccounts/tableServices/tables", + "apiVersion": "2024-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), 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": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', 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": [ + "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]" + } + } + } + } + } + }, + "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" + ] + }, + "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(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('storageAccount', '2024-01-01').keys[0].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString1Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2024-01-01').keys[0].value, environment().suffixes.storage))), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('storageAccount', '2024-01-01').keys[1].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString2Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2024-01-01').keys[1].value, environment().suffixes.storage))), 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.35.1.17967", + "templateHash": "15126360152170162999" + } + }, + "definitions": { + "secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the secret to set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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]" + } + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetOutputType" + }, + "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]", + "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]" + } + } + } + } + } + }, + "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", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('storageAccount', '2024-01-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('storageAccount', '2024-01-01', 'full').location]" + }, + "serviceEndpoints": { + "type": "object", + "metadata": { + "description": "All service endpoints of the deployed storage account, Note Standard_LRS and Standard_ZRS accounts only have a blob service endpoint." + }, + "value": "[reference('storageAccount').primaryEndpoints]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the Storage Account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "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())]" + }, + "primaryAccessKey": { + "type": "securestring", + "metadata": { + "description": "The primary access key of the storage account." + }, + "value": "[listKeys('storageAccount', '2024-01-01').keys[0].value]" + }, + "secondayAccessKey": { + "type": "securestring", + "metadata": { + "description": "The secondary access key of the storage account." + }, + "value": "[listKeys('storageAccount', '2024-01-01').keys[1].value]" + }, + "primaryConnectionString": { + "type": "securestring", + "metadata": { + "description": "The primary connection string of the storage account." + }, + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2024-01-01').keys[0].value, environment().suffixes.storage)]" + }, + "secondaryConnectionString": { + "type": "securestring", + "metadata": { + "description": "The secondary connection string of the storage account." + }, + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2024-01-01').keys[1].value, environment().suffixes.storage)]" + } + } + } + }, + "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", + "keyvault", + "network", + "userAssignedIdentity" + ] + }, + "saveStorageAccountSecretsInKeyVault": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('saveStorageAccountSecretsInKeyVault.{0}', variables('keyVaultName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('keyVaultName')]" + }, + "enablePurgeProtection": { + "value": "[parameters('enablePurgeProtection')]" + }, + "enableVaultForDeployment": { + "value": true + }, + "enableVaultForDiskEncryption": { + "value": true + }, + "enableVaultForTemplateDeployment": { + "value": true + }, + "enableRbacAuthorization": { + "value": true + }, + "enableSoftDelete": { + "value": true + }, + "softDeleteRetentionInDays": { + "value": 7 + }, + "secrets": { + "value": [ + { + "name": "ADLS-ACCOUNT-NAME", + "value": "[variables('storageAccountName')]" + }, + { + "name": "ADLS-ACCOUNT-CONTAINER", + "value": "data" + }, + { + "name": "ADLS-ACCOUNT-KEY", + "value": "[listOutputsWithSecureValues('avmStorageAccount', '2022-09-01').primaryAccessKey]" + } + ] + } + }, + "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.33.93.31351", + "templateHash": "17553975707245390963" + }, + "name": "Key Vaults", + "description": "This module deploys a Key Vault." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "credentialOutputType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The item's resourceId." + } + }, + "uri": { + "type": "string", + "metadata": { + "description": "The item's uri." + } + }, + "uriWithVersion": { + "type": "string", + "metadata": { + "description": "The item's uri with version." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a credential output." + } + }, + "accessPolicyType": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an access policy." + } + }, + "secretType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributes": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Defines whether the secret is enabled or disabled." + } + }, + "exp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Defines when the secret will become invalid. Defined in seconds since 1970-01-01T00:00:00Z." + } + }, + "nbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. If set, defines the date from which onwards the secret becomes valid. Defined in seconds since 1970-01-01T00:00:00Z." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Contains attributes of the secret." + } + }, + "contentType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a secret output." + } + }, + "keyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributes": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Defines whether the key is enabled or disabled." + } + }, + "exp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Defines when the key will become invalid. Defined in seconds since 1970-01-01T00:00:00Z." + } + }, + "nbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. If set, defines the date from which onwards the key becomes valid. Defined in seconds since 1970-01-01T00:00:00Z." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Contains attributes of the key." + } + }, + "curveName": { + "type": "string", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "nullable": true, + "metadata": { + "description": "Optional. The elliptic curve name. Only works if \"keySize\" equals \"EC\" or \"EC-HSM\". Default is \"P-256\"." + } + }, + "keyOps": { + "type": "array", + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "release", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. The allowed operations on this key." + } + }, + "keySize": { + "type": "int", + "allowedValues": [ + 2048, + 3072, + 4096 + ], + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. Only works if \"keySize\" equals \"RSA\" or \"RSA-HSM\". Default is \"4096\"." + } + }, + "kty": { + "type": "string", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of the key. Default is \"EC\"." + } + }, + "releasePolicy": { + "type": "object", + "properties": { + "contentType": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Content type and version of key release policy." + } + }, + "data": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Blob encoding the policy rules under which the key can be released." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, + "rotationPolicy": { + "$ref": "#/definitions/rotationPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a key." + } + }, + "rotationPolicyType": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "expiryTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The expiration time for the new key version. It should be in ISO8601 format. Eg: \"P90D\", \"P1Y\"." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The attributes of key rotation policy." + } + }, + "lifetimeActions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "action": { + "type": "object", + "properties": { + "type": { + "type": "string", + "allowedValues": [ + "Notify", + "Rotate" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of action." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The action of key rotation policy lifetimeAction." + } + }, + "trigger": { + "type": "object", + "properties": { + "timeAfterCreate": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time duration after key creation to rotate the key. It only applies to rotate. It will be in ISO 8601 duration format. Eg: \"P90D\", \"P1Y\"." + } + }, + "timeBeforeExpiry": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time duration before key expiring to rotate or notify. It will be in ISO 8601 duration format. Eg: \"P90D\", \"P1Y\"." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The trigger of key rotation policy lifetimeAction." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The lifetimeActions for key rotation action." + } + } + }, + "metadata": { + "description": "The type for a rotation policy." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Key Vault. Must be globally unique." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "accessPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/accessPolicyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All access policies to create." + } + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/secretType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All secrets to create." + } + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/keyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All keys to create." + } + }, + "enableVaultForDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for deployment by script or compute." + } + }, + "enableVaultForTemplateDeployment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the vault is enabled for a template deployment." + } + }, + "enableVaultForDiskEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies if the azure platform has access to the vault for enabling disk encryption scenarios." + } + }, + "enableSoftDelete": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Switch to enable/disable Key Vault's soft delete feature." + } + }, + "softDeleteRetentionInDays": { + "type": "int", + "defaultValue": 90, + "metadata": { + "description": "Optional. softDelete data retention days. It accepts >=7 and <=90." + } + }, + "enableRbacAuthorization": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. Note that management actions are always authorized with RBAC." + } + }, + "createMode": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The vault's create mode to indicate whether the vault need to be recovered or not. - recover or default." + } + }, + "enablePurgeProtection": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Provide 'true' to enable Key Vault's purge protection feature." + } + }, + "sku": { + "type": "string", + "defaultValue": "premium", + "allowedValues": [ + "premium", + "standard" + ], + "metadata": { + "description": "Optional. Specifies the SKU for the vault." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Rules governing the accessibility of the resource from specific network locations." + } + }, + "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." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "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": "formattedAccessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('formattedAccessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ], + "enableReferencedModulesTelemetry": false, + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Certificates Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a4417e6f-fecd-4de8-b567-7b0420556985')]", + "Key Vault Certificate User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db79e9a7-68ee-4b58-9aeb-b90e7c24fcba')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "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.keyvault-vault.{0}.{1}', replace('0.12.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" + } + } + } + } + }, + "keyVault": { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "enabledForDeployment": "[parameters('enableVaultForDeployment')]", + "enabledForTemplateDeployment": "[parameters('enableVaultForTemplateDeployment')]", + "enabledForDiskEncryption": "[parameters('enableVaultForDiskEncryption')]", + "enableSoftDelete": "[parameters('enableSoftDelete')]", + "softDeleteRetentionInDays": "[parameters('softDeleteRetentionInDays')]", + "enableRbacAuthorization": "[parameters('enableRbacAuthorization')]", + "createMode": "[parameters('createMode')]", + "enablePurgeProtection": "[if(parameters('enablePurgeProtection'), parameters('enablePurgeProtection'), null())]", + "tenantId": "[subscription().tenantId]", + "accessPolicies": "[variables('formattedAccessPolicies')]", + "sku": { + "name": "[parameters('sku')]", + "family": "A" + }, + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass'), 'defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(coalesce(parameters('privateEndpoints'), createArray()))), empty(coalesce(parameters('networkAcls'), createObject()))), 'Disabled', null()))]" + } + }, + "keyVault_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.KeyVault/vaults/{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": [ + "keyVault" + ] + }, + "keyVault_diagnosticSettings": { + "copy": { + "name": "keyVault_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{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": [ + "keyVault" + ] + }, + "keyVault_roleAssignments": { + "copy": { + "name": "keyVault_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.KeyVault/vaults', 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": [ + "keyVault" + ] + }, + "keyVault_accessPolicies": { + "condition": "[not(empty(parameters('accessPolicies')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-AccessPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('name')]" + }, + "accessPolicies": { + "value": "[parameters('accessPolicies')]" + } + }, + "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.33.93.31351", + "templateHash": "6321524620984159084" + }, + "name": "Key Vault Access Policies", + "description": "This module deploys a Key Vault Access Policy." + }, + "definitions": { + "accessPoliciesType": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tenant ID that is used for authenticating requests to the key vault." + } + }, + "objectId": { + "type": "string", + "metadata": { + "description": "Required. The object ID of a user, service principal or security group in the tenant for the vault." + } + }, + "applicationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Application ID of the client making request on behalf of a principal." + } + }, + "permissions": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "decrypt", + "delete", + "encrypt", + "get", + "getrotationpolicy", + "import", + "list", + "purge", + "recover", + "release", + "restore", + "rotate", + "setrotationpolicy", + "sign", + "unwrapKey", + "update", + "verify", + "wrapKey" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to keys." + } + }, + "secrets": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "get", + "list", + "purge", + "recover", + "restore", + "set" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to secrets." + } + }, + "certificates": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "create", + "delete", + "deleteissuers", + "get", + "getissuers", + "import", + "list", + "listissuers", + "managecontacts", + "manageissuers", + "purge", + "recover", + "restore", + "setissuers", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to certificates." + } + }, + "storage": { + "type": "array", + "allowedValues": [ + "all", + "backup", + "delete", + "deletesas", + "get", + "getsas", + "list", + "listsas", + "purge", + "recover", + "regeneratekey", + "restore", + "set", + "setsas", + "update" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions to storage accounts." + } + } + }, + "metadata": { + "description": "Required. Permissions the identity has for keys, secrets and certificates." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an access policy." + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "accessPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/accessPoliciesType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of 0 to 16 identities that have access to the key vault. All identities in the array must use the same tenant ID as the key vault's tenant ID." + } + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "policies": { + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), 'add')]", + "properties": { + "copy": [ + { + "name": "accessPolicies", + "count": "[length(coalesce(parameters('accessPolicies'), createArray()))]", + "input": { + "applicationId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')], 'applicationId'), '')]", + "objectId": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')].objectId]", + "permissions": "[coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')].permissions]", + "tenantId": "[coalesce(tryGet(coalesce(parameters('accessPolicies'), createArray())[copyIndex('accessPolicies')], 'tenantId'), tenant().tenantId)]" + } + } + ] + } + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the access policies assignment was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the access policies assignment." + }, + "value": "add" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the access policies assignment." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/accessPolicies', parameters('keyVaultName'), 'add')]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_secrets": { + "copy": { + "name": "keyVault_secrets", + "count": "[length(coalesce(parameters('secrets'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Secret-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('secrets'), createArray())[copyIndex()].name]" + }, + "value": { + "value": "[coalesce(parameters('secrets'), createArray())[copyIndex()].value]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributes'), 'enabled')]" + }, + "attributesExp": { + "value": "[tryGet(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributes'), 'exp')]" + }, + "attributesNbf": { + "value": "[tryGet(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'attributes'), 'nbf')]" + }, + "contentType": { + "value": "[tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'contentType')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('secrets'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('secrets'), 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.33.93.31351", + "templateHash": "4741547827723795923" + }, + "name": "Key Vault Secrets", + "description": "This module deploys a Key Vault Secret." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "contentType": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The content type of the secret." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret. NOTE: \"value\" will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "Key Vault Secrets Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7')]", + "Key Vault Secrets User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')]", + "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": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "secret": { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "contentType": "[parameters('contentType')]", + "attributes": { + "enabled": "[parameters('attributesEnabled')]", + "exp": "[parameters('attributesExp')]", + "nbf": "[parameters('attributesNbf')]" + }, + "value": "[parameters('value')]" + } + }, + "secret_roleAssignments": { + "copy": { + "name": "secret_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/secrets/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), 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": [ + "secret" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the secret." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the secret." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('name'))]" + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The uri of the secret." + }, + "value": "[reference('secret').secretUri]" + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The uri with version of the secret." + }, + "value": "[reference('secret').secretUriWithVersion]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the secret was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_keys": { + "copy": { + "name": "keyVault_keys", + "count": "[length(coalesce(parameters('keys'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-KeyVault-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('keys'), createArray())[copyIndex()].name]" + }, + "keyVaultName": { + "value": "[parameters('name')]" + }, + "attributesEnabled": { + "value": "[tryGet(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributes'), 'enabled')]" + }, + "attributesExp": { + "value": "[tryGet(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributes'), 'exp')]" + }, + "attributesNbf": { + "value": "[tryGet(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'attributes'), 'nbf')]" + }, + "curveName": "[if(and(not(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA')), not(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA-HSM'))), createObject('value', coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'curveName'), 'P-256')), createObject('value', null()))]", + "keyOps": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keyOps')]" + }, + "keySize": "[if(or(equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA'), equals(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'RSA-HSM')), createObject('value', coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'keySize'), 4096)), createObject('value', null()))]", + "releasePolicy": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'releasePolicy'), createObject())]" + }, + "kty": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'kty'), 'EC')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "rotationPolicy": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'rotationPolicy')]" + } + }, + "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.33.93.31351", + "templateHash": "12000970886778046699" + }, + "name": "Key Vault Keys", + "description": "This module deploys a Key Vault Key." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent key vault. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the key." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "attributesEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Determines whether the object is enabled." + } + }, + "attributesExp": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Expiry date in seconds since 1970-01-01T00:00:00Z. For security reasons, it is recommended to set an expiration date whenever possible." + } + }, + "attributesNbf": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Not before date in seconds since 1970-01-01T00:00:00Z." + } + }, + "curveName": { + "type": "string", + "defaultValue": "P-256", + "allowedValues": [ + "P-256", + "P-256K", + "P-384", + "P-521" + ], + "metadata": { + "description": "Optional. The elliptic curve name." + } + }, + "keyOps": { + "type": "array", + "nullable": true, + "allowedValues": [ + "decrypt", + "encrypt", + "import", + "sign", + "unwrapKey", + "verify", + "wrapKey" + ], + "metadata": { + "description": "Optional. Array of JsonWebKeyOperation." + } + }, + "keySize": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The key size in bits. For example: 2048, 3072, or 4096 for RSA." + } + }, + "kty": { + "type": "string", + "defaultValue": "EC", + "allowedValues": [ + "EC", + "EC-HSM", + "RSA", + "RSA-HSM" + ], + "metadata": { + "description": "Optional. The type of the key." + } + }, + "releasePolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key release policy." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "rotationPolicy": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Key rotation policy properties object." + } + } + }, + "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')]", + "Key Vault Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", + "Key Vault Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f25e0fa2-a7c8-4377-a976-54943a77a395')]", + "Key Vault Crypto Officer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '14b46e9e-c2b7-41b4-b07b-48a6ebf60603')]", + "Key Vault Crypto Service Encryption User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]", + "Key Vault Crypto User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '12338af0-0e69-4776-bea7-57ae8d297424')]", + "Key Vault Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2')]", + "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": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[parameters('keyVaultName')]" + }, + "key": { + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": "[shallowMerge(createArray(createObject('attributes', createObject('enabled', parameters('attributesEnabled'), 'exp', parameters('attributesExp'), 'nbf', parameters('attributesNbf')), 'curveName', parameters('curveName'), 'keyOps', parameters('keyOps'), 'keySize', parameters('keySize'), 'kty', parameters('kty'), 'release_policy', coalesce(parameters('releasePolicy'), createObject())), if(not(empty(parameters('rotationPolicy'))), createObject('rotationPolicy', parameters('rotationPolicy')), createObject())))]" + }, + "key_roleAssignments": { + "copy": { + "name": "key_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.KeyVault/vaults/{0}/keys/{1}', parameters('keyVaultName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), 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": [ + "key" + ] + } + }, + "outputs": { + "keyUri": { + "type": "string", + "metadata": { + "description": "The uri of the key." + }, + "value": "[reference('key').keyUri]" + }, + "keyUriWithVersion": { + "type": "string", + "metadata": { + "description": "The uri with version of the key." + }, + "value": "[reference('key').keyUriWithVersion]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults/keys', parameters('keyVaultName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + }, + "keyVault_privateEndpoints": { + "copy": { + "name": "keyVault_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-keyVault-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.KeyVault/vaults', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.KeyVault/vaults', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'vault')), '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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.33.13.18514", + "templateHash": "15954548978129725136" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.10.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-11-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.33.13.18514", + "templateHash": "5440815542537978381" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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-11-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "keyVault" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key vault." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the key vault was created in." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the key vault." + }, + "value": "[parameters('name')]" + }, + "uri": { + "type": "string", + "metadata": { + "description": "The URI of the key vault." + }, + "value": "[reference('keyVault').vaultUri]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('keyVault', '2022-07-01', 'full').location]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the key vault." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('keyVault_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/credentialOutputType" + }, + "metadata": { + "description": "The properties of the created secrets." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('secrets'), createArray()))))]", + "input": { + "resourceId": "[reference(format('keyVault_secrets[{0}]', range(0, length(coalesce(parameters('secrets'), createArray())))[copyIndex()])).outputs.resourceId.value]", + "uri": "[reference(format('keyVault_secrets[{0}]', range(0, length(coalesce(parameters('secrets'), createArray())))[copyIndex()])).outputs.secretUri.value]", + "uriWithVersion": "[reference(format('keyVault_secrets[{0}]', range(0, length(coalesce(parameters('secrets'), createArray())))[copyIndex()])).outputs.secretUriWithVersion.value]" + } + } + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/credentialOutputType" + }, + "metadata": { + "description": "The properties of the created keys." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('keys'), createArray()))))]", + "input": { + "resourceId": "[reference(format('keyVault_keys[{0}]', range(0, length(coalesce(parameters('keys'), createArray())))[copyIndex()])).outputs.resourceId.value]", + "uri": "[reference(format('keyVault_keys[{0}]', range(0, length(coalesce(parameters('keys'), createArray())))[copyIndex()])).outputs.keyUri.value]", + "uriWithVersion": "[reference(format('keyVault_keys[{0}]', range(0, length(coalesce(parameters('keys'), createArray())))[copyIndex()])).outputs.keyUriWithVersion.value]" + } + } + } + } + } + }, + "dependsOn": [ + "avmStorageAccount" + ] + }, + "sqlDBModule": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "serverDeployment", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('sql-{0}', variables('solutionSuffix'))]" + }, + "administrators": { + "value": { + "azureADOnlyAuthentication": true, + "login": "[reference('userAssignedIdentity').outputs.name.value]", + "principalType": "Application", + "sid": "[reference('userAssignedIdentity').outputs.principalId.value]", + "tenantId": "[subscription().tenantId]" + } + }, + "connectionPolicy": { + "value": "Redirect" + }, + "databases": { + "value": [ + { + "availabilityZone": 1, + "backupLongTermRetentionPolicy": { + "monthlyRetention": "P6M" + }, + "backupShortTermRetentionPolicy": { + "retentionDays": 14 + }, + "collation": "SQL_Latin1_General_CP1_CI_AS", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value)), null())]", + "elasticPoolResourceId": "[resourceId('Microsoft.Sql/servers/elasticPools', format('sql-{0}', variables('solutionSuffix')), 'sqlswaf-ep-001')]", + "licenseType": "LicenseIncluded", + "maxSizeBytes": 34359738368, + "name": "[format('sqldb-{0}', variables('solutionSuffix'))]", + "sku": { + "capacity": 0, + "name": "ElasticPool", + "tier": "GeneralPurpose" + } + } + ] + }, + "elasticPools": { + "value": [ + { + "availabilityZone": -1, + "name": "sqlswaf-ep-001", + "sku": { + "capacity": 10, + "name": "GP_Gen5", + "tier": "GeneralPurpose" + }, + "roleAssignments": [ + { + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "db_datareader" + }, + { + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "db_datawriter" + } + ] + } + ] + }, + "firewallRules": { + "value": [ + { + "endIpAddress": "255.255.255.255", + "name": "AllowSpecificRange", + "startIpAddress": "0.0.0.0" + }, + { + "endIpAddress": "0.0.0.0", + "name": "AllowAllWindowsAzureIps", + "startIpAddress": "0.0.0.0" + } + ] + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "managedIdentities": { + "value": { + "systemAssigned": true, + "userAssignedResourceIds": [ + "[reference('userAssignedIdentity').outputs.resourceId.value]" + ] + } + }, + "primaryUserAssignedIdentityResourceId": { + "value": "[reference('userAssignedIdentity').outputs.resourceId.value]" + }, + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').sqlServer)).outputs.resourceId.value))), 'service', 'sqlServer', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value, 'tags', parameters('tags')))), createObject('value', createArray()))]", + "restrictOutboundNetworkAccess": { + "value": "Disabled" + }, + "securityAlertPolicies": { + "value": [ + { + "emailAccountAdmins": true, + "name": "Default", + "state": "Enabled" + } + ] + }, + "tags": { + "value": "[parameters('tags')]" + }, + "virtualNetworkRules": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('ignoreMissingVnetServiceEndpoint', true(), 'name', 'newVnetRule1', 'virtualNetworkSubnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]", + "vulnerabilityAssessmentsObj": { + "value": { + "name": "default", + "storageAccountResourceId": "[reference('avmStorageAccount').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.37.4.10188", + "templateHash": "2243885230640205079" + }, + "name": "Azure SQL Servers", + "description": "This module deploys an Azure SQL Server." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a private endpoint output." + } + }, + "auditSettingsType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the name of the audit settings." + } + }, + "auditActionsAndGroups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the Actions-Groups and Actions to audit." + } + }, + "isAzureMonitorTargetEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether audit events are sent to Azure Monitor." + } + }, + "isDevopsAuditEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the state of devops audit. If state is Enabled, devops logs will be sent to Azure Monitor." + } + }, + "isManagedIdentityInUse": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether Managed Identity is used to access blob storage." + } + }, + "isStorageSecondaryKeyInUse": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether storageAccountAccessKey value is the storage's secondary key." + } + }, + "queueDelayMs": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the amount of time in milliseconds that can elapse before audit actions are forced to be processed." + } + }, + "retentionDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the number of days to keep in the audit logs in the storage account." + } + }, + "state": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the state of the audit. If state is Enabled, storageEndpoint or isAzureMonitorTargetEnabled are required." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the identifier key of the auditing storage account." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for audit settings." + } + }, + "secretsExportConfigurationType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the key vault where to store the secrets of this module." + } + }, + "sqlAdminPasswordSecretName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The sqlAdminPassword secret name to create." + } + }, + "sqlAzureConnectionStringSecretName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The sqlAzureConnectionString secret name to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a secrets export configuration." + } + }, + "serverExternalAdministratorType": { + "type": "object", + "properties": { + "administratorType": { + "type": "string", + "allowedValues": [ + "ActiveDirectory" + ], + "nullable": true, + "metadata": { + "description": "Optional. Type of the sever administrator." + } + }, + "azureADOnlyAuthentication": { + "type": "bool", + "metadata": { + "description": "Required. Azure Active Directory only Authentication enabled." + } + }, + "login": { + "type": "string", + "metadata": { + "description": "Required. Login name of the server administrator." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Application", + "Group", + "User" + ], + "metadata": { + "description": "Required. Principal Type of the sever administrator." + } + }, + "sid": { + "type": "string", + "metadata": { + "description": "Required. SID (object ID) of the server administrator." + } + }, + "tenantId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Tenant ID of the administrator." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a sever-external administrator." + } + }, + "databaseType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Elastic Pool." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Sql/servers/databases@2023-08-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the database." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityOnlyUserAssignedType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identities for the database." + } + }, + "sku": { + "$ref": "#/definitions/databaseSkuType", + "nullable": true, + "metadata": { + "description": "Optional. The database SKU." + } + }, + "autoPauseDelay": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled." + } + }, + "availabilityZone": { + "type": "int", + "allowedValues": [ + -1, + 1, + 2, + 3 + ], + "metadata": { + "description": "Required. If set to 1, 2 or 3, the availability zone is hardcoded to that value. If set to -1, no zone is defined. Note that the availability zone numbers here are the logical availability zone in your Azure subscription. Different subscriptions might have a different mapping of the physical zone and logical zone. To understand more, please refer to [Physical and logical availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview?tabs=azure-cli#physical-and-logical-availability-zones)." + } + }, + "catalogCollation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Collation of the metadata catalog." + } + }, + "collation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The collation of the database." + } + }, + "createMode": { + "type": "string", + "allowedValues": [ + "Copy", + "Default", + "OnlineSecondary", + "PointInTimeRestore", + "Recovery", + "Restore", + "RestoreExternalBackup", + "RestoreExternalBackupSecondary", + "RestoreLongTermRetentionBackup", + "Secondary" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the mode of database creation." + } + }, + "elasticPoolResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the elastic pool containing this database." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyWithAutoRotateType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition for database TDE." + } + }, + "federatedClientId": { + "type": "string", + "nullable": true, + "minLength": 36, + "maxLength": 36, + "metadata": { + "description": "Optional. The Client id used for cross tenant per database CMK scenario." + } + }, + "freeLimitExhaustionBehavior": { + "type": "string", + "allowedValues": [ + "AutoPause", + "BillOverUsage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the behavior when monthly free limits are exhausted for the free database." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of secondary replicas associated with the database that are used to provide high availability. Not applicable to a Hyperscale database within an elastic pool." + } + }, + "isLedgerOn": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not this database is a ledger database, which means all tables in the database are ledger tables." + } + }, + "licenseType": { + "type": "string", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "nullable": true, + "metadata": { + "description": "Optional. The license type to apply for this database." + } + }, + "longTermRetentionBackupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the long term retention backup associated with create operation of this database." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maintenance configuration id assigned to the database. This configuration defines the period when the maintenance updates will occur." + } + }, + "manualCutover": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not customer controlled manual cutover needs to be done during Update Database operation to Hyperscale tier." + } + }, + "maxSizeBytes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The max size of the database expressed in bytes." + } + }, + "minCapacity": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Minimal capacity that database will always have allocated, if not paused." + } + }, + "performCutover": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. To trigger customer controlled manual cutover during the wait state while Scaling operation is in progress." + } + }, + "preferredEnclaveType": { + "type": "string", + "allowedValues": [ + "Default", + "VBS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Type of enclave requested on the database." + } + }, + "readScale": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The state of read-only routing. If enabled, connections that have application intent set to readonly in their connection string may be routed to a readonly secondary replica in the same region. Not applicable to a Hyperscale database within an elastic pool." + } + }, + "recoverableDatabaseResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the recoverable database associated with create operation of this database." + } + }, + "recoveryServicesRecoveryPointResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the recovery point associated with create operation of this database." + } + }, + "requestedBackupStorageRedundancy": { + "type": "string", + "allowedValues": [ + "Geo", + "GeoZone", + "Local", + "Zone" + ], + "nullable": true, + "metadata": { + "description": "Optional. The storage account type to be used to store backups for this database." + } + }, + "restorableDroppedDatabaseResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the restorable dropped database associated with create operation of this database." + } + }, + "restorePointInTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database." + } + }, + "sampleName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the sample schema to apply when creating this database." + } + }, + "secondaryType": { + "type": "string", + "allowedValues": [ + "Geo", + "Named", + "Standby" + ], + "nullable": true, + "metadata": { + "description": "Optional. The secondary type of the database if it is a secondary." + } + }, + "sourceDatabaseDeletionDate": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the time that the database was deleted." + } + }, + "sourceDatabaseResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the source database associated with create operation of this database." + } + }, + "sourceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the source associated with the create operation of this database." + } + }, + "useFreeLimit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not the database uses free monthly limits. Allowed on one database in a subscription." + } + }, + "zoneRedundant": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not this database is zone redundant, which means the replicas of this database will be spread across multiple availability zones." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "backupShortTermRetentionPolicy": { + "$ref": "#/definitions/shortTermBackupRetentionPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. The short term backup retention policy for the database." + } + }, + "backupLongTermRetentionPolicy": { + "$ref": "#/definitions/longTermBackupRetentionPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. The long term backup retention policy for the database." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a database." + } + }, + "elasticPoolType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Elastic Pool." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the elastic pool." + } + }, + "sku": { + "$ref": "#/definitions/skuType", + "nullable": true, + "metadata": { + "description": "Optional. The elastic pool SKU." + } + }, + "autoPauseDelay": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Time in minutes after which elastic pool is automatically paused. A value of -1 means that automatic pause is disabled." + } + }, + "availabilityZone": { + "type": "int", + "allowedValues": [ + -1, + 1, + 2, + 3 + ], + "metadata": { + "description": "Required. If set to 1, 2 or 3, the availability zone is hardcoded to that value. If set to -1, no zone is defined. Note that the availability zone numbers here are the logical availability zone in your Azure subscription. Different subscriptions might have a different mapping of the physical zone and logical zone. To understand more, please refer to [Physical and logical availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview?tabs=azure-cli#physical-and-logical-availability-zones)." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools." + } + }, + "licenseType": { + "type": "string", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "nullable": true, + "metadata": { + "description": "Optional. The license type to apply for this elastic pool." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maintenance configuration id assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur." + } + }, + "maxSizeBytes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The storage limit for the database elastic pool in bytes." + } + }, + "minCapacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Minimal capacity that serverless pool will not shrink below, if not paused." + } + }, + "perDatabaseSettings": { + "$ref": "#/definitions/perDatabaseSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The per database settings for the elastic pool." + } + }, + "preferredEnclaveType": { + "type": "string", + "allowedValues": [ + "Default", + "VBS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Type of enclave requested on the elastic pool." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "zoneRedundant": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an elastic pool property." + } + }, + "vulnerabilityAssessmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the vulnerability assessment." + } + }, + "recurringScans": { + "$ref": "#/definitions/recurringScansType", + "nullable": true, + "metadata": { + "description": "Optional. The recurring scans settings." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the storage account to store the scan reports." + } + }, + "useStorageAccountAccessKey": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether to use the storage account access key to access the storage account." + } + }, + "createStorageRoleAssignment": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether to create a role assignment for the storage account." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a vulnerability assessment." + } + }, + "firewallRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the firewall rule." + } + }, + "startIpAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The start IP address of the firewall rule. Must be IPv4 format. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "endIpAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a firewall rule." + } + }, + "keyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the key. Must follow the [__] pattern." + } + }, + "serverKeyType": { + "type": "string", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "nullable": true, + "metadata": { + "description": "Optional. The server key type." + } + }, + "uri": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The URI of the server key. If the ServerKeyType is AzureKeyVault, then the URI is required. The AKV URI is required to be in this format: 'https://YourVaultName.azure.net/keys/YourKeyName/YourKeyVersion'." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a key." + } + }, + "virtualNetworkRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Virtual Network Rule." + } + }, + "virtualNetworkSubnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the virtual network subnet." + } + }, + "ignoreMissingVnetServiceEndpoint": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Allow creating a firewall rule before the virtual network has vnet service endpoint enabled." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a virtual network rule." + } + }, + "securityAlertPolicyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Security Alert Policy." + } + }, + "disabledAlerts": { + "type": "array", + "allowedValues": [ + "Access_Anomaly", + "Brute_Force", + "Data_Exfiltration", + "Sql_Injection", + "Sql_Injection_Vulnerability", + "Unsafe_Action" + ], + "nullable": true, + "metadata": { + "description": "Optional. Alerts to disable." + } + }, + "emailAccountAdmins": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies that the alert is sent to the account administrators." + } + }, + "emailAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies an array of email addresses to which the alert is sent." + } + }, + "retentionDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the number of days to keep in the Threat Detection audit logs." + } + }, + "state": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database." + } + }, + "storageAccountAccessKey": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the identifier key of the Threat Detection audit storage account." + } + }, + "storageEndpoint": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a security alert policy." + } + }, + "failoverGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the failover group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "databases": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of databases in the failover group." + } + }, + "partnerServerResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of the partner server Resource Id for the failover group." + } + }, + "readOnlyEndpoint": { + "$ref": "#/definitions/readOnlyEndpointType", + "nullable": true, + "metadata": { + "description": "Optional. Read-only endpoint of the failover group instance." + } + }, + "readWriteEndpoint": { + "$ref": "#/definitions/readWriteEndpointType", + "metadata": { + "description": "Required. Read-write endpoint of the failover group instance." + } + }, + "secondaryType": { + "type": "string", + "allowedValues": [ + "Geo", + "Standby" + ], + "metadata": { + "description": "Required. Databases secondary type on partner server." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a failover group." + } + }, + "_1.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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "customerManagedKeyWithAutoRotateType": { + "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 version as per 'autoRotationEnabled' setting." + } + }, + "autoRotationEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable auto-rotating to the latest key version. Default is `true`. If set to `false`, the latest key version at the time of the deployment is used." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type supports auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "databaseSkuType": { + "type": "object", + "properties": { + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the particular SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SKU, typically, a letter + Number code, e.g. P3." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Size of the particular SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier or edition of the particular SKU, e.g. Basic, Premium." + } + } + }, + "metadata": { + "description": "The database SKU.", + "__bicep_imported_from!": { + "sourceTemplate": "database/main.bicep" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "longTermBackupRetentionPolicyType": { + "type": "object", + "properties": { + "monthlyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Monthly retention in ISO 8601 duration format." + } + }, + "weeklyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Weekly retention in ISO 8601 duration format." + } + }, + "weekOfYear": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Week of year backup to keep for yearly retention." + } + }, + "yearlyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Yearly retention in ISO 8601 duration format." + } + } + }, + "metadata": { + "description": "The long-term backup retention policy for the database.", + "__bicep_imported_from!": { + "sourceTemplate": "database/main.bicep" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityOnlyUserAssignedType": { + "type": "object", + "properties": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if only user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "perDatabaseSettingsType": { + "type": "object", + "properties": { + "autoPauseDelay": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Auto Pause Delay for per database within pool." + } + }, + "maxCapacity": { + "type": "string", + "metadata": { + "description": "Required. The maximum capacity any one database can consume. Examples: '0.5', '2'." + } + }, + "minCapacity": { + "type": "string", + "metadata": { + "description": "Required. The minimum capacity all databases are guaranteed. Examples: '0.5', '1'." + } + } + }, + "metadata": { + "description": "The per database settings for the elastic pool.", + "__bicep_imported_from!": { + "sourceTemplate": "elastic-pool/main.bicep" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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/_1.lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "readOnlyEndpointType": { + "type": "object", + "properties": { + "failoverPolicy": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Required. Failover policy of the read-only endpoint for the failover group." + } + }, + "targetServer": { + "type": "string", + "metadata": { + "description": "Required. The target partner server where the read-only endpoint points to." + } + } + }, + "metadata": { + "description": "The type for a read-only endpoint.", + "__bicep_imported_from!": { + "sourceTemplate": "failover-group/main.bicep" + } + } + }, + "readWriteEndpointType": { + "type": "object", + "properties": { + "failoverPolicy": { + "type": "string", + "allowedValues": [ + "Automatic", + "Manual" + ], + "metadata": { + "description": "Required. Failover policy of the read-write endpoint for the failover group. If failoverPolicy is Automatic then failoverWithDataLossGracePeriodMinutes is required." + } + }, + "failoverWithDataLossGracePeriodMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Grace period before failover with data loss is attempted for the read-write endpoint." + } + } + }, + "metadata": { + "description": "The type for a read-write endpoint.", + "__bicep_imported_from!": { + "sourceTemplate": "failover-group/main.bicep" + } + } + }, + "recurringScansType": { + "type": "object", + "properties": { + "emails": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Specifies an array of e-mail addresses to which the scan notification is sent." + } + }, + "emailSubscriptionAdmins": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies that the schedule scan notification will be sent to the subscription administrators." + } + }, + "isEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Recurring scans state." + } + } + }, + "metadata": { + "description": "The type for recurring scans.", + "__bicep_imported_from!": { + "sourceTemplate": "vulnerability-assessment/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_1.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "shortTermBackupRetentionPolicyType": { + "type": "object", + "properties": { + "diffBackupIntervalInHours": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Differential backup interval in hours. For Hyperscale tiers this value will be ignored." + } + }, + "retentionDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Point-in-time retention in days." + } + } + }, + "metadata": { + "description": "The short-term backup retention policy for the database.", + "__bicep_imported_from!": { + "sourceTemplate": "database/main.bicep" + } + } + }, + "skuType": { + "type": "object", + "properties": { + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the particular SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "name": { + "type": "string", + "allowedValues": [ + "BC_DC", + "BC_Gen5", + "BasicPool", + "GP_DC", + "GP_FSv2", + "GP_Gen5", + "HS_Gen5", + "HS_MOPRMS", + "HS_PRMS", + "PremiumPool", + "ServerlessPool", + "StandardPool" + ], + "metadata": { + "description": "Required. The name of the SKU, typically, a letter + Number code, e.g. P3." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Size of the particular SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier or edition of the particular SKU, e.g. Basic, Premium." + } + } + }, + "metadata": { + "description": "The elastic pool SKU.", + "__bicep_imported_from!": { + "sourceTemplate": "elastic-pool/main.bicep" + } + } + } + }, + "parameters": { + "administratorLogin": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The administrator username for the server. Required if no `administrators` object for AAD authentication is provided." + } + }, + "administratorLoginPassword": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Conditional. The administrator login password. Required if no `administrators` object for AAD authentication is provided." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the server." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "primaryUserAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The resource ID of a user assigned identity to be used by default. Required if \"userAssignedIdentities\" is not empty." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Sql/servers@2023-08-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "databases": { + "type": "array", + "items": { + "$ref": "#/definitions/databaseType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The databases to create in the server." + } + }, + "elasticPools": { + "type": "array", + "items": { + "$ref": "#/definitions/elasticPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The Elastic Pools to create in the server." + } + }, + "firewallRules": { + "type": "array", + "items": { + "$ref": "#/definitions/firewallRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The firewall rules to create in the server." + } + }, + "virtualNetworkRules": { + "type": "array", + "items": { + "$ref": "#/definitions/virtualNetworkRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The virtual network rules to create in the server." + } + }, + "securityAlertPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/securityAlertPolicyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The security alert policies to create in the server." + } + }, + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/keyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The keys to configure." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyWithAutoRotateType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition for server TDE." + } + }, + "administrators": { + "$ref": "#/definitions/serverExternalAdministratorType", + "nullable": true, + "metadata": { + "description": "Conditional. The Azure Active Directory (AAD) administrator authentication. Required if no `administratorLogin` & `administratorLoginPassword` is provided." + } + }, + "federatedClientId": { + "type": "string", + "nullable": true, + "minLength": 36, + "maxLength": 36, + "metadata": { + "description": "Optional. The Client id used for cross tenant CMK scenario." + } + }, + "minimalTlsVersion": { + "type": "string", + "defaultValue": "1.2", + "allowedValues": [ + "1.0", + "1.1", + "1.2", + "1.3" + ], + "metadata": { + "description": "Optional. Minimal TLS version allowed." + } + }, + "isIPv6Enabled": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Whether or not to enable IPv6 support for this server." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "Enabled", + "Disabled", + "SecuredByPerimeter" + ], + "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 neither firewall rules nor virtual network rules are set." + } + }, + "restrictOutboundNetworkAccess": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not to restrict outbound network access for this server." + } + }, + "connectionPolicy": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Default", + "Redirect", + "Proxy" + ], + "metadata": { + "description": "Optional. SQL logical server connection policy." + } + }, + "vulnerabilityAssessmentsObj": { + "$ref": "#/definitions/vulnerabilityAssessmentType", + "nullable": true, + "metadata": { + "description": "Optional. The vulnerability assessment configuration." + } + }, + "auditSettings": { + "$ref": "#/definitions/auditSettingsType", + "defaultValue": { + "state": "Enabled" + }, + "metadata": { + "description": "Optional. The audit settings configuration. If you want to disable auditing, set the parmaeter to an empty object." + } + }, + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Key vault reference and secret settings for the module's secrets export." + } + }, + "failoverGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/failoverGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The failover groups configuration." + } + } + }, + "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)))))]" + } + ], + "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', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "enableReferencedModulesTelemetry": false, + "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')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "Monitoring Metrics Publisher": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')]", + "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "Reservation Purchaser": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689')]", + "Resource Policy Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608')]", + "SQL DB Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec')]", + "SQL Security Manager": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3')]", + "SQL Server Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6d8ee4ec-f05a-4a1d-8b00-a9b17e38b437')]", + "SqlDb Migration Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '189207d4-bb67-4208-a635-b06afe8b2c57')]" + } + }, + "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": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.sql-server.{0}.{1}', replace('0.20.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" + } + } + } + } + }, + "server": { + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "administratorLogin": "[parameters('administratorLogin')]", + "administratorLoginPassword": "[parameters('administratorLoginPassword')]", + "administrators": "[union(createObject('administratorType', 'ActiveDirectory'), coalesce(parameters('administrators'), createObject()))]", + "federatedClientId": "[parameters('federatedClientId')]", + "isIPv6Enabled": "[parameters('isIPv6Enabled')]", + "keyId": "[if(not(equals(parameters('customerManagedKey'), null())), if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), format('{0}/{1}', tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUri'), tryGet(parameters('customerManagedKey'), 'keyVersion')), tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUriWithVersion')), null())]", + "version": "12.0", + "minimalTlsVersion": "[parameters('minimalTlsVersion')]", + "primaryUserAssignedIdentityId": "[parameters('primaryUserAssignedIdentityResourceId')]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(and(not(empty(parameters('privateEndpoints'))), empty(parameters('firewallRules'))), empty(parameters('virtualNetworkRules'))), 'Disabled', null()))]", + "restrictOutboundNetworkAccess": "[parameters('restrictOutboundNetworkAccess')]" + }, + "dependsOn": [ + "cMKKeyVault::cMKKey" + ] + }, + "server_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.Sql/servers/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "server" + ] + }, + "server_roleAssignments": { + "copy": { + "name": "server_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Sql/servers/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Sql/servers', 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": [ + "server" + ] + }, + "server_connection_policy": { + "type": "Microsoft.Sql/servers/connectionPolicies", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "properties": { + "connectionType": "[parameters('connectionPolicy')]" + }, + "dependsOn": [ + "server" + ] + }, + "server_databases": { + "copy": { + "name": "server_databases", + "count": "[length(coalesce(parameters('databases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-DB-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "name": { + "value": "[coalesce(parameters('databases'), createArray())[copyIndex()].name]" + }, + "managedIdentities": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'managedIdentities')]" + }, + "sku": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'sku')]" + }, + "autoPauseDelay": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'autoPauseDelay')]" + }, + "availabilityZone": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'availabilityZone')]" + }, + "catalogCollation": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'catalogCollation')]" + }, + "collation": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'collation')]" + }, + "createMode": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'createMode')]" + }, + "elasticPoolResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'elasticPoolResourceId')]" + }, + "customerManagedKey": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'customerManagedKey')]" + }, + "federatedClientId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'federatedClientId')]" + }, + "freeLimitExhaustionBehavior": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'freeLimitExhaustionBehavior')]" + }, + "highAvailabilityReplicaCount": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'highAvailabilityReplicaCount')]" + }, + "isLedgerOn": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'isLedgerOn')]" + }, + "licenseType": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'licenseType')]" + }, + "lock": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'lock')]" + }, + "longTermRetentionBackupResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'longTermRetentionBackupResourceId')]" + }, + "maintenanceConfigurationId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'maintenanceConfigurationId')]" + }, + "manualCutover": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'manualCutover')]" + }, + "maxSizeBytes": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'maxSizeBytes')]" + }, + "minCapacity": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'minCapacity')]" + }, + "performCutover": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'performCutover')]" + }, + "preferredEnclaveType": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'preferredEnclaveType')]" + }, + "readScale": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'readScale')]" + }, + "recoverableDatabaseResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'recoverableDatabaseResourceId')]" + }, + "recoveryServicesRecoveryPointResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'recoveryServicesRecoveryPointResourceId')]" + }, + "requestedBackupStorageRedundancy": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'requestedBackupStorageRedundancy')]" + }, + "restorableDroppedDatabaseResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'restorableDroppedDatabaseResourceId')]" + }, + "restorePointInTime": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'restorePointInTime')]" + }, + "sampleName": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'sampleName')]" + }, + "secondaryType": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'secondaryType')]" + }, + "sourceDatabaseDeletionDate": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'sourceDatabaseDeletionDate')]" + }, + "sourceDatabaseResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'sourceDatabaseResourceId')]" + }, + "sourceResourceId": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'sourceResourceId')]" + }, + "useFreeLimit": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'useFreeLimit')]" + }, + "zoneRedundant": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'zoneRedundant')]" + }, + "diagnosticSettings": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'diagnosticSettings')]" + }, + "backupShortTermRetentionPolicy": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'backupShortTermRetentionPolicy')]" + }, + "backupLongTermRetentionPolicy": { + "value": "[tryGet(coalesce(parameters('databases'), createArray())[copyIndex()], 'backupLongTermRetentionPolicy')]" + } + }, + "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.37.4.10188", + "templateHash": "12287712179409148457" + }, + "name": "SQL Server Database", + "description": "This module deploys an Azure SQL Server Database." + }, + "definitions": { + "databaseSkuType": { + "type": "object", + "properties": { + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the particular SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the SKU, typically, a letter + Number code, e.g. P3." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Size of the particular SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier or edition of the particular SKU, e.g. Basic, Premium." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The database SKU." + } + }, + "shortTermBackupRetentionPolicyType": { + "type": "object", + "properties": { + "diffBackupIntervalInHours": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Differential backup interval in hours. For Hyperscale tiers this value will be ignored." + } + }, + "retentionDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Point-in-time retention in days." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The short-term backup retention policy for the database." + } + }, + "longTermBackupRetentionPolicyType": { + "type": "object", + "properties": { + "monthlyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Monthly retention in ISO 8601 duration format." + } + }, + "weeklyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Weekly retention in ISO 8601 duration format." + } + }, + "weekOfYear": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Week of year backup to keep for yearly retention." + } + }, + "yearlyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Yearly retention in ISO 8601 duration format." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The long-term backup retention policy for the database." + } + }, + "customerManagedKeyWithAutoRotateType": { + "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 version as per 'autoRotationEnabled' setting." + } + }, + "autoRotationEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable auto-rotating to the latest key version. Default is `true`. If set to `false`, the latest key version at the time of the deployment is used." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type supports auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "managedIdentityOnlyUserAssignedType": { + "type": "object", + "properties": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if only user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the database." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + }, + "sku": { + "$ref": "#/definitions/databaseSkuType", + "defaultValue": { + "name": "GP_Gen5_2", + "tier": "GeneralPurpose" + }, + "metadata": { + "description": "Optional. The database SKU." + } + }, + "autoPauseDelay": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Time in minutes after which database is automatically paused. A value of -1 means that automatic pause is disabled." + } + }, + "availabilityZone": { + "type": "int", + "allowedValues": [ + -1, + 1, + 2, + 3 + ], + "metadata": { + "description": "Required. If set to 1, 2 or 3, the availability zone is hardcoded to that value. If set to -1, no zone is defined. Note that the availability zone numbers here are the logical availability zone in your Azure subscription. Different subscriptions might have a different mapping of the physical zone and logical zone. To understand more, please refer to [Physical and logical availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview?tabs=azure-cli#physical-and-logical-availability-zones)." + } + }, + "catalogCollation": { + "type": "string", + "defaultValue": "DATABASE_DEFAULT", + "metadata": { + "description": "Optional. Collation of the metadata catalog." + } + }, + "collation": { + "type": "string", + "defaultValue": "SQL_Latin1_General_CP1_CI_AS", + "metadata": { + "description": "Optional. The collation of the database." + } + }, + "createMode": { + "type": "string", + "allowedValues": [ + "Copy", + "Default", + "OnlineSecondary", + "PointInTimeRestore", + "Recovery", + "Restore", + "RestoreExternalBackup", + "RestoreExternalBackupSecondary", + "RestoreLongTermRetentionBackup", + "Secondary" + ], + "defaultValue": "Default", + "metadata": { + "description": "Optional. Specifies the mode of database creation." + } + }, + "elasticPoolResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the elastic pool containing this database." + } + }, + "federatedClientId": { + "type": "string", + "nullable": true, + "minLength": 36, + "maxLength": 36, + "metadata": { + "description": "Optional. The Client id used for cross tenant per database CMK scenario." + } + }, + "freeLimitExhaustionBehavior": { + "type": "string", + "allowedValues": [ + "AutoPause", + "BillOverUsage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the behavior when monthly free limits are exhausted for the free database." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. The number of readonly secondary replicas associated with the database." + } + }, + "isLedgerOn": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether or not this database is a ledger database, which means all tables in the database are ledger tables. Note: the value of this property cannot be changed after the database has been created." + } + }, + "licenseType": { + "type": "string", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "nullable": true, + "metadata": { + "description": "Optional. The license type to apply for this database." + } + }, + "longTermRetentionBackupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the long term retention backup associated with create operation of this database." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maintenance configuration ID assigned to the database. This configuration defines the period when the maintenance updates will occur." + } + }, + "manualCutover": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not customer controlled manual cutover needs to be done during Update Database operation to Hyperscale tier." + } + }, + "maxSizeBytes": { + "type": "int", + "defaultValue": 34359738368, + "metadata": { + "description": "Optional. The max size of the database expressed in bytes." + } + }, + "minCapacity": { + "type": "string", + "defaultValue": "0", + "metadata": { + "description": "Optional. Minimal capacity that database will always have allocated." + } + }, + "performCutover": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. To trigger customer controlled manual cutover during the wait state while Scaling operation is in progress." + } + }, + "preferredEnclaveType": { + "type": "string", + "allowedValues": [ + "Default", + "VBS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Type of enclave requested on the database i.e. Default or VBS enclaves." + } + }, + "readScale": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "defaultValue": "Disabled", + "metadata": { + "description": "Optional. The state of read-only routing." + } + }, + "recoverableDatabaseResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the recoverable database associated with create operation of this database." + } + }, + "recoveryServicesRecoveryPointResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the recovery point associated with create operation of this database." + } + }, + "requestedBackupStorageRedundancy": { + "type": "string", + "allowedValues": [ + "Geo", + "GeoZone", + "Local", + "Zone" + ], + "defaultValue": "Local", + "metadata": { + "description": "Optional. The storage account type to be used to store backups for this database." + } + }, + "restorableDroppedDatabaseResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the restorable dropped database associated with create operation of this database." + } + }, + "restorePointInTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Point in time (ISO8601 format) of the source database to restore when createMode set to Restore or PointInTimeRestore." + } + }, + "sampleName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The name of the sample schema to apply when creating this database." + } + }, + "secondaryType": { + "type": "string", + "allowedValues": [ + "Geo", + "Named", + "Standby" + ], + "nullable": true, + "metadata": { + "description": "Optional. The secondary type of the database if it is a secondary." + } + }, + "sourceDatabaseDeletionDate": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time that the database was deleted when restoring a deleted database." + } + }, + "sourceDatabaseResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the source database associated with create operation of this database." + } + }, + "sourceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource identifier of the source associated with the create operation of this database." + } + }, + "useFreeLimit": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether or not the database uses free monthly limits. Allowed on one database in a subscription." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether or not this database is zone redundant." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Sql/servers/database@2023-08-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the databse." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "backupShortTermRetentionPolicy": { + "$ref": "#/definitions/shortTermBackupRetentionPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. The short term backup retention policy to create for the database." + } + }, + "backupLongTermRetentionPolicy": { + "$ref": "#/definitions/longTermBackupRetentionPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. The long term backup retention policy to create for the database." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityOnlyUserAssignedType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyWithAutoRotateType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition for database TDE." + } + } + }, + "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(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "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": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" + }, + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" + }, + "database": { + "type": "Microsoft.Sql/servers/databases", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "identity": "[variables('identity')]", + "properties": { + "autoPauseDelay": "[parameters('autoPauseDelay')]", + "availabilityZone": "[if(not(equals(parameters('availabilityZone'), -1)), string(parameters('availabilityZone')), 'NoPreference')]", + "catalogCollation": "[parameters('catalogCollation')]", + "collation": "[parameters('collation')]", + "createMode": "[parameters('createMode')]", + "elasticPoolId": "[parameters('elasticPoolResourceId')]", + "encryptionProtector": "[if(not(equals(parameters('customerManagedKey'), null())), if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), format('{0}/{1}', tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUri'), tryGet(parameters('customerManagedKey'), 'keyVersion')), tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUriWithVersion')), null())]", + "encryptionProtectorAutoRotation": "[tryGet(parameters('customerManagedKey'), 'autoRotationEnabled')]", + "federatedClientId": "[parameters('federatedClientId')]", + "freeLimitExhaustionBehavior": "[parameters('freeLimitExhaustionBehavior')]", + "highAvailabilityReplicaCount": "[parameters('highAvailabilityReplicaCount')]", + "isLedgerOn": "[parameters('isLedgerOn')]", + "licenseType": "[parameters('licenseType')]", + "longTermRetentionBackupResourceId": "[parameters('longTermRetentionBackupResourceId')]", + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationId')]", + "manualCutover": "[parameters('manualCutover')]", + "maxSizeBytes": "[parameters('maxSizeBytes')]", + "minCapacity": "[if(not(empty(parameters('minCapacity'))), json(parameters('minCapacity')), 0)]", + "performCutover": "[parameters('performCutover')]", + "preferredEnclaveType": "[parameters('preferredEnclaveType')]", + "readScale": "[parameters('readScale')]", + "recoverableDatabaseId": "[parameters('recoverableDatabaseResourceId')]", + "recoveryServicesRecoveryPointId": "[parameters('recoveryServicesRecoveryPointResourceId')]", + "requestedBackupStorageRedundancy": "[parameters('requestedBackupStorageRedundancy')]", + "restorableDroppedDatabaseId": "[parameters('restorableDroppedDatabaseResourceId')]", + "restorePointInTime": "[parameters('restorePointInTime')]", + "sampleName": "[parameters('sampleName')]", + "secondaryType": "[parameters('secondaryType')]", + "sourceDatabaseDeletionDate": "[parameters('sourceDatabaseDeletionDate')]", + "sourceDatabaseId": "[parameters('sourceDatabaseResourceId')]", + "sourceResourceId": "[parameters('sourceResourceId')]", + "useFreeLimit": "[parameters('useFreeLimit')]", + "zoneRedundant": "[parameters('zoneRedundant')]" + }, + "dependsOn": [ + "cMKKeyVault::cMKKey" + ] + }, + "database_diagnosticSettings": { + "copy": { + "name": "database_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Sql/servers/{0}/databases/{1}', parameters('serverName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', replace(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": [ + "database" + ] + }, + "database_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.Sql/servers/{0}/databases/{1}', parameters('serverName'), parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "database" + ] + }, + "database_backupShortTermRetentionPolicy": { + "condition": "[not(empty(parameters('backupShortTermRetentionPolicy')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-shBakRetPol', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('serverName')]" + }, + "databaseName": { + "value": "[parameters('name')]" + }, + "diffBackupIntervalInHours": { + "value": "[tryGet(parameters('backupShortTermRetentionPolicy'), 'diffBackupIntervalInHours')]" + }, + "retentionDays": { + "value": "[tryGet(parameters('backupShortTermRetentionPolicy'), 'retentionDays')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "16409308766047346265" + }, + "name": "Azure SQL Server Database Short Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Short-Term Backup Retention Policy." + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "diffBackupIntervalInHours": { + "type": "int", + "defaultValue": 24, + "metadata": { + "description": "Optional. Differential backup interval in hours. For Hyperscal tiers this value will be ignored." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 7, + "metadata": { + "description": "Optional. Poin-in-time retention in days." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "diffBackupIntervalInHours": "[if(equals(reference(resourceId('Microsoft.Sql/servers/databases', parameters('serverName'), parameters('databaseName')), '2023-08-01', 'full').sku.tier, 'Hyperscale'), null(), parameters('diffBackupIntervalInHours'))]", + "retentionDays": "[parameters('retentionDays')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the short-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the short-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the short-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupShortTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } + } + }, + "dependsOn": [ + "database" + ] + }, + "database_backupLongTermRetentionPolicy": { + "condition": "[not(empty(parameters('backupLongTermRetentionPolicy')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-lgBakRetPol', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('serverName')]" + }, + "databaseName": { + "value": "[parameters('name')]" + }, + "weeklyRetention": { + "value": "[tryGet(parameters('backupLongTermRetentionPolicy'), 'weeklyRetention')]" + }, + "monthlyRetention": { + "value": "[tryGet(parameters('backupLongTermRetentionPolicy'), 'monthlyRetention')]" + }, + "yearlyRetention": { + "value": "[tryGet(parameters('backupLongTermRetentionPolicy'), 'yearlyRetention')]" + }, + "weekOfYear": { + "value": "[tryGet(parameters('backupLongTermRetentionPolicy'), 'weekOfYear')]" + } + }, + "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.37.4.10188", + "templateHash": "14473382343475213230" + }, + "name": "SQL Server Database Long Term Backup Retention Policies", + "description": "This module deploys an Azure SQL Server Database Long-Term Backup Retention Policy." + }, + "parameters": { + "serverName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent SQL Server." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. The name of the parent database." + } + }, + "monthlyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Monthly retention in ISO 8601 duration format." + } + }, + "weeklyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Weekly retention in ISO 8601 duration format." + } + }, + "weekOfYear": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Week of year backup to keep for yearly retention." + } + }, + "yearlyRetention": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Yearly retention in ISO 8601 duration format." + } + } + }, + "resources": { + "server::database": { + "existing": true, + "type": "Microsoft.Sql/servers/databases", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('databaseName'))]" + }, + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "backupLongTermRetentionPolicy": { + "type": "Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}/{2}', parameters('serverName'), parameters('databaseName'), 'default')]", + "properties": { + "monthlyRetention": "[parameters('monthlyRetention')]", + "weeklyRetention": "[parameters('weeklyRetention')]", + "weekOfYear": "[parameters('weekOfYear')]", + "yearlyRetention": "[parameters('yearlyRetention')]" + } + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the long-term policy was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the long-term policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the long-term policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases/backupLongTermRetentionPolicies', parameters('serverName'), parameters('databaseName'), 'default')]" + } + } + } + }, + "dependsOn": [ + "database" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed database." + }, + "value": "[resourceId('Microsoft.Sql/servers/databases', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed database." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('database', '2023-08-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "server", + "server_elasticPools" + ] + }, + "server_elasticPools": { + "copy": { + "name": "server_elasticPools", + "count": "[length(coalesce(parameters('elasticPools'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-SQLServer-ElasticPool-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "name": { + "value": "[coalesce(parameters('elasticPools'), createArray())[copyIndex()].name]" + }, + "sku": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'sku')]" + }, + "autoPauseDelay": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'autoPauseDelay')]" + }, + "availabilityZone": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'availabilityZone')]" + }, + "highAvailabilityReplicaCount": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'highAvailabilityReplicaCount')]" + }, + "licenseType": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'licenseType')]" + }, + "lock": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'lock')]" + }, + "maintenanceConfigurationId": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'maintenanceConfigurationId')]" + }, + "maxSizeBytes": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'maxSizeBytes')]" + }, + "minCapacity": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'minCapacity')]" + }, + "perDatabaseSettings": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'perDatabaseSettings')]" + }, + "preferredEnclaveType": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'preferredEnclaveType')]" + }, + "zoneRedundant": { + "value": "[tryGet(coalesce(parameters('elasticPools'), createArray())[copyIndex()], 'zoneRedundant')]" + } + }, + "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.37.4.10188", + "templateHash": "11758119748604471264" + }, + "name": "SQL Server Elastic Pool", + "description": "This module deploys an Azure SQL Server Elastic Pool." + }, + "definitions": { + "perDatabaseSettingsType": { + "type": "object", + "properties": { + "autoPauseDelay": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Auto Pause Delay for per database within pool." + } + }, + "maxCapacity": { + "type": "string", + "metadata": { + "description": "Required. The maximum capacity any one database can consume. Examples: '0.5', '2'." + } + }, + "minCapacity": { + "type": "string", + "metadata": { + "description": "Required. The minimum capacity all databases are guaranteed. Examples: '0.5', '1'." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The per database settings for the elastic pool." + } + }, + "skuType": { + "type": "object", + "properties": { + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the particular SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. If the service has different generations of hardware, for the same SKU, then that can be captured here." + } + }, + "name": { + "type": "string", + "allowedValues": [ + "BC_DC", + "BC_Gen5", + "BasicPool", + "GP_DC", + "GP_FSv2", + "GP_Gen5", + "HS_Gen5", + "HS_MOPRMS", + "HS_PRMS", + "PremiumPool", + "ServerlessPool", + "StandardPool" + ], + "metadata": { + "description": "Required. The name of the SKU, typically, a letter + Number code, e.g. P3." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Size of the particular SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier or edition of the particular SKU, e.g. Basic, Premium." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The elastic pool SKU." + } + }, + "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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Elastic Pool." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Sql/servers/elasticPools@2023-08-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the elastic pool." + } + }, + "sku": { + "$ref": "#/definitions/skuType", + "defaultValue": { + "capacity": 2, + "name": "GP_Gen5", + "tier": "GeneralPurpose" + }, + "metadata": { + "description": "Optional. The elastic pool SKU." + } + }, + "autoPauseDelay": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Time in minutes after which elastic pool is automatically paused. A value of -1 means that automatic pause is disabled." + } + }, + "availabilityZone": { + "type": "int", + "allowedValues": [ + -1, + 1, + 2, + 3 + ], + "metadata": { + "description": "Required. If set to 1, 2 or 3, the availability zone is hardcoded to that value. If set to -1, no zone is defined. Note that the availability zone numbers here are the logical availability zone in your Azure subscription. Different subscriptions might have a different mapping of the physical zone and logical zone. To understand more, please refer to [Physical and logical availability zones](https://learn.microsoft.com/en-us/azure/reliability/availability-zones-overview?tabs=azure-cli#physical-and-logical-availability-zones)." + } + }, + "highAvailabilityReplicaCount": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of secondary replicas associated with the elastic pool that are used to provide high availability. Applicable only to Hyperscale elastic pools." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "LicenseIncluded", + "allowedValues": [ + "BasePrice", + "LicenseIncluded" + ], + "metadata": { + "description": "Optional. The license type to apply for this elastic pool." + } + }, + "maintenanceConfigurationId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Maintenance configuration resource ID assigned to the elastic pool. This configuration defines the period when the maintenance updates will will occur." + } + }, + "maxSizeBytes": { + "type": "int", + "defaultValue": 34359738368, + "metadata": { + "description": "Optional. The storage limit for the database elastic pool in bytes." + } + }, + "minCapacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Minimal capacity that serverless pool will not shrink below, if not paused." + } + }, + "perDatabaseSettings": { + "$ref": "#/definitions/perDatabaseSettingsType", + "defaultValue": { + "autoPauseDelay": -1, + "maxCapacity": "2", + "minCapacity": "0" + }, + "metadata": { + "description": "Optional. The per database settings for the elastic pool." + } + }, + "preferredEnclaveType": { + "type": "string", + "allowedValues": [ + "Default", + "VBS" + ], + "defaultValue": "Default", + "metadata": { + "description": "Optional. Type of enclave requested on the elastic pool." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether or not this elastic pool is zone redundant, which means the replicas of this elastic pool will be spread across multiple availability zones." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "Monitoring Metrics Publisher": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3913510d-42f4-4e42-8a64-420c390055eb')]", + "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "Reservation Purchaser": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f7b75c60-3036-4b75-91c3-6b41c27c1689')]", + "Resource Policy Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '36243c78-bf99-498c-9df9-86d9f8d28608')]", + "SQL DB Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9b7fa17d-e63e-47b0-bb0a-15c516ac86ec')]", + "SQL Security Manager": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '056cd41c-7e88-42e1-933e-88ba6a50c9c3')]", + "SQL Server Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6d8ee4ec-f05a-4a1d-8b00-a9b17e38b437')]", + "SqlDb Migration Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '189207d4-bb67-4208-a635-b06afe8b2c57')]" + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "elasticPool": { + "type": "Microsoft.Sql/servers/elasticPools", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[parameters('sku')]", + "properties": { + "autoPauseDelay": "[parameters('autoPauseDelay')]", + "availabilityZone": "[if(not(equals(parameters('availabilityZone'), -1)), string(parameters('availabilityZone')), 'NoPreference')]", + "highAvailabilityReplicaCount": "[parameters('highAvailabilityReplicaCount')]", + "licenseType": "[parameters('licenseType')]", + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationId')]", + "maxSizeBytes": "[parameters('maxSizeBytes')]", + "minCapacity": "[parameters('minCapacity')]", + "perDatabaseSettings": "[if(not(empty(parameters('perDatabaseSettings'))), createObject('autoPauseDelay', tryGet(parameters('perDatabaseSettings'), 'autoPauseDelay'), 'maxCapacity', json(tryGet(parameters('perDatabaseSettings'), 'maxCapacity')), 'minCapacity', json(tryGet(parameters('perDatabaseSettings'), 'minCapacity'))), null())]", + "preferredEnclaveType": "[parameters('preferredEnclaveType')]", + "zoneRedundant": "[parameters('zoneRedundant')]" + } + }, + "elasticPool_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.Sql/servers/{0}/elasticPools/{1}', parameters('serverName'), parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "elasticPool" + ] + }, + "elasticPool_roleAssignments": { + "copy": { + "name": "elasticPool_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Sql/servers/{0}/elasticPools/{1}', parameters('serverName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Sql/servers', parameters('serverName')), 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": [ + "elasticPool" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed Elastic Pool." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed Elastic Pool." + }, + "value": "[resourceId('Microsoft.Sql/servers/elasticPools', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed Elastic Pool." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('elasticPool', '2023-08-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_privateEndpoints": { + "copy": { + "name": "server_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-server-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.Sql/servers', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Sql/servers', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Sql/servers', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Sql/servers', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Sql/servers', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sqlServer')), '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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.34.44.8038", + "templateHash": "12389807800450456797" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } + }, + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.11.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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.34.44.8038", + "templateHash": "13997305779829540948" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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', '2024-05-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_firewallRules": { + "copy": { + "name": "server_firewallRules", + "count": "[length(coalesce(parameters('firewallRules'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-FirewallRules-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('firewallRules'), createArray())[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "endIpAddress": { + "value": "[tryGet(coalesce(parameters('firewallRules'), createArray())[copyIndex()], 'endIpAddress')]" + }, + "startIpAddress": { + "value": "[tryGet(coalesce(parameters('firewallRules'), createArray())[copyIndex()], 'startIpAddress')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "659890310134855258" + }, + "name": "Azure SQL Server Firewall Rule", + "description": "This module deploys an Azure SQL Server Firewall Rule." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Firewall Rule." + } + }, + "endIpAddress": { + "type": "string", + "defaultValue": "0.0.0.0", + "metadata": { + "description": "Optional. The end IP address of the firewall rule. Must be IPv4 format. Must be greater than or equal to startIpAddress. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "startIpAddress": { + "type": "string", + "defaultValue": "0.0.0.0", + "metadata": { + "description": "Optional. The start IP address of the firewall rule. Must be IPv4 format. Use value '0.0.0.0' for all Azure-internal IP addresses." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/firewallRules", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "endIpAddress": "[parameters('endIpAddress')]", + "startIpAddress": "[parameters('startIpAddress')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed firewall rule." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed firewall rule." + }, + "value": "[resourceId('Microsoft.Sql/servers/firewallRules', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed firewall rule." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_virtualNetworkRules": { + "copy": { + "name": "server_virtualNetworkRules", + "count": "[length(coalesce(parameters('virtualNetworkRules'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-VirtualNetworkRules-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('virtualNetworkRules'), createArray())[copyIndex()].name]" + }, + "ignoreMissingVnetServiceEndpoint": { + "value": "[tryGet(coalesce(parameters('virtualNetworkRules'), createArray())[copyIndex()], 'ignoreMissingVnetServiceEndpoint')]" + }, + "virtualNetworkSubnetResourceId": { + "value": "[coalesce(parameters('virtualNetworkRules'), createArray())[copyIndex()].virtualNetworkSubnetResourceId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "3609362223373791436" + }, + "name": "Azure SQL Server Virtual Network Rules", + "description": "This module deploys an Azure SQL Server Virtual Network Rule." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Server Virtual Network Rule." + } + }, + "ignoreMissingVnetServiceEndpoint": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Allow creating a firewall rule before the virtual network has vnet service endpoint enabled." + } + }, + "virtualNetworkSubnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the virtual network subnet." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/virtualNetworkRules", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "ignoreMissingVnetServiceEndpoint": "[parameters('ignoreMissingVnetServiceEndpoint')]", + "virtualNetworkSubnetId": "[parameters('virtualNetworkSubnetResourceId')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed virtual network rule." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed virtual network rule." + }, + "value": "[resourceId('Microsoft.Sql/servers/virtualNetworkRules', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed virtual network rule." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_securityAlertPolicies": { + "copy": { + "name": "server_securityAlertPolicies", + "count": "[length(coalesce(parameters('securityAlertPolicies'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-SecAlertPolicy-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()].name]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "disabledAlerts": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'disabledAlerts')]" + }, + "emailAccountAdmins": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'emailAccountAdmins')]" + }, + "emailAddresses": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'emailAddresses')]" + }, + "retentionDays": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'retentionDays')]" + }, + "state": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'state')]" + }, + "storageAccountAccessKey": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'storageAccountAccessKey')]" + }, + "storageEndpoint": { + "value": "[tryGet(coalesce(parameters('securityAlertPolicies'), createArray())[copyIndex()], 'storageEndpoint')]" + } + }, + "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.37.4.10188", + "templateHash": "6365245285305355040" + }, + "name": "Azure SQL Server Security Alert Policies", + "description": "This module deploys an Azure SQL Server Security Alert Policy." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Security Alert Policy." + } + }, + "disabledAlerts": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "allowedValues": [ + "Sql_Injection", + "Sql_Injection_Vulnerability", + "Access_Anomaly", + "Data_Exfiltration", + "Unsafe_Action", + "Brute_Force" + ], + "metadata": { + "description": "Optional. Alerts to disable." + } + }, + "emailAccountAdmins": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies that the alert is sent to the account administrators." + } + }, + "emailAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Specifies an array of email addresses to which the alert is sent." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Specifies the number of days to keep in the Threat Detection audit logs." + } + }, + "state": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Specifies the state of the policy, whether it is enabled or disabled or a policy has not been applied yet on the specific database." + } + }, + "storageAccountAccessKey": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the identifier key of the Threat Detection audit storage account." + } + }, + "storageEndpoint": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the blob storage endpoint. This blob storage will hold all Threat Detection audit logs." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Server. Required if the template is used in a standalone deployment." + } + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "securityAlertPolicy": { + "type": "Microsoft.Sql/servers/securityAlertPolicies", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "disabledAlerts": "[parameters('disabledAlerts')]", + "emailAccountAdmins": "[parameters('emailAccountAdmins')]", + "emailAddresses": "[parameters('emailAddresses')]", + "retentionDays": "[parameters('retentionDays')]", + "state": "[parameters('state')]", + "storageAccountAccessKey": "[parameters('storageAccountAccessKey')]", + "storageEndpoint": "[parameters('storageEndpoint')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed security alert policy." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed security alert policy." + }, + "value": "[resourceId('Microsoft.Sql/servers/securityAlertPolicies', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed security alert policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "server_vulnerabilityAssessment": { + "condition": "[not(equals(parameters('vulnerabilityAssessmentsObj'), null()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-VulnAssessm', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[parameters('vulnerabilityAssessmentsObj').name]" + }, + "recurringScans": { + "value": "[tryGet(parameters('vulnerabilityAssessmentsObj'), 'recurringScans')]" + }, + "storageAccountResourceId": { + "value": "[parameters('vulnerabilityAssessmentsObj').storageAccountResourceId]" + }, + "useStorageAccountAccessKey": { + "value": "[tryGet(parameters('vulnerabilityAssessmentsObj'), 'useStorageAccountAccessKey')]" + }, + "createStorageRoleAssignment": { + "value": "[tryGet(parameters('vulnerabilityAssessmentsObj'), 'createStorageRoleAssignment')]" + } + }, + "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.37.4.10188", + "templateHash": "16444889186124072526" + }, + "name": "Azure SQL Server Vulnerability Assessments", + "description": "This module deploys an Azure SQL Server Vulnerability Assessment." + }, + "definitions": { + "recurringScansType": { + "type": "object", + "properties": { + "emails": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Specifies an array of e-mail addresses to which the scan notification is sent." + } + }, + "emailSubscriptionAdmins": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies that the schedule scan notification will be sent to the subscription administrators." + } + }, + "isEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Recurring scans state." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for recurring scans." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the vulnerability assessment." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The Name of SQL Server. Required if the template is used in a standalone deployment." + } + }, + "recurringScans": { + "$ref": "#/definitions/recurringScansType", + "defaultValue": { + "emails": [], + "emailSubscriptionAdmins": false, + "isEnabled": false + }, + "metadata": { + "description": "Optional. The recurring scans settings." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. A blob storage to hold the scan results." + } + }, + "useStorageAccountAccessKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use Access Key to access the storage account. The storage account cannot be behind a firewall or virtual network. If an access key is not used, the SQL Server system assigned managed identity must be assigned the Storage Blob Data Contributor role on the storage account." + } + }, + "createStorageRoleAssignment": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Create the Storage Blob Data Contributor role assignment on the storage account. Note, the role assignment must not already exist on the storage account." + } + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "vulnerabilityAssessment": { + "type": "Microsoft.Sql/servers/vulnerabilityAssessments", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "storageContainerPath": "[format('https://{0}.blob.{1}/vulnerability-assessment/', last(split(parameters('storageAccountResourceId'), '/')), environment().suffixes.storage)]", + "storageAccountAccessKey": "[if(parameters('useStorageAccountAccessKey'), listKeys(parameters('storageAccountResourceId'), '2019-06-01').keys[0].value, null())]", + "recurringScans": "[parameters('recurringScans')]" + } + }, + "storageAccount_sbdc_rbac": { + "condition": "[and(not(parameters('useStorageAccountAccessKey')), parameters('createStorageRoleAssignment'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-sbdc-rbac', parameters('serverName'))]", + "subscriptionId": "[split(parameters('storageAccountResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "managedInstanceIdentityPrincipalId": { + "value": "[reference('server', '2023-08-01', 'full').identity.principalId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "5481403767961617995" + } + }, + "parameters": { + "storageAccountName": { + "type": "string" + }, + "managedInstanceIdentityPrincipalId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(format('{0}-{1}-Storage-Blob-Data-Contributor', resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('managedInstanceIdentityPrincipalId')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[parameters('managedInstanceIdentityPrincipalId')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "server" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed vulnerability assessment." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed vulnerability assessment." + }, + "value": "[resourceId('Microsoft.Sql/servers/vulnerabilityAssessments', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed vulnerability assessment." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server", + "server_securityAlertPolicies" + ] + }, + "server_keys": { + "copy": { + "name": "server_keys", + "count": "[length(coalesce(parameters('keys'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-Key-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'name')]" + }, + "serverKeyType": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'serverKeyType')]" + }, + "uri": { + "value": "[tryGet(coalesce(parameters('keys'), createArray())[copyIndex()], 'uri')]" + } + }, + "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.37.4.10188", + "templateHash": "5825929095205418837" + }, + "name": "Azure SQL Server Keys", + "description": "This module deploys an Azure SQL Server Key." + }, + "parameters": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the key. Must follow the [__] pattern." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL server. Required if the template is used in a standalone deployment." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The server key type." + } + }, + "uri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The URI of the server key. If the ServerKeyType is AzureKeyVault, then the URI is required. The AKV URI is required to be in this format: 'https://YourVaultName.azure.net/keys/YourKeyName/YourKeyVersion'." + } + } + }, + "variables": { + "splittedKeyUri": "[split(parameters('uri'), '/')]", + "serverKeyName": "[if(empty(parameters('uri')), 'ServiceManaged', format('{0}_{1}_{2}', split(variables('splittedKeyUri')[2], '.')[0], variables('splittedKeyUri')[4], variables('splittedKeyUri')[5]))]" + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "key": { + "type": "Microsoft.Sql/servers/keys", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "uri": "[parameters('uri')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed server key." + }, + "value": "[coalesce(parameters('name'), variables('serverKeyName'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed server key." + }, + "value": "[resourceId('Microsoft.Sql/servers/keys', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed server key." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "cmk_key": { + "condition": "[not(equals(parameters('customerManagedKey'), null()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-Key', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[format('{0}_{1}_{2}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'), if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), tryGet(parameters('customerManagedKey'), 'keyVersion'), last(split(coalesce(tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUriWithVersion'), ''), '/'))))]" + }, + "serverKeyType": { + "value": "AzureKeyVault" + }, + "uri": "[if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), createObject('value', format('{0}/{1}', tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUri'), tryGet(parameters('customerManagedKey'), 'keyVersion'))), createObject('value', tryGet(if(and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName'))))), reference('cMKKeyVault::cMKKey', '2024-11-01', 'full'), null()), 'properties', 'keyUriWithVersion')))]" + }, + "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.37.4.10188", + "templateHash": "5825929095205418837" + }, + "name": "Azure SQL Server Keys", + "description": "This module deploys an Azure SQL Server Key." + }, + "parameters": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the key. Must follow the [__] pattern." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL server. Required if the template is used in a standalone deployment." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The server key type." + } + }, + "uri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The URI of the server key. If the ServerKeyType is AzureKeyVault, then the URI is required. The AKV URI is required to be in this format: 'https://YourVaultName.azure.net/keys/YourKeyName/YourKeyVersion'." + } + } + }, + "variables": { + "splittedKeyUri": "[split(parameters('uri'), '/')]", + "serverKeyName": "[if(empty(parameters('uri')), 'ServiceManaged', format('{0}_{1}_{2}', split(variables('splittedKeyUri')[2], '.')[0], variables('splittedKeyUri')[4], variables('splittedKeyUri')[5]))]" + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "key": { + "type": "Microsoft.Sql/servers/keys", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "uri": "[parameters('uri')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed server key." + }, + "value": "[coalesce(parameters('name'), variables('serverKeyName'))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed server key." + }, + "value": "[resourceId('Microsoft.Sql/servers/keys', parameters('serverName'), coalesce(parameters('name'), variables('serverKeyName')))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed server key." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "cMKKeyVault::cMKKey", + "server" + ] + }, + "server_encryptionProtector": { + "condition": "[not(equals(parameters('customerManagedKey'), null()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-EncryProtector', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "sqlServerName": { + "value": "[parameters('name')]" + }, + "serverKeyName": { + "value": "[coalesce(tryGet(if(not(equals(parameters('customerManagedKey'), null())), reference('cmk_key'), null()), 'outputs', 'name', 'value'), '')]" + }, + "serverKeyType": { + "value": "AzureKeyVault" + }, + "autoRotationEnabled": { + "value": "[tryGet(parameters('customerManagedKey'), 'autoRotationEnabled')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "15444903167779042084" + }, + "name": "Azure SQL Server Encryption Protector", + "description": "This module deploys an Azure SQL Server Encryption Protector." + }, + "parameters": { + "sqlServerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the sql server. Required if the template is used in a standalone deployment." + } + }, + "serverKeyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the server key." + } + }, + "autoRotationEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Key auto rotation opt-in flag." + } + }, + "serverKeyType": { + "type": "string", + "defaultValue": "ServiceManaged", + "allowedValues": [ + "AzureKeyVault", + "ServiceManaged" + ], + "metadata": { + "description": "Optional. The encryption protector type." + } + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers/encryptionProtector", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('sqlServerName'), 'current')]", + "properties": { + "serverKeyType": "[parameters('serverKeyType')]", + "autoRotationEnabled": "[parameters('autoRotationEnabled')]", + "serverKeyName": "[parameters('serverKeyName')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed encryption protector." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the encryption protector." + }, + "value": "[resourceId('Microsoft.Sql/servers/encryptionProtector', parameters('sqlServerName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed encryption protector." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "cmk_key", + "server" + ] + }, + "server_audit_settings": { + "condition": "[not(empty(parameters('auditSettings')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-AuditSettings', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "serverName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('auditSettings'), 'name'), 'default')]" + }, + "state": { + "value": "[tryGet(parameters('auditSettings'), 'state')]" + }, + "auditActionsAndGroups": { + "value": "[tryGet(parameters('auditSettings'), 'auditActionsAndGroups')]" + }, + "isAzureMonitorTargetEnabled": { + "value": "[tryGet(parameters('auditSettings'), 'isAzureMonitorTargetEnabled')]" + }, + "isDevopsAuditEnabled": { + "value": "[tryGet(parameters('auditSettings'), 'isDevopsAuditEnabled')]" + }, + "isManagedIdentityInUse": { + "value": "[tryGet(parameters('auditSettings'), 'isManagedIdentityInUse')]" + }, + "isStorageSecondaryKeyInUse": { + "value": "[tryGet(parameters('auditSettings'), 'isStorageSecondaryKeyInUse')]" + }, + "queueDelayMs": { + "value": "[tryGet(parameters('auditSettings'), 'queueDelayMs')]" + }, + "retentionDays": { + "value": "[tryGet(parameters('auditSettings'), 'retentionDays')]" + }, + "storageAccountResourceId": { + "value": "[tryGet(parameters('auditSettings'), 'storageAccountResourceId')]" + } + }, + "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.37.4.10188", + "templateHash": "8739659678401704297" + }, + "name": "Azure SQL Server Audit Settings", + "description": "This module deploys an Azure SQL Server Audit Settings." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the audit settings." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The Name of SQL Server. Required if the template is used in a standalone deployment." + } + }, + "state": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Specifies the state of the audit. If state is Enabled, storageEndpoint or isAzureMonitorTargetEnabled are required." + } + }, + "auditActionsAndGroups": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [ + "BATCH_COMPLETED_GROUP", + "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP", + "FAILED_DATABASE_AUTHENTICATION_GROUP" + ], + "metadata": { + "description": "Optional. Specifies the Actions-Groups and Actions to audit." + } + }, + "isAzureMonitorTargetEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies whether audit events are sent to Azure Monitor." + } + }, + "isDevopsAuditEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies the state of devops audit. If state is Enabled, devops logs will be sent to Azure Monitor." + } + }, + "isManagedIdentityInUse": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether Managed Identity is used to access blob storage." + } + }, + "isStorageSecondaryKeyInUse": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether storageAccountAccessKey value is the storage's secondary key." + } + }, + "queueDelayMs": { + "type": "int", + "defaultValue": 1000, + "metadata": { + "description": "Optional. Specifies the amount of time in milliseconds that can elapse before audit actions are forced to be processed." + } + }, + "retentionDays": { + "type": "int", + "defaultValue": 90, + "metadata": { + "description": "Optional. Specifies the number of days to keep in the audit logs in the storage account." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. A blob storage to hold the auditing storage account." + } + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "auditSettings": { + "type": "Microsoft.Sql/servers/auditingSettings", + "apiVersion": "2023-08-01", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "properties": { + "state": "[parameters('state')]", + "auditActionsAndGroups": "[parameters('auditActionsAndGroups')]", + "isAzureMonitorTargetEnabled": "[parameters('isAzureMonitorTargetEnabled')]", + "isDevopsAuditEnabled": "[parameters('isDevopsAuditEnabled')]", + "isManagedIdentityInUse": "[parameters('isManagedIdentityInUse')]", + "isStorageSecondaryKeyInUse": "[parameters('isStorageSecondaryKeyInUse')]", + "queueDelayMs": "[parameters('queueDelayMs')]", + "retentionDays": "[parameters('retentionDays')]", + "storageAccountAccessKey": "[if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('isManagedIdentityInUse'))), listKeys(parameters('storageAccountResourceId'), '2019-06-01').keys[0].value, null())]", + "storageAccountSubscriptionId": "[if(not(empty(parameters('storageAccountResourceId'))), split(parameters('storageAccountResourceId'), '/')[2], null())]", + "storageEndpoint": "[if(not(empty(parameters('storageAccountResourceId'))), format('https://{0}.blob.{1}', last(split(parameters('storageAccountResourceId'), '/')), environment().suffixes.storage), null())]" + } + }, + "storageAccount_sbdc_rbac": { + "condition": "[and(parameters('isManagedIdentityInUse'), not(empty(parameters('storageAccountResourceId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('storageAccount_sbdc_rbac-{0}', uniqueString('storageAccount_sbdc_rbac', deployment().name))]", + "subscriptionId": "[split(coalesce(parameters('storageAccountResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(parameters('storageAccountResourceId'), resourceGroup().id), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "managedIdentityPrincipalId": "[if(equals(reference('server', '2023-08-01', 'full').identity.type, 'UserAssigned'), createObject('value', filter(items(reference('server', '2023-08-01', 'full').identity.userAssignedIdentities), lambda('identity', equals(lambdaVariables('identity').key, reference('server').primaryUserAssignedIdentityId)))[0].value.principalId), createObject('value', reference('server', '2023-08-01', 'full').identity.principalId))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "4676262900408370911" + } + }, + "parameters": { + "storageAccountName": { + "type": "string" + }, + "managedIdentityPrincipalId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(format('{0}-{1}-Storage-Blob-Data-Contributor', resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), parameters('managedIdentityPrincipalId')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "principalId": "[parameters('managedIdentityPrincipalId')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "server" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed audit settings." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed audit settings." + }, + "value": "[resourceId('Microsoft.Sql/servers/auditingSettings', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed audit settings." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "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(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'sqlAdminPasswordSecretName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'sqlAdminPasswordSecretName'), 'value', parameters('administratorLoginPassword'))), createArray()), if(contains(parameters('secretsExportConfiguration'), 'sqlAzureConnectionStringSecretName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'sqlAzureConnectionStringSecretName'), 'value', format('Server={0}; Database={1}; User={2}; Password={3}', reference('server').fullyQualifiedDomainName, if(not(empty(parameters('databases'))), tryGet(parameters('databases'), 0, 'name'), ''), parameters('administratorLogin'), parameters('administratorLoginPassword')))), 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.37.4.10188", + "templateHash": "10221696837745467129" + } + }, + "definitions": { + "secretSetOutputType": { + "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." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the secret to set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Key Vault to set the secrets 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": "2024-11-01", + "name": "[parameters('keyVaultName')]" + }, + "secrets": { + "copy": { + "name": "secrets", + "count": "[length(parameters('secretsToSet'))]" + }, + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2024-11-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]", + "properties": { + "value": "[parameters('secretsToSet')[copyIndex()].value]" + } + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetOutputType" + }, + "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]", + "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]" + } + } + } + } + } + }, + "dependsOn": [ + "server" + ] + }, + "failover_groups": { + "copy": { + "name": "failover_groups", + "count": "[length(coalesce(parameters('failoverGroups'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Sql-FailoverGroup-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('failoverGroups'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('failoverGroups'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "serverName": { + "value": "[parameters('name')]" + }, + "databases": { + "value": "[coalesce(parameters('failoverGroups'), createArray())[copyIndex()].databases]" + }, + "partnerServerResourceIds": { + "value": "[coalesce(parameters('failoverGroups'), createArray())[copyIndex()].partnerServerResourceIds]" + }, + "readOnlyEndpoint": { + "value": "[tryGet(coalesce(parameters('failoverGroups'), createArray())[copyIndex()], 'readOnlyEndpoint')]" + }, + "readWriteEndpoint": { + "value": "[coalesce(parameters('failoverGroups'), createArray())[copyIndex()].readWriteEndpoint]" + }, + "secondaryType": { + "value": "[coalesce(parameters('failoverGroups'), createArray())[copyIndex()].secondaryType]" + } + }, + "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.37.4.10188", + "templateHash": "12623039249930333884" + }, + "name": "Azure SQL Server failover group", + "description": "This module deploys Azure SQL Server failover group." + }, + "definitions": { + "readOnlyEndpointType": { + "type": "object", + "properties": { + "failoverPolicy": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Required. Failover policy of the read-only endpoint for the failover group." + } + }, + "targetServer": { + "type": "string", + "metadata": { + "description": "Required. The target partner server where the read-only endpoint points to." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a read-only endpoint." + } + }, + "readWriteEndpointType": { + "type": "object", + "properties": { + "failoverPolicy": { + "type": "string", + "allowedValues": [ + "Automatic", + "Manual" + ], + "metadata": { + "description": "Required. Failover policy of the read-write endpoint for the failover group. If failoverPolicy is Automatic then failoverWithDataLossGracePeriodMinutes is required." + } + }, + "failoverWithDataLossGracePeriodMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Grace period before failover with data loss is attempted for the read-write endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a read-write endpoint." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the failover group." + } + }, + "serverName": { + "type": "string", + "metadata": { + "description": "Conditional. The Name of SQL Server. Required if the template is used in a standalone deployment." + } + }, + "databases": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of databases in the failover group." + } + }, + "partnerServerResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of the partner server Resource Ids for the failover group." + } + }, + "readOnlyEndpoint": { + "$ref": "#/definitions/readOnlyEndpointType", + "nullable": true, + "metadata": { + "description": "Optional. Read-only endpoint of the failover group instance." + } + }, + "readWriteEndpoint": { + "$ref": "#/definitions/readWriteEndpointType", + "metadata": { + "description": "Required. Read-write endpoint of the failover group instance." + } + }, + "secondaryType": { + "type": "string", + "allowedValues": [ + "Geo", + "Standby" + ], + "metadata": { + "description": "Required. Databases secondary type on partner server." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Sql/servers/failoverGroups@2023-08-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + } + }, + "resources": { + "server": { + "existing": true, + "type": "Microsoft.Sql/servers", + "apiVersion": "2023-08-01", + "name": "[parameters('serverName')]" + }, + "failoverGroup": { + "type": "Microsoft.Sql/servers/failoverGroups", + "apiVersion": "2024-05-01-preview", + "name": "[format('{0}/{1}', parameters('serverName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "databases", + "count": "[length(parameters('databases'))]", + "input": "[resourceId('Microsoft.Sql/servers/databases', parameters('serverName'), parameters('databases')[copyIndex('databases')])]" + }, + { + "name": "partnerServers", + "count": "[length(parameters('partnerServerResourceIds'))]", + "input": { + "id": "[parameters('partnerServerResourceIds')[copyIndex('partnerServers')]]" + } + } + ], + "readOnlyEndpoint": "[if(not(empty(parameters('readOnlyEndpoint'))), createObject('failoverPolicy', parameters('readOnlyEndpoint').failoverPolicy, 'targetServer', resourceId(resourceGroup().name, 'Microsoft.Sql/servers', parameters('readOnlyEndpoint').targetServer)), null())]", + "readWriteEndpoint": "[parameters('readWriteEndpoint')]", + "secondaryType": "[parameters('secondaryType')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed failover group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed failover group." + }, + "value": "[resourceId('Microsoft.Sql/servers/failoverGroups', parameters('serverName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed failover group." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "server", + "server_databases" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed SQL server." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed SQL server." + }, + "value": "[resourceId('Microsoft.Sql/servers', parameters('name'))]" + }, + "fullyQualifiedDomainName": { + "type": "string", + "metadata": { + "description": "The fully qualified domain name of the deployed SQL server." + }, + "value": "[reference('server').fullyQualifiedDomainName]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed SQL server." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('server', '2023-08-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('server', '2023-08-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(coalesce(tryGet(tryGet(tryGet(if(not(equals(parameters('secretsExportConfiguration'), null())), reference('secretsExport'), null()), 'outputs'), 'secretsSet'), 'value'), createArray()), lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the SQL server." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('server_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('server_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('server_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('server_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('server_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + } + } + } + }, + "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').sqlServer)]", + "avmStorageAccount", + "logAnalyticsWorkspace", + "network", + "userAssignedIdentity" + ] + }, + "webServerFarm": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.web.serverfarm.{0}', variables('webServerFarmResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('webServerFarmResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "reserved": { + "value": true + }, + "kind": { + "value": "linux" + }, + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", + "skuName": "[if(or(parameters('enableScalability'), parameters('enableRedundancy')), createObject('value', 'P1v3'), createObject('value', 'B3'))]", + "skuCapacity": "[if(parameters('enableScalability'), createObject('value', 3), createObject('value', 1))]", + "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]" + }, + "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.36.177.2456", + "templateHash": "16945786131371363466" + }, + "name": "App Service Plan", + "description": "This module deploys an App Service Plan." + }, + "definitions": { + "diagnosticSettingMetricsOnlyType": { + "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 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 60, + "metadata": { + "description": "Required. Name of the app service plan." + } + }, + "skuName": { + "type": "string", + "defaultValue": "P1v3", + "metadata": { + "example": " 'F1'\n 'B1'\n 'P1v3'\n 'I1v2'\n 'FC1'\n ", + "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": "Optional. Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "kind": { + "type": "string", + "defaultValue": "app", + "allowedValues": [ + "app", + "elastic", + "functionapp", + "windows", + "linux" + ], + "metadata": { + "description": "Optional. Kind of server OS." + } + }, + "reserved": { + "type": "bool", + "defaultValue": "[equals(parameters('kind'), 'linux')]", + "metadata": { + "description": "Conditional. Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true." + } + }, + "appServiceEnvironmentResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource ID of the App Service Environment to use for the App Service Plan." + } + }, + "workerTierName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Target worker tier assigned to the App Service plan." + } + }, + "perSiteScaling": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. 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." + } + }, + "elasticScaleEnabled": { + "type": "bool", + "defaultValue": "[greater(parameters('maximumElasticWorkerCount'), 1)]", + "metadata": { + "description": "Optional. Enable/Disable ElasticScaleEnabled App Service Plan." + } + }, + "maximumElasticWorkerCount": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Maximum number of total workers allowed for this ElasticScaleEnabled App Service Plan." + } + }, + "targetWorkerCount": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Scaling worker count." + } + }, + "targetWorkerSize": { + "type": "int", + "defaultValue": 0, + "allowedValues": [ + 0, + 1, + 2 + ], + "metadata": { + "description": "Optional. The instance size of the hosting plan (small, medium, or large)." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": "[if(or(startsWith(parameters('skuName'), 'P'), startsWith(parameters('skuName'), 'EP')), true(), false())]", + "metadata": { + "description": "Optional. Zone Redundant server farms can only be used on Premium or ElasticPremium SKU tiers within ZRS Supported regions (https://learn.microsoft.com/en-us/azure/storage/common/redundancy-regions-zrs)." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2024-11-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingMetricsOnlyType" + }, + "nullable": true, + "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')]", + "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]", + "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.web-serverfarm.{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" + } + } + } + } + }, + "appServicePlan": { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2024-11-01", + "name": "[parameters('name')]", + "kind": "[parameters('kind')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": "[if(equals(parameters('skuName'), 'FC1'), createObject('name', parameters('skuName'), 'tier', 'FlexConsumption'), createObject('name', parameters('skuName'), 'capacity', parameters('skuCapacity')))]", + "properties": { + "workerTierName": "[parameters('workerTierName')]", + "hostingEnvironmentProfile": "[if(not(empty(parameters('appServiceEnvironmentResourceId'))), createObject('id', parameters('appServiceEnvironmentResourceId')), null())]", + "perSiteScaling": "[parameters('perSiteScaling')]", + "maximumElasticWorkerCount": "[parameters('maximumElasticWorkerCount')]", + "elasticScaleEnabled": "[parameters('elasticScaleEnabled')]", + "reserved": "[parameters('reserved')]", + "targetWorkerCount": "[parameters('targetWorkerCount')]", + "targetWorkerSizeId": "[parameters('targetWorkerSize')]", + "zoneRedundant": "[parameters('zoneRedundant')]" + } + }, + "appServicePlan_diagnosticSettings": { + "copy": { + "name": "appServicePlan_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Web/serverfarms/{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": [ + "appServicePlan" + ] + }, + "appServicePlan_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.Web/serverfarms/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "appServicePlan" + ] + }, + "appServicePlan_roleAssignments": { + "copy": { + "name": "appServicePlan_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Web/serverfarms', 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": [ + "appServicePlan" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the app service plan was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the app service plan." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the app service plan." + }, + "value": "[resourceId('Microsoft.Web/serverfarms', parameters('name'))]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('appServicePlan', '2024-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "webSite": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('module.web-sites.{0}', variables('webSiteResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('webSiteResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "kind": { + "value": "app,linux,container" + }, + "serverFarmResourceId": { + "value": "[tryGet(reference('webServerFarm'), 'outputs', 'resourceId', 'value')]" + }, + "siteConfig": { + "value": { + "linuxFxVersion": "[format('DOCKER|{0}/{1}:{2}', parameters('containerRegistryHostname'), parameters('containerImageName'), parameters('containerImageTag'))]", + "minTlsVersion": "1.2" + } + }, + "configs": { + "value": [ + { + "name": "appsettings", + "properties": { + "APP_ENV": "[variables('appEnvironment')]", + "APPINSIGHTS_INSTRUMENTATIONKEY": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.instrumentationKey.value, '')]", + "APPLICATIONINSIGHTS_CONNECTION_STRING": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.connectionString.value, '')]", + "AZURE_SEARCH_SERVICE": "[variables('aiSearchName')]", + "AZURE_SEARCH_INDEX": "[variables('azureSearchIndex')]", + "AZURE_SEARCH_USE_SEMANTIC_SEARCH": "[variables('azureSearchUseSemanticSearch')]", + "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG": "[variables('azureSearchSemanticSearchConfig')]", + "AZURE_SEARCH_TOP_K": "[variables('azureSearchTopK')]", + "AZURE_SEARCH_ENABLE_IN_DOMAIN": "[variables('azureSearchEnableInDomain')]", + "AZURE_SEARCH_CONTENT_COLUMNS": "[variables('azureSearchContentColumns')]", + "AZURE_SEARCH_FILENAME_COLUMN": "[variables('azureSearchFilenameColumn')]", + "AZURE_SEARCH_TITLE_COLUMN": "[variables('azureSearchTitleColumn')]", + "AZURE_SEARCH_URL_COLUMN": "[variables('azureSearchUrlColumn')]", + "AZURE_OPENAI_RESOURCE": "[reference('aiFoundryAiServices').outputs.name.value]", + "AZURE_OPENAI_MODEL": "[parameters('gptModelName')]", + "AZURE_OPENAI_ENDPOINT": "[reference('aiFoundryAiServices').outputs.endpoint.value]", + "AZURE_OPENAI_TEMPERATURE": "[variables('azureOpenAITemperature')]", + "AZURE_OPENAI_TOP_P": "[variables('azureOpenAITopP')]", + "AZURE_OPENAI_MAX_TOKENS": "[variables('azureOpenAIMaxTokens')]", + "AZURE_OPENAI_STOP_SEQUENCE": "[variables('azureOpenAIStopSequence')]", + "AZURE_OPENAI_SYSTEM_MESSAGE": "[variables('azureOpenAISystemMessage')]", + "AZURE_OPENAI_PREVIEW_API_VERSION": "[parameters('azureOpenaiAPIVersion')]", + "AZURE_OPENAI_STREAM": "[variables('azureOpenAIStream')]", + "AZURE_SEARCH_QUERY_TYPE": "[variables('azureSearchQueryType')]", + "AZURE_SEARCH_VECTOR_COLUMNS": "[variables('azureSearchVectorFields')]", + "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN": "[variables('azureSearchPermittedGroupsField')]", + "AZURE_SEARCH_STRICTNESS": "[variables('azureSearchStrictness')]", + "AZURE_OPENAI_EMBEDDING_NAME": "[parameters('embeddingModel')]", + "AZURE_OPENAI_EMBEDDING_ENDPOINT": "[reference('aiFoundryAiServices').outputs.endpoint.value]", + "SQLDB_SERVER": "[variables('sqlServerFqdn')]", + "SQLDB_DATABASE": "[variables('sqlDbName')]", + "USE_INTERNAL_STREAM": "[variables('useInternalStream')]", + "AZURE_COSMOSDB_ACCOUNT": "[reference('cosmosDb').outputs.name.value]", + "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": "[variables('collectionName')]", + "AZURE_COSMOSDB_DATABASE": "[variables('cosmosDbDatabaseName')]", + "AZURE_COSMOSDB_ENABLE_FEEDBACK": "[variables('azureCosmosDbEnableFeedback')]", + "SQLDB_USER_MID": "[reference('userAssignedIdentity').outputs.clientId.value]", + "AZURE_AI_SEARCH_ENDPOINT": "[reference('searchService').outputs.endpoint.value]", + "AZURE_SQL_SYSTEM_PROMPT": "[variables('functionAppSqlPrompt')]", + "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT": "[variables('functionAppCallTranscriptSystemPrompt')]", + "AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT": "[variables('functionAppStreamTextSystemPrompt')]", + "USE_AI_PROJECT_CLIENT": "[variables('useAIProjectClientFlag')]", + "AZURE_AI_AGENT_ENDPOINT": "[reference('aiFoundryAiServices').outputs.aiProjectInfo.value.apiEndpoint]", + "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": "[parameters('gptModelName')]", + "AZURE_AI_AGENT_API_VERSION": "[parameters('azureOpenaiAPIVersion')]", + "AZURE_SEARCH_CONNECTION_NAME": "" + }, + "applicationInsightResourceId": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.resourceId.value, null())]" + } + ] + }, + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", + "vnetRouteAllEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", + "vnetImagePullEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", + "virtualNetworkSubnetId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('network').outputs.subnetWebResourceId.value), createObject('value', null()))]", + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('webSiteResourceName')), 'customNetworkInterfaceName', format('nic-{0}', variables('webSiteResourceName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').appService)).outputs.resourceId.value))), 'service', 'sites', 'subnetResourceId', reference('network').outputs.subnetWebResourceId.value))), createObject('value', null()))]" + }, + "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.37.4.10188", + "templateHash": "4298119334635398540" + } + }, + "definitions": { + "appSettingsConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": [ + "appsettings" + ], + "metadata": { + "description": "Required. The type of config." + } + }, + "storageAccountUseIdentityAuthentication": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. 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'." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions." + } + }, + "applicationInsightResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application insight to leverage for this resource." + } + }, + "retainCurrentAppSettings": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The retain the current app settings. Defaults to true." + } + }, + "properties": { + "type": "object", + "properties": {}, + "additionalProperties": { + "type": "string", + "metadata": { + "description": "Required. An app settings key-value pair." + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The app settings key-value pairs except for AzureWebJobsStorage, AzureWebJobsDashboard, APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of an app settings configuration." + } + }, + "_1.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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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/_1.lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.roleAssignmentType" + }, + "nullable": true, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the site." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "functionapp", + "functionapp,linux", + "functionapp,workflowapp", + "functionapp,workflowapp,linux", + "functionapp,linux,container", + "functionapp,linux,container,azurecontainerapps", + "app,linux", + "app", + "linux,api", + "api", + "app,linux,container", + "app,container,windows" + ], + "metadata": { + "description": "Required. Type of site to deploy." + } + }, + "serverFarmResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the app service plan to use for the site." + } + }, + "managedEnvironmentId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure Resource Manager ID of the customers selected Managed Environment on which to host this app." + } + }, + "httpsOnly": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Configures a site to accept only HTTPS requests. Issues redirect for HTTP requests." + } + }, + "clientAffinityEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. If client affinity is enabled." + } + }, + "appServiceEnvironmentResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the app service environment to use for this resource." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "keyVaultAccessIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the assigned identity to be used to access a key vault with." + } + }, + "storageAccountRequired": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Checks if Customer provided storage account is required." + } + }, + "virtualNetworkSubnetId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration. This must be of the form /subscriptions/{subscriptionName}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}." + } + }, + "vnetContentShareEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. To enable accessing content over virtual network." + } + }, + "vnetImagePullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. To enable pulling image over Virtual Network." + } + }, + "vnetRouteAllEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Virtual Network Route All enabled. This causes all outbound traffic to have Virtual Network Security Groups and User Defined Routes applied." + } + }, + "scmSiteAlsoStopped": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Stop SCM (KUDU) site when the app is stopped." + } + }, + "siteConfig": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/siteConfig" + }, + "description": "Optional. The site config object. The defaults are set to the following values: alwaysOn: true, minTlsVersion: '1.2', ftpsState: 'FtpsOnly'." + }, + "defaultValue": { + "alwaysOn": true, + "minTlsVersion": "1.2", + "ftpsState": "FtpsOnly" + } + }, + "configs": { + "type": "array", + "items": { + "$ref": "#/definitions/appSettingsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The web site config." + } + }, + "functionAppConfig": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/functionAppConfig" + }, + "description": "Optional. The Function App configuration object." + }, + "nullable": true + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "clientCertEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. To enable client certificate authentication (TLS mutual authentication)." + } + }, + "clientCertExclusionPaths": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Client certificate authentication comma-separated exclusion paths." + } + }, + "clientCertMode": { + "type": "string", + "defaultValue": "Optional", + "allowedValues": [ + "Optional", + "OptionalInteractiveUser", + "Required" + ], + "metadata": { + "description": "Optional. This composes with ClientCertEnabled setting.\n- ClientCertEnabled=false means ClientCert is ignored.\n- ClientCertEnabled=true and ClientCertMode=Required means ClientCert is required.\n- ClientCertEnabled=true and ClientCertMode=Optional means ClientCert is optional or accepted.\n" + } + }, + "cloningInfo": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/cloningInfo" + }, + "description": "Optional. If specified during app creation, the app is cloned from a source app." + }, + "nullable": true + }, + "containerSize": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Size of the function container." + } + }, + "dailyMemoryTimeQuota": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum allowed daily memory-time quota (applicable on dynamic apps only)." + } + }, + "enabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Setting this value to false disables the app (takes the app offline)." + } + }, + "hostNameSslStates": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/hostNameSslStates" + }, + "description": "Optional. Hostname SSL states are used to manage the SSL bindings for app's hostnames." + }, + "nullable": true + }, + "hyperV": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Hyper-V sandbox." + } + }, + "redundancyMode": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "ActiveActive", + "Failover", + "GeoRedundant", + "Manual", + "None" + ], + "metadata": { + "description": "Optional. Site redundancy mode." + } + }, + "publicNetworkAccess": { + "type": "string", + "nullable": true, + "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." + } + }, + "e2eEncryptionEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. End to End Encryption Setting." + } + }, + "dnsConfiguration": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/dnsConfiguration" + }, + "description": "Optional. Property to configure various DNS related settings for a site." + }, + "nullable": true + }, + "autoGeneratedDomainNameLabelScope": { + "type": "string", + "nullable": true, + "allowedValues": [ + "NoReuse", + "ResourceGroupReuse", + "SubscriptionReuse", + "TenantReuse" + ], + "metadata": { + "description": "Optional. Specifies the scope of uniqueness for the default hostname during resource creation." + } + } + }, + "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())]" + }, + "resources": { + "app": { + "type": "Microsoft.Web/sites", + "apiVersion": "2024-04-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "kind": "[parameters('kind')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "managedEnvironmentId": "[if(not(empty(parameters('managedEnvironmentId'))), parameters('managedEnvironmentId'), null())]", + "serverFarmId": "[parameters('serverFarmResourceId')]", + "clientAffinityEnabled": "[parameters('clientAffinityEnabled')]", + "httpsOnly": "[parameters('httpsOnly')]", + "hostingEnvironmentProfile": "[if(not(empty(parameters('appServiceEnvironmentResourceId'))), createObject('id', parameters('appServiceEnvironmentResourceId')), null())]", + "storageAccountRequired": "[parameters('storageAccountRequired')]", + "keyVaultReferenceIdentity": "[parameters('keyVaultAccessIdentityResourceId')]", + "virtualNetworkSubnetId": "[parameters('virtualNetworkSubnetId')]", + "siteConfig": "[parameters('siteConfig')]", + "functionAppConfig": "[parameters('functionAppConfig')]", + "clientCertEnabled": "[parameters('clientCertEnabled')]", + "clientCertExclusionPaths": "[parameters('clientCertExclusionPaths')]", + "clientCertMode": "[parameters('clientCertMode')]", + "cloningInfo": "[parameters('cloningInfo')]", + "containerSize": "[parameters('containerSize')]", + "dailyMemoryTimeQuota": "[parameters('dailyMemoryTimeQuota')]", + "enabled": "[parameters('enabled')]", + "hostNameSslStates": "[parameters('hostNameSslStates')]", + "hyperV": "[parameters('hyperV')]", + "redundancyMode": "[parameters('redundancyMode')]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(not(empty(parameters('privateEndpoints'))), 'Disabled', 'Enabled'))]", + "vnetContentShareEnabled": "[parameters('vnetContentShareEnabled')]", + "vnetImagePullEnabled": "[parameters('vnetImagePullEnabled')]", + "vnetRouteAllEnabled": "[parameters('vnetRouteAllEnabled')]", + "scmSiteAlsoStopped": "[parameters('scmSiteAlsoStopped')]", + "endToEndEncryptionEnabled": "[parameters('e2eEncryptionEnabled')]", + "dnsConfiguration": "[parameters('dnsConfiguration')]", + "autoGeneratedDomainNameLabelScope": "[parameters('autoGeneratedDomainNameLabelScope')]" + } + }, + "app_diagnosticSettings": { + "copy": { + "name": "app_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Web/sites/{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": [ + "app" + ] + }, + "app_config": { + "copy": { + "name": "app_config", + "count": "[length(coalesce(parameters('configs'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[parameters('saName')]", - "location": "[parameters('solutionLocation')]", - "sku": { - "name": "Standard_LRS", - "tier": "Standard" + "name": "[format('{0}-Site-Config-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "appName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('configs'), createArray())[copyIndex()].name]" + }, + "applicationInsightResourceId": { + "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'applicationInsightResourceId')]" + }, + "storageAccountResourceId": { + "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'storageAccountResourceId')]" + }, + "storageAccountUseIdentityAuthentication": { + "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'storageAccountUseIdentityAuthentication')]" + }, + "properties": { + "value": "[tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'properties')]" + }, + "currentAppSettings": "[if(coalesce(tryGet(coalesce(parameters('configs'), createArray())[copyIndex()], 'retainCurrentAppSettings'), and(true(), equals(coalesce(parameters('configs'), createArray())[copyIndex()].name, 'appsettings'))), 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#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "4653685834544796273" + }, + "name": "Site App Settings", + "description": "This module deploys a Site App Setting." + }, + "parameters": { + "appName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent site resource. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "allowedValues": [ + "appsettings", + "authsettings", + "authsettingsV2", + "azurestorageaccounts", + "backup", + "connectionstrings", + "logs", + "metadata", + "pushsettings", + "slotConfigNames", + "web" + ], + "metadata": { + "description": "Required. The name of the config." + } + }, + "properties": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The properties of the config. Note: This parameter is highly dependent on the config type, defined by its name." + } + }, + "storageAccountUseIdentityAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. 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'." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Required if app of kind functionapp. Resource ID of the storage account to manage triggers and logging function executions." + } + }, + "applicationInsightResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application insight to leverage for this resource." + } + }, + "currentAppSettings": { + "type": "object", + "properties": {}, + "additionalProperties": { + "type": "string", + "metadata": { + "description": "Required. The key-values pairs of the current app settings." + } + }, + "defaultValue": {}, + "metadata": { + "description": "Optional. The current app settings." + } + } + }, + "resources": { + "applicationInsights": { + "condition": "[not(empty(parameters('applicationInsightResourceId')))]", + "existing": true, + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "subscriptionId": "[split(parameters('applicationInsightResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('applicationInsightResourceId'), '/')[4]]", + "name": "[last(split(parameters('applicationInsightResourceId'), '/'))]" + }, + "storageAccount": { + "condition": "[not(empty(parameters('storageAccountResourceId')))]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "subscriptionId": "[split(parameters('storageAccountResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", + "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "app": { + "existing": true, + "type": "Microsoft.Web/sites", + "apiVersion": "2023-12-01", + "name": "[parameters('appName')]" + }, + "config": { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2024-04-01", + "name": "[format('{0}/{1}', parameters('appName'), parameters('name'))]", + "properties": "[union(parameters('currentAppSettings'), parameters('properties'), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(parameters('storageAccountResourceId'), '/')), listKeys('storageAccount', '2024-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), createObject('AzureWebJobsStorage__accountName', last(split(parameters('storageAccountResourceId'), '/')), 'AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob, 'AzureWebJobsStorage__queueServiceUri', reference('storageAccount').primaryEndpoints.queue, 'AzureWebJobsStorage__tableServiceUri', reference('storageAccount').primaryEndpoints.table), createObject())), if(not(empty(parameters('applicationInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('applicationInsights').ConnectionString), createObject()))]", + "dependsOn": [ + "applicationInsights", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the site config." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the site config." + }, + "value": "[resourceId('Microsoft.Web/sites/config', parameters('appName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the site config was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "app" + ] + }, + "app_privateEndpoints": { + "copy": { + "name": "app_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, - "kind": "StorageV2", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", "properties": { - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "isHnsEnabled": true, - "networkAcls": { - "bypass": "AzureServices", - "virtualNetworkRules": [], - "ipRules": [], - "defaultAction": "Allow" + "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.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Web/sites', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Web/sites', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Web/sites', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'sites')), '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": false + }, + "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'), null())]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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')]" + } }, - "supportsHttpsTrafficOnly": true, - "encryption": { - "services": { - "file": { - "keyType": "Account", - "enabled": true + "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.34.44.8038", + "templateHash": "12389807800450456797" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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", + "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." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } + }, + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } }, - "blob": { - "keyType": "Account", - "enabled": true + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } } }, - "keySource": "Microsoft.Storage" - }, - "accessTier": "Hot", - "allowSharedKeyAccess": false - }, - "tags": "[parameters('tags')]" - }, - { - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('saName'), 'default')]", - "properties": { - "cors": { - "corsRules": [] - }, - "deleteRetentionPolicy": { - "allowPermanentDelete": false, - "enabled": false + "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": "[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-privateendpoint.{0}.{1}', replace('0.11.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" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.34.44.8038", + "templateHash": "13997305779829540948" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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', '2024-05-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } } }, "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', parameters('saName'))]" - ] - }, - { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('saName'), 'default', 'data')]", - "properties": { - "defaultEncryptionScope": "$account-encryption-key", - "denyEncryptionScopeOverride": false, - "publicAccess": "None" - }, - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('saName'), 'default')]", - "[resourceId('Microsoft.Storage/storageAccounts', parameters('saName'))]" + "app" ] - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceGroup().id, parameters('managedIdentityObjectId'), resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'))]", - "properties": { - "principalId": "[parameters('managedIdentityObjectId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", - "principalType": "ServicePrincipal" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'ADLS-ACCOUNT-NAME')]", - "properties": { - "value": "[parameters('saName')]" - }, - "tags": "[parameters('tags')]" - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'ADLS-ACCOUNT-CONTAINER')]", - "properties": { - "value": "data" - }, - "tags": "[parameters('tags')]" } - ], + }, "outputs": { - "storageName": { + "name": { "type": "string", "metadata": { - "description": "Name of the storage account." + "description": "The name of the site." }, - "value": "[parameters('saName')]" + "value": "[parameters('name')]" }, - "storageContainer": { + "resourceId": { "type": "string", "metadata": { - "description": "Name of the default storage container." + "description": "The resource ID of the site." }, - "value": "data" - } - } - } - }, - "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault')]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploy_sql_db", - "resourceGroup": "[resourceGroup().name]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "solutionLocation": { - "value": "[variables('solutionLocation')]" - }, - "keyVaultName": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" - }, - "managedIdentityObjectId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]" - }, - "managedIdentityName": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.name]" - }, - "serverName": { - "value": "[format('sql-{0}', variables('solutionSuffix'))]" - }, - "sqlDBName": { - "value": "[format('sqldb-{0}', variables('solutionSuffix'))]" - }, - "tags": { - "value": "[parameters('tags')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "16739666735476206580" - } - }, - "parameters": { - "solutionLocation": { - "type": "string", - "metadata": { - "description": "Required. Deployment location for the solution." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Azure Key Vault." - } - }, - "managedIdentityObjectId": { - "type": "string", - "metadata": { - "description": "Required. Object ID of the managed identity." - } - }, - "managedIdentityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity." - } + "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]" }, - "serverName": { + "resourceGroupName": { "type": "string", "metadata": { - "description": "Required. The name of the SQL logical server." - } + "description": "The resource group the site was deployed into." + }, + "value": "[resourceGroup().name]" }, - "sqlDBName": { + "systemAssignedMIPrincipalId": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The name of the SQL Database." - } + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('app', '2024-04-01', 'full'), 'identity'), 'principalId')]" }, "location": { "type": "string", - "defaultValue": "[parameters('solutionLocation')]", - "metadata": { - "description": "Required. Location for all resources." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, "metadata": { - "description": "Optional. Tags to be applied to the resources." - } - } - }, - "resources": [ - { - "type": "Microsoft.Sql/servers", - "apiVersion": "2023-08-01-preview", - "name": "[parameters('serverName')]", - "location": "[parameters('location')]", - "kind": "v12.0", - "properties": { - "publicNetworkAccess": "Enabled", - "version": "12.0", - "restrictOutboundNetworkAccess": "Disabled", - "minimalTlsVersion": "1.2", - "administrators": { - "login": "[parameters('managedIdentityName')]", - "sid": "[parameters('managedIdentityObjectId')]", - "tenantId": "[subscription().tenantId]", - "administratorType": "ActiveDirectory", - "azureADOnlyAuthentication": true - } - }, - "tags": "[parameters('tags')]" - }, - { - "type": "Microsoft.Sql/servers/firewallRules", - "apiVersion": "2023-08-01-preview", - "name": "[format('{0}/{1}', parameters('serverName'), 'AllowSpecificRange')]", - "properties": { - "startIpAddress": "0.0.0.0", - "endIpAddress": "255.255.255.255" - }, - "dependsOn": [ - "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" - ] - }, - { - "type": "Microsoft.Sql/servers/firewallRules", - "apiVersion": "2023-08-01-preview", - "name": "[format('{0}/{1}', parameters('serverName'), 'AllowAllWindowsAzureIps')]", - "properties": { - "startIpAddress": "0.0.0.0", - "endIpAddress": "0.0.0.0" - }, - "dependsOn": [ - "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" - ] - }, - { - "type": "Microsoft.Sql/servers/databases", - "apiVersion": "2023-08-01-preview", - "name": "[format('{0}/{1}', parameters('serverName'), parameters('sqlDBName'))]", - "location": "[parameters('location')]", - "sku": { - "name": "GP_S_Gen5", - "tier": "GeneralPurpose", - "family": "Gen5", - "capacity": 2 - }, - "kind": "v12.0,user,vcore,serverless", - "properties": { - "collation": "SQL_Latin1_General_CP1_CI_AS", - "autoPauseDelay": 60, - "minCapacity": 1, - "readScale": "Disabled", - "zoneRedundant": false - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'SQLDB-SERVER')]", - "properties": { - "value": "[format('{0}.database.windows.net', parameters('serverName'))]" + "description": "The location the resource was deployed into." }, - "tags": "[parameters('tags')]" + "value": "[reference('app', '2024-04-01', 'full').location]" }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'SQLDB-DATABASE')]", - "properties": { - "value": "[parameters('sqlDBName')]" - }, - "tags": "[parameters('tags')]" - } - ], - "outputs": { - "sqlServerName": { + "defaultHostname": { "type": "string", "metadata": { - "description": "Name of the SQL logical server." + "description": "Default hostname of the app." }, - "value": "[parameters('serverName')]" + "value": "[reference('app').defaultHostName]" }, - "sqlDbName": { + "customDomainVerificationId": { "type": "string", "metadata": { - "description": "Name of the SQL database." + "description": "Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification." }, - "value": "[parameters('sqlDBName')]" + "value": "[reference('app').customDomainVerificationId]" + }, + "outboundIpAddresses": { + "type": "string", + "metadata": { + "description": "The outbound IP addresses of the app." + }, + "value": "[reference('app').outboundIpAddresses]" } } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault')]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]" + "aiFoundryAiServices", + "applicationInsights", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').appService)]", + "cosmosDb", + "logAnalyticsWorkspace", + "network", + "searchService", + "userAssignedIdentity", + "webServerFarm" ] }, - { + "searchService": { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "deploy_app_service", - "resourceGroup": "[resourceGroup().name]", + "name": "searchServiceDeployment", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "solutionLocation": { - "value": "[variables('solutionLocation')]" - }, - "hostingPlanName": { - "value": "[variables('hostingPlanName')]" - }, - "websiteName": { - "value": "[variables('websiteName')]" - }, - "appEnvironment": { - "value": "[variables('appEnvironment')]" - }, - "azureSearchService": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" - }, - "azureSearchIndex": { - "value": "[variables('azureSearchIndex')]" - }, - "azureSearchUseSemanticSearch": { - "value": "[variables('azureSearchUseSemanticSearch')]" - }, - "azureSearchSemanticSearchConfig": { - "value": "[variables('azureSearchSemanticSearchConfig')]" - }, - "azureSearchTopK": { - "value": "[variables('azureSearchTopK')]" - }, - "azureSearchContentColumns": { - "value": "[variables('azureSearchContentColumns')]" - }, - "azureSearchFilenameColumn": { - "value": "[variables('azureSearchFilenameColumn')]" - }, - "azureSearchTitleColumn": { - "value": "[variables('azureSearchTitleColumn')]" - }, - "azureSearchUrlColumn": { - "value": "[variables('azureSearchUrlColumn')]" - }, - "azureOpenAIResource": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" - }, - "azureOpenAIEndpoint": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" - }, - "azureOpenAIModel": { - "value": "[parameters('gptModelName')]" - }, - "azureOpenAITemperature": { - "value": "[variables('azureOpenAITemperature')]" - }, - "azureOpenAITopP": { - "value": "[variables('azureOpenAITopP')]" - }, - "azureOpenAIMaxTokens": { - "value": "[variables('azureOpenAIMaxTokens')]" - }, - "azureOpenAIStopSequence": { - "value": "[variables('azureOpenAIStopSequence')]" - }, - "azureOpenAISystemMessage": { - "value": "[variables('azureOpenAISystemMessage')]" - }, - "azureOpenAIApiVersion": { - "value": "[parameters('azureOpenaiAPIVersion')]" - }, - "azureOpenAIStream": { - "value": "[variables('azureOpenAIStream')]" - }, - "azureSearchQueryType": { - "value": "[variables('azureSearchQueryType')]" - }, - "azureSearchVectorFields": { - "value": "[variables('azureSearchVectorFields')]" - }, - "azureSearchPermittedGroupsField": { - "value": "[variables('azureSearchPermittedGroupsField')]" - }, - "azureSearchStrictness": { - "value": "[variables('azureSearchStrictness')]" + "name": { + "value": "[variables('aiSearchName')]" }, - "azureOpenAIEmbeddingName": { - "value": "[parameters('embeddingModel')]" - }, - "azureOpenAIEmbeddingEndpoint": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" - }, - "USE_INTERNAL_STREAM": { - "value": "[variables('useInternalStream')]" - }, - "SQLDB_SERVER": { - "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" - }, - "SQLDB_DATABASE": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlDbName.value]" - }, - "AZURE_COSMOSDB_ACCOUNT": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" - }, - "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosContainerName.value]" - }, - "AZURE_COSMOSDB_DATABASE": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" - }, - "AZURE_COSMOSDB_ENABLE_FEEDBACK": { - "value": "[variables('azureCosmosDbEnableFeedback')]" - }, - "imageTag": { - "value": "[parameters('imageTag')]" - }, - "userassignedIdentityClientId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" - }, - "userassignedIdentityId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.id]" - }, - "applicationInsightsId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsId.value]" - }, - "azureSearchServiceEndpoint": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchTarget.value]" + "authOptions": { + "value": { + "aadOrApiKey": { + "aadAuthFailureMode": "http401WithBearerChallenge" + } + } }, - "sqlSystemPrompt": { - "value": "[variables('functionAppSqlPrompt')]" + "cmkEnforcement": { + "value": "Enabled" }, - "callTranscriptSystemPrompt": { - "value": "[variables('functionAppCallTranscriptSystemPrompt')]" + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "disableLocalAuth": { + "value": false }, - "streamTextSystemPrompt": { - "value": "[variables('functionAppStreamTextSystemPrompt')]" + "hostingMode": { + "value": "default" }, - "aiFoundryProjectEndpoint": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryProjectEndpoint.value]" + "managedIdentities": { + "value": { + "systemAssigned": true + } }, - "aiFoundryName": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" + "networkRuleSet": { + "value": { + "bypass": "AzureServices", + "ipRules": [ + { + "value": "40.74.28.0/23" + }, + { + "value": "87.147.204.13" + } + ] + } }, - "applicationInsightsConnectionString": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + "partitionCount": { + "value": 2 }, - "azureExistingAIProjectResourceId": { - "value": "[parameters('azureExistingAIProjectResourceId')]" + "replicaCount": { + "value": 3 }, - "aiSearchProjectConnectionName": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" + "sku": { + "value": "basic" }, "tags": { "value": "[parameters('tags')]" @@ -2188,924 +54753,2332 @@ }, "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.37.4.10188", - "templateHash": "1598037199910826757" - } - }, - "parameters": { - "solutionLocation": { - "type": "string", - "metadata": { - "description": "Required. Solution Location" - } - }, - "hostingPlanSku": { - "type": "string", - "defaultValue": "B2", - "allowedValues": [ - "F1", - "D1", - "B1", - "B2", - "B3", - "S1", - "S2", - "S3", - "P1", - "P2", - "P3", - "P4", - "P0v3" - ], - "metadata": { - "description": "Optional. The pricing tier for the App Service plan" - } - }, - "hostingPlanName": { - "type": "string", - "metadata": { - "description": "Required. Name of App Service plan" - } - }, - "websiteName": { - "type": "string", - "metadata": { - "description": "Required. Name of Web App" - } + "templateHash": "10902281417196168235" }, - "appEnvironment": { - "type": "string", + "name": "Search Services", + "description": "This module deploys a Search Service." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, "metadata": { - "description": "Specifies the application environment" + "__bicep_export!": true } }, - "azureSearchService": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Name of Azure Search Service" + "secretsExportConfigurationType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The key vault name where to store the API Admin keys generated by the modules." + } + }, + "primaryAdminKeyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The primaryAdminKey secret name to create." + } + }, + "secondaryAdminKeyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The secondaryAdminKey secret name to create." + } + } } }, - "azureSearchIndex": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Name of Azure Search Index" + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/secretSetType", + "metadata": { + "description": "An exported secret's references." + } } }, - "azureSearchUseSemanticSearch": { - "type": "string", - "defaultValue": "False", + "authOptionsType": { + "type": "object", + "properties": { + "aadOrApiKey": { + "type": "object", + "properties": { + "aadAuthFailureMode": { + "type": "string", + "allowedValues": [ + "http401WithBearerChallenge", + "http403" + ], + "nullable": true, + "metadata": { + "description": "Optional. Describes what response the data plane API of a search service would send for requests that failed authentication." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Indicates that either the API key or an access token from a Microsoft Entra ID tenant can be used for authentication." + } + }, + "apiKeyOnly": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Indicates that only the API key can be used for authentication." + } + } + }, "metadata": { - "description": "Optional. Use semantic search" + "__bicep_export!": true } }, - "azureSearchSemanticSearchConfig": { - "type": "string", - "defaultValue": "default", + "networkRuleSetType": { + "type": "object", + "properties": { + "bypass": { + "type": "string", + "allowedValues": [ + "AzurePortal", + "AzureServices", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Network specific rules that determine how the Azure AI Search service may be reached." + } + }, + "ipRules": { + "type": "array", + "items": { + "$ref": "#/definitions/ipRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP restriction rules that defines the inbound network(s) with allowing access to the search service endpoint. At the meantime, all other public IP networks are blocked by the firewall. These restriction rules are applied only when the 'publicNetworkAccess' of the search service is 'enabled'; otherwise, traffic over public interface is not allowed even with any public IP rules, and private endpoint connections would be the exclusive access method." + } + } + }, "metadata": { - "description": "Optional. Semantic search config" + "__bicep_export!": true } }, - "azureSearchTopK": { - "type": "string", - "defaultValue": "5", + "ipRuleType": { + "type": "object", + "properties": { + "value": { + "type": "string", + "metadata": { + "description": "Required. Value corresponding to a single IPv4 address (eg., 123.1.2.3) or an IP range in CIDR format (eg., 123.1.2.3/24) to be allowed." + } + } + }, "metadata": { - "description": "Optional. Top K results" + "__bicep_export!": true } }, - "azureSearchEnableInDomain": { - "type": "string", - "defaultValue": "False", + "_1.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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, "metadata": { - "description": "Optional. Enable in domain" + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } } }, - "azureSearchContentColumns": { - "type": "string", - "defaultValue": "content", + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, "metadata": { - "description": "Optional. Content columns" + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } } }, - "azureSearchFilenameColumn": { - "type": "string", - "defaultValue": "filename", + "_1.privateEndpointIpConfigurationType": { + "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." + } + } + }, "metadata": { - "description": "Optional. Filename column" + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } } }, - "azureSearchTitleColumn": { - "type": "string", - "defaultValue": "client_id", + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, "metadata": { - "description": "Optional. Title column" + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } } }, - "azureSearchUrlColumn": { - "type": "string", - "defaultValue": "sourceurl", + "_1.roleAssignmentType": { + "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." + } + } + }, "metadata": { - "description": "Optional. Url column" + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } } }, - "azureOpenAIResource": { - "type": "string", + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, "metadata": { - "description": "Required. Name of Azure OpenAI Resource" + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "azureOpenAIModel": { - "type": "string", - "defaultValue": "gpt-4o-mini", + "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." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, "metadata": { - "description": "Optional. Azure OpenAI Model Deployment Name" + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } } }, - "azureOpenAIEndpoint": { - "type": "string", - "defaultValue": "", + "managedIdentityAllType": { + "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." + } + } + }, "metadata": { - "description": "Optional. Azure Open AI Endpoint" + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "azureOpenAITemperature": { - "type": "string", - "defaultValue": "0", + "privateEndpointSingleServiceType": { + "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." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "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": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "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/_1.lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags" + }, + "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." + } + } + }, "metadata": { - "description": "Optional. Azure OpenAI Temperature" + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } } }, - "azureOpenAITopP": { - "type": "string", - "defaultValue": "1", + "roleAssignmentType": { + "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." + } + } + }, "metadata": { - "description": "Optional. Azure OpenAI Top P" + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "azureOpenAIMaxTokens": { - "type": "string", - "defaultValue": "1000", + "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": { - "description": "Optional. Azure OpenAI Max Tokens" + "__bicep_imported_from!": { + "sourceTemplate": "modules/keyVaultExport.bicep" + } } - }, - "azureOpenAIStopSequence": { + } + }, + "parameters": { + "name": { "type": "string", - "defaultValue": "\n", "metadata": { - "description": "Optional. Azure OpenAI Stop Sequence" + "description": "Required. The name of the Azure Cognitive Search service to create or update. Search service names must only contain lowercase letters, digits or dashes, cannot use dash as the first two or last one characters, cannot contain consecutive dashes, and must be between 2 and 60 characters in length. Search service names must be globally unique since they are part of the service URI (https://.search.windows.net). You cannot change the service name after the service is created." } }, - "azureOpenAISystemMessage": { - "type": "string", - "defaultValue": "You are an AI assistant that helps people find information.", + "authOptions": { + "$ref": "#/definitions/authOptionsType", + "nullable": true, "metadata": { - "description": "Optional. Azure OpenAI System Message" + "description": "Optional. Defines the options for how the data plane API of a Search service authenticates requests. Must remain an empty object {} if 'disableLocalAuth' is set to true." } }, - "azureOpenAIApiVersion": { - "type": "string", - "defaultValue": "2024-02-15-preview", + "disableLocalAuth": { + "type": "bool", + "defaultValue": true, "metadata": { - "description": "Optional. Azure OpenAI Api Version" + "description": "Optional. When set to true, calls to the search service will not be permitted to utilize API keys for authentication. This cannot be set to true if 'authOptions' are defined." } }, - "azureOpenAIStream": { - "type": "string", - "defaultValue": "True", + "enableTelemetry": { + "type": "bool", + "defaultValue": true, "metadata": { - "description": "Optional. Whether or not to stream responses from Azure OpenAI" + "description": "Optional. Enable/Disable usage telemetry for module." } }, - "azureSearchQueryType": { + "cmkEnforcement": { "type": "string", - "defaultValue": "simple", + "defaultValue": "Unspecified", "allowedValues": [ - "simple", - "semantic", - "vector", - "vectorSimpleHybrid", - "vectorSemanticHybrid" + "Disabled", + "Enabled", + "Unspecified" ], "metadata": { - "description": "Optional. Azure Search Query Type" - } - }, - "azureSearchVectorFields": { - "type": "string", - "defaultValue": "contentVector", - "metadata": { - "description": "Optional. Azure Search Vector Fields" - } - }, - "azureSearchPermittedGroupsField": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure Search Permitted Groups Field" + "description": "Optional. Describes a policy that determines how resources within the search service are to be encrypted with Customer Managed Keys." } }, - "azureSearchStrictness": { + "hostingMode": { "type": "string", - "defaultValue": "3", + "defaultValue": "default", "allowedValues": [ - "1", - "2", - "3", - "4", - "5" + "default", + "highDensity" ], "metadata": { - "description": "Optional. Azure Search Strictness" - } - }, - "azureOpenAIEmbeddingName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure OpenAI Embedding Deployment Name" - } - }, - "azureOpenAIEmbeddingEndpoint": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure Open AI Embedding Endpoint" - } - }, - "USE_INTERNAL_STREAM": { - "type": "string", - "defaultValue": "True", - "metadata": { - "description": "Optional. Use Azure Function" - } - }, - "SQLDB_SERVER": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. SQL Database Server Name" - } - }, - "SQLDB_DATABASE": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. SQL Database Name" - } - }, - "AZURE_COSMOSDB_ACCOUNT": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure Cosmos DB Account" - } - }, - "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure Cosmos DB Conversations Container" - } - }, - "AZURE_COSMOSDB_DATABASE": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure Cosmos DB Database" + "description": "Optional. Applicable only for the standard3 SKU. You can set this property to enable up to 3 high density partitions that allow up to 1000 indexes, which is much higher than the maximum indexes allowed for any other SKU. For the standard3 SKU, the value is either 'default' or 'highDensity'. For all other SKUs, this value must be 'default'." } }, - "AZURE_COSMOSDB_ENABLE_FEEDBACK": { + "location": { "type": "string", - "defaultValue": "True", + "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "Optional. Enable feedback in Cosmos DB" + "description": "Optional. Location for all Resources." } }, - "imageTag": { - "type": "string", + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, "metadata": { - "description": "Required. The container image tag to be deployed" + "description": "Optional. The lock settings for all Resources in the solution." } }, - "userassignedIdentityId": { - "type": "string", + "networkRuleSet": { + "$ref": "#/definitions/networkRuleSetType", + "nullable": true, "metadata": { - "description": "Required. The resource ID of the user-assigned managed identity to be used by the deployed resources." + "description": "Optional. Network specific rules that determine how the Azure Cognitive Search service may be reached." } }, - "userassignedIdentityClientId": { - "type": "string", + "partitionCount": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 12, "metadata": { - "description": "Required. The client ID of the user-assigned managed identity." + "description": "Optional. The number of partitions in the search service; if specified, it can be 1, 2, 3, 4, 6, or 12. Values greater than 1 are only valid for standard SKUs. For 'standard3' services with hostingMode set to 'highDensity', the allowed values are between 1 and 3." } }, - "applicationInsightsId": { - "type": "string", + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, "metadata": { - "description": "Required. The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring." + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." } }, - "azureSearchServiceEndpoint": { - "type": "string", + "sharedPrivateLinkResources": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "Required. The endpoint URL of the Azure Cognitive Search service." + "description": "Optional. The sharedPrivateLinkResources to create as part of the search Service." } }, - "sqlSystemPrompt": { + "publicNetworkAccess": { "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], "metadata": { - "description": "Required. Azure Function App SQL System Prompt" + "description": "Optional. This value can be set to 'Enabled' to avoid breaking changes on existing customer resources and templates. If set to 'Disabled', traffic over public interface is not allowed, and private endpoint connections would be the exclusive access method." } }, - "callTranscriptSystemPrompt": { - "type": "string", + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, "metadata": { - "description": "Required. Azure Function App CallTranscript System Prompt" + "description": "Optional. Key vault reference and secret settings for the module's secrets export." } }, - "streamTextSystemPrompt": { - "type": "string", + "replicaCount": { + "type": "int", + "defaultValue": 3, + "minValue": 1, + "maxValue": 12, "metadata": { - "description": "Required. Azure Function App Stream Text System Prompt" + "description": "Optional. The number of replicas in the search service. If specified, it must be a value between 1 and 12 inclusive for standard SKUs or between 1 and 3 inclusive for basic SKU." } }, - "aiFoundryProjectEndpoint": { - "type": "string", + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, "metadata": { - "description": "Required. AI Foundry project endpoint URL." + "description": "Optional. Array of role assignments to create." } }, - "useAIProjectClientFlag": { + "semanticSearch": { "type": "string", - "defaultValue": "false", + "nullable": true, + "allowedValues": [ + "disabled", + "free", + "standard" + ], "metadata": { - "description": "Optional. Flag to enable AI project client." + "description": "Optional. Sets options that control the availability of semantic search. This configuration is only possible for certain search SKUs in certain locations." } }, - "aiFoundryName": { + "sku": { "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "basic", + "free", + "standard", + "standard2", + "standard3", + "storage_optimized_l1", + "storage_optimized_l2" + ], "metadata": { - "description": "Required. Name of the AI Foundry project." + "description": "Optional. Defines the SKU of an Azure Cognitive Search Service, which determines price tier and capacity limits." } }, - "applicationInsightsConnectionString": { - "type": "string", + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, "metadata": { - "description": "Required. Application Insights connection string." + "description": "Optional. The managed identity definition for this resource." } }, - "aiSearchProjectConnectionName": { - "type": "string", + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, "metadata": { - "description": "Required. Connection name for Azure Cognitive Search." + "description": "Optional. The diagnostic settings of the service." } }, "tags": { "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to be applied to the resources." - } - }, - "azureExistingAIProjectResourceId": { - "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Resource ID of the existing AI Foundry project." - } + "__bicep_resource_derived_type!": { + "source": "Microsoft.Search/searchServices@2025-02-01-preview#properties/tags" + }, + "description": "Optional. Tags to help categorize the resource in the Azure portal." + }, + "nullable": true } }, "variables": { - "webAppImageName": "[format('DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:{0}', parameters('imageTag'))]", - "existingAIServiceSubscription": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[2], subscription().subscriptionId)]", - "existingAIServiceResourceGroup": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[4], resourceGroup().name)]", - "existingAIServicesName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[8], '')]", - "existingAIProjectName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), split(parameters('azureExistingAIProjectResourceId'), '/')[10], '')]" + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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', '')), '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')]", + "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')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } }, - "resources": [ - { - "type": "Microsoft.Web/serverfarms", - "apiVersion": "2020-06-01", - "name": "[parameters('hostingPlanName')]", - "location": "[parameters('solutionLocation')]", + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.search-searchservice.{0}.{1}', replace('0.11.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" + } + } + } + } + }, + "searchService": { + "type": "Microsoft.Search/searchServices", + "apiVersion": "2025-02-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", "sku": { - "name": "[parameters('hostingPlanSku')]" + "name": "[parameters('sku')]" + }, + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "authOptions": "[parameters('authOptions')]", + "disableLocalAuth": "[parameters('disableLocalAuth')]", + "encryptionWithCmk": { + "enforcement": "[parameters('cmkEnforcement')]" + }, + "hostingMode": "[parameters('hostingMode')]", + "networkRuleSet": "[parameters('networkRuleSet')]", + "partitionCount": "[parameters('partitionCount')]", + "replicaCount": "[parameters('replicaCount')]", + "publicNetworkAccess": "[toLower(parameters('publicNetworkAccess'))]", + "semanticSearch": "[parameters('semanticSearch')]" + } + }, + "searchService_diagnosticSettings": { + "copy": { + "name": "searchService_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", "properties": { - "name": "[parameters('hostingPlanName')]", - "reserved": true + "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')]" }, - "kind": "linux", - "tags": "[parameters('tags')]" + "dependsOn": [ + "searchService" + ] }, - { - "type": "Microsoft.Web/sites", - "apiVersion": "2020-06-01", - "name": "[parameters('websiteName')]", - "location": "[parameters('solutionLocation')]", - "identity": { - "type": "SystemAssigned, UserAssigned", - "userAssignedIdentities": { - "[format('{0}', parameters('userassignedIdentityId'))]": {} - } + "searchService_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.Search/searchServices/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "searchService" + ] + }, + "searchService_roleAssignments": { + "copy": { + "name": "searchService_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Search/searchServices', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { - "serverFarmId": "[parameters('hostingPlanName')]", - "siteConfig": { - "appSettings": [ - { - "name": "APP_ENV", - "value": "[parameters('appEnvironment')]" - }, - { - "name": "APPINSIGHTS_INSTRUMENTATIONKEY", - "value": "[reference(parameters('applicationInsightsId'), '2015-05-01').InstrumentationKey]" - }, - { - "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", - "value": "[parameters('applicationInsightsConnectionString')]" - }, - { - "name": "AZURE_SEARCH_SERVICE", - "value": "[parameters('azureSearchService')]" - }, - { - "name": "AZURE_SEARCH_INDEX", - "value": "[parameters('azureSearchIndex')]" - }, - { - "name": "AZURE_SEARCH_USE_SEMANTIC_SEARCH", - "value": "[parameters('azureSearchUseSemanticSearch')]" - }, - { - "name": "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG", - "value": "[parameters('azureSearchSemanticSearchConfig')]" - }, - { - "name": "AZURE_SEARCH_TOP_K", - "value": "[parameters('azureSearchTopK')]" - }, - { - "name": "AZURE_SEARCH_ENABLE_IN_DOMAIN", - "value": "[parameters('azureSearchEnableInDomain')]" - }, - { - "name": "AZURE_SEARCH_CONTENT_COLUMNS", - "value": "[parameters('azureSearchContentColumns')]" - }, - { - "name": "AZURE_SEARCH_FILENAME_COLUMN", - "value": "[parameters('azureSearchFilenameColumn')]" - }, - { - "name": "AZURE_SEARCH_TITLE_COLUMN", - "value": "[parameters('azureSearchTitleColumn')]" - }, - { - "name": "AZURE_SEARCH_URL_COLUMN", - "value": "[parameters('azureSearchUrlColumn')]" - }, - { - "name": "AZURE_OPENAI_RESOURCE", - "value": "[parameters('azureOpenAIResource')]" - }, - { - "name": "AZURE_OPENAI_MODEL", - "value": "[parameters('azureOpenAIModel')]" - }, - { - "name": "AZURE_OPENAI_ENDPOINT", - "value": "[parameters('azureOpenAIEndpoint')]" + "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": [ + "searchService" + ] + }, + "searchService_privateEndpoints": { + "copy": { + "name": "searchService_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-searchService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "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.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Search/searchServices', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Search/searchServices', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'searchService')), '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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "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.34.44.8038", + "templateHash": "12389807800450456797" }, - { - "name": "AZURE_OPENAI_TEMPERATURE", - "value": "[parameters('azureOpenAITemperature')]" + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } }, - { - "name": "AZURE_OPENAI_TOP_P", - "value": "[parameters('azureOpenAITopP')]" + "ipConfigurationType": { + "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. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } }, - { - "name": "AZURE_OPENAI_MAX_TOKENS", - "value": "[parameters('azureOpenAIMaxTokens')]" + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } }, - { - "name": "AZURE_OPENAI_STOP_SEQUENCE", - "value": "[parameters('azureOpenAIStopSequence')]" + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": true + } }, - { - "name": "AZURE_OPENAI_SYSTEM_MESSAGE", - "value": "[parameters('azureOpenAISystemMessage')]" + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } }, - { - "name": "AZURE_OPENAI_PREVIEW_API_VERSION", - "value": "[parameters('azureOpenAIApiVersion')]" + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } }, - { - "name": "AZURE_OPENAI_STREAM", - "value": "[parameters('azureOpenAIStream')]" + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } }, - { - "name": "AZURE_SEARCH_QUERY_TYPE", - "value": "[parameters('azureSearchQueryType')]" + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } }, - { - "name": "AZURE_SEARCH_VECTOR_COLUMNS", - "value": "[parameters('azureSearchVectorFields')]" + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } }, - { - "name": "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN", - "value": "[parameters('azureSearchPermittedGroupsField')]" + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } }, - { - "name": "AZURE_SEARCH_STRICTNESS", - "value": "[parameters('azureSearchStrictness')]" + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "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." + } }, - { - "name": "AZURE_OPENAI_EMBEDDING_NAME", - "value": "[parameters('azureOpenAIEmbeddingName')]" + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } }, - { - "name": "AZURE_OPENAI_EMBEDDING_ENDPOINT", - "value": "[parameters('azureOpenAIEmbeddingEndpoint')]" + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } }, - { - "name": "SQLDB_SERVER", - "value": "[parameters('SQLDB_SERVER')]" + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } }, - { - "name": "SQLDB_DATABASE", - "value": "[parameters('SQLDB_DATABASE')]" + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } }, - { - "name": "USE_INTERNAL_STREAM", - "value": "[parameters('USE_INTERNAL_STREAM')]" + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } }, - { - "name": "AZURE_COSMOSDB_ACCOUNT", - "value": "[parameters('AZURE_COSMOSDB_ACCOUNT')]" + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } }, - { - "name": "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER", - "value": "[parameters('AZURE_COSMOSDB_CONVERSATIONS_CONTAINER')]" + "manualPrivateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. 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. Required if `privateLinkServiceConnections` is empty." + } }, - { - "name": "AZURE_COSMOSDB_DATABASE", - "value": "[parameters('AZURE_COSMOSDB_DATABASE')]" + "privateLinkServiceConnections": { + "type": "array", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + } }, - { - "name": "AZURE_COSMOSDB_ENABLE_FEEDBACK", - "value": "[parameters('AZURE_COSMOSDB_ENABLE_FEEDBACK')]" + "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')]", + "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": "[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-privateendpoint.{0}.{1}', replace('0.11.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" + } + } + } + } }, - { - "name": "SQLDB_USER_MID", - "value": "[parameters('userassignedIdentityClientId')]" + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-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')]" + } + } }, - { - "name": "AZURE_AI_SEARCH_ENDPOINT", - "value": "[parameters('azureSearchServiceEndpoint')]" + "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" + ] }, - { - "name": "AZURE_SQL_SYSTEM_PROMPT", - "value": "[parameters('sqlSystemPrompt')]" + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', 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": [ + "privateEndpoint" + ] }, - { - "name": "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT", - "value": "[parameters('callTranscriptSystemPrompt')]" + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "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": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "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.34.44.8038", + "templateHash": "13997305779829540948" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. 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": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + } + } + }, + "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]" }, - { - "name": "AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT", - "value": "[parameters('streamTextSystemPrompt')]" + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" }, - { - "name": "USE_AI_PROJECT_CLIENT", - "value": "[parameters('useAIProjectClientFlag')]" + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" }, - { - "name": "AZURE_AI_AGENT_ENDPOINT", - "value": "[parameters('aiFoundryProjectEndpoint')]" + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]" }, - { - "name": "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME", - "value": "[parameters('azureOpenAIModel')]" + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" }, - { - "name": "AZURE_AI_AGENT_API_VERSION", - "value": "[parameters('azureOpenAIApiVersion')]" + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" }, - { - "name": "AZURE_SEARCH_CONNECTION_NAME", - "value": "[parameters('aiSearchProjectConnectionName')]" + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" } - ], - "linuxFxVersion": "[variables('webAppImageName')]" + } } }, - "tags": "[parameters('tags')]", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]" + "searchService" ] }, - { - "condition": "[empty(parameters('azureExistingAIProjectResourceId'))]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceGroup().id, extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]", - "principalType": "ServicePrincipal" + "searchService_sharedPrivateLinkResources": { + "copy": { + "name": "searchService_sharedPrivateLinkResources", + "count": "[length(parameters('sharedPrivateLinkResources'))]", + "mode": "serial", + "batchSize": 1 }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" - ] - }, - { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('cosmos-sql-user-role-{0}', parameters('websiteName'))]", + "name": "[format('{0}-searchService-SharedPrvLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "accountName": { - "value": "[parameters('AZURE_COSMOSDB_ACCOUNT')]" + "name": { + "value": "[coalesce(tryGet(parameters('sharedPrivateLinkResources')[copyIndex()], 'name'), format('spl-{0}-{1}-{2}', last(split(resourceId('Microsoft.Search/searchServices', parameters('name')), '/')), parameters('sharedPrivateLinkResources')[copyIndex()].groupId, copyIndex()))]" }, - "roleDefinitionId": { - "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', split(format('{0}/00000000-0000-0000-0000-000000000002', parameters('AZURE_COSMOSDB_ACCOUNT')), '/')[0], split(format('{0}/00000000-0000-0000-0000-000000000002', parameters('AZURE_COSMOSDB_ACCOUNT')), '/')[1])]" + "searchServiceName": { + "value": "[parameters('name')]" }, - "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]" + "privateLinkResourceId": { + "value": "[parameters('sharedPrivateLinkResources')[copyIndex()].privateLinkResourceId]" + }, + "groupId": { + "value": "[parameters('sharedPrivateLinkResources')[copyIndex()].groupId]" + }, + "requestMessage": { + "value": "[parameters('sharedPrivateLinkResources')[copyIndex()].requestMessage]" + }, + "resourceRegion": { + "value": "[tryGet(parameters('sharedPrivateLinkResources')[copyIndex()], 'resourceRegion')]" } }, "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.37.4.10188", - "templateHash": "13822905633352095375" + "templateHash": "557730297583881254" }, - "description": "Creates a SQL role assignment under an Azure Cosmos DB account." + "name": "Search Services Private Link Resources", + "description": "This module deploys a Search Service Private Link Resource." }, "parameters": { - "accountName": { + "searchServiceName": { "type": "string", "metadata": { - "description": "Required. Name of the Azure Cosmos DB account." + "description": "Conditional. The name of the parent searchServices. Required if the template is used in a standalone deployment." } }, - "roleDefinitionId": { + "name": { "type": "string", "metadata": { - "description": "Required. ID of the Cosmos DB SQL role definition." + "description": "Required. The name of the shared private link resource managed by the Azure Cognitive Search service within the specified resource group." } }, - "principalId": { + "privateLinkResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Otional. Principal ID to assign the role to." + "description": "Required. The resource ID of the resource the shared private link resource is for." + } + }, + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The group ID from the provider of resource the shared private link resource is for." + } + }, + "requestMessage": { + "type": "string", + "metadata": { + "description": "Required. The request message for requesting approval of the shared private link resource." + } + }, + "resourceRegion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Can be used to specify the Azure Resource Manager location of the resource to which a shared private link is to be created. This is only required for those resources whose DNS configuration are regional (such as Azure Kubernetes Service)." } } }, - "resources": [ - { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", - "apiVersion": "2022-05-15", - "name": "[format('{0}/{1}', parameters('accountName'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('accountName'))))]", + "resources": { + "searchService": { + "existing": true, + "type": "Microsoft.Search/searchServices", + "apiVersion": "2025-02-01-preview", + "name": "[parameters('searchServiceName')]" + }, + "sharedPrivateLinkResource": { + "type": "Microsoft.Search/searchServices/sharedPrivateLinkResources", + "apiVersion": "2025-02-01-preview", + "name": "[format('{0}/{1}', parameters('searchServiceName'), parameters('name'))]", "properties": { - "principalId": "[parameters('principalId')]", - "roleDefinitionId": "[parameters('roleDefinitionId')]", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('accountName'))]" + "privateLinkResourceId": "[parameters('privateLinkResourceId')]", + "groupId": "[parameters('groupId')]", + "requestMessage": "[parameters('requestMessage')]", + "resourceRegion": "[parameters('resourceRegion')]" } } - ] + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the shared private link resource." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the shared private link resource." + }, + "value": "[resourceId('Microsoft.Search/searchServices/sharedPrivateLinkResources', parameters('searchServiceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the shared private link resource was created in." + }, + "value": "[resourceGroup().name]" + } + } } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" + "searchService" ] }, - { - "condition": "[not(empty(parameters('azureExistingAIProjectResourceId')))]", + "secretsExport": { + "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "assignAiUserRoleToAiProjectExisting", - "subscriptionId": "[variables('existingAIServiceSubscription')]", - "resourceGroup": "[variables('existingAIServiceResourceGroup')]", + "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', parameters('websiteName')), '2020-06-01', 'full').identity.principalId]" - }, - "roleDefinitionId": { - "value": "[extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]" - }, - "roleAssignmentName": { - "value": "[guid(parameters('websiteName'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), extensionResourceId(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingAIServiceSubscription'), variables('existingAIServiceResourceGroup')), 'Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), 'Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]" + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" }, - "aiFoundryName": "[if(not(empty(parameters('azureExistingAIProjectResourceId'))), createObject('value', variables('existingAIServicesName')), createObject('value', parameters('aiFoundryName')))]", - "aiProjectName": { - "value": "[variables('existingAIProjectName')]" - }, - "tags": { - "value": "[parameters('tags')]" + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-02-01-preview').primaryKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-02-01-preview').secondaryKey)), 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.37.4.10188", - "templateHash": "14256377996349985323" + "templateHash": "7634110751636246703" } }, - "parameters": { - "principalId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Principal ID to assign the role to." - } - }, - "roleDefinitionId": { - "type": "string", - "metadata": { - "description": "Required. ID of the role definition to assign." - } - }, - "roleAssignmentName": { - "type": "string", - "defaultValue": "", + "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": { - "description": "Optional. Name of the role assignment." + "__bicep_export!": true } }, - "aiFoundryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the AI Foundry resource." + "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." + } + } } - }, - "aiProjectName": { + } + }, + "parameters": { + "keyVaultName": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Name of the AI project." + "description": "Required. The name of the Key Vault to set the ecrets in." } }, - "aiModelDeployments": { + "secretsToSet": { "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. List of AI model deployments." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, + "items": { + "$ref": "#/definitions/secretToSetType" + }, "metadata": { - "description": "Optional. Tags to be applied to the resources." + "description": "Required. The secrets to set in the Key Vault." } } }, - "resources": [ - { + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "name": "[parameters('keyVaultName')]" + }, + "secrets": { "copy": { - "name": "aiServicesDeployments", - "count": "[length(parameters('aiModelDeployments'))]", - "mode": "serial", - "batchSize": 1 - }, - "condition": "[not(empty(parameters('aiModelDeployments')))]", - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2025-04-01-preview", - "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('aiModelDeployments')[copyIndex()].name)]", - "properties": { - "model": { - "format": "OpenAI", - "name": "[parameters('aiModelDeployments')[copyIndex()].model]" - }, - "raiPolicyName": "[parameters('aiModelDeployments')[copyIndex()].raiPolicyName]" + "name": "secrets", + "count": "[length(parameters('secretsToSet'))]" }, - "sku": { - "name": "[parameters('aiModelDeployments')[copyIndex()].sku.name]", - "capacity": "[parameters('aiModelDeployments')[copyIndex()].sku.capacity]" - }, - "tags": "[parameters('tags')]" - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('aiFoundryName'))]", - "name": "[parameters('roleAssignmentName')]", + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2024-11-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]", "properties": { - "roleDefinitionId": "[parameters('roleDefinitionId')]", - "principalId": "[parameters('principalId')]", - "principalType": "ServicePrincipal" + "value": "[parameters('secretsToSet')[copyIndex()].value]" } } - ], + }, "outputs": { - "aiServicesPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the AI Services resource." + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetType" }, - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName')), '2025-04-01-preview', 'full').identity.principalId]" - }, - "aiProjectPrincipalId": { - "type": "string", "metadata": { - "description": "Principal ID of the AI Project resource if defined." + "description": "The references to the secrets exported to the provided Key Vault." }, - "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" + "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": [ - "[resourceId('Microsoft.Web/sites', parameters('websiteName'))]" + "searchService" ] } - ], + }, "outputs": { - "webAppUrl": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the search service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the search service." + }, + "value": "[resourceId('Microsoft.Search/searchServices', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the search service was created in." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('searchService', '2025-02-01-preview', 'full'), 'identity'), 'principalId')]" + }, + "location": { "type": "string", "metadata": { - "description": "URL of the deployed web application." + "description": "The location the resource was deployed into." }, - "value": "[format('https://{0}.azurewebsites.net', parameters('websiteName'))]" + "value": "[reference('searchService', '2025-02-01-preview', 'full').location]" }, - "webAppName": { + "endpoint": { "type": "string", "metadata": { - "description": "Name of the deployed web application." + "description": "The endpoint of the search service." + }, + "value": "[reference('searchService').endpoint]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the search service." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('searchService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "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())]" + }, + "primaryKey": { + "type": "securestring", + "metadata": { + "description": "The primary admin API key of the search service." }, - "value": "[parameters('websiteName')]" + "value": "[listAdminKeys('searchService', '2025-02-01-preview').primaryKey]" + }, + "secondaryKey": { + "type": "securestring", + "metadata": { + "description": "The secondaryKey admin API key of the search service." + }, + "value": "[listAdminKeys('searchService', '2025-02-01-preview').secondaryKey]" } } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry')]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db')]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]", - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db')]" + "logAnalyticsWorkspace" ] } - ], + }, "outputs": { "WEB_APP_URL": { "type": "string", "metadata": { "description": "URL of the deployed web application." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppUrl.value]" + "value": "[format('https://{0}.azurewebsites.net', reference('webSite').outputs.name.value)]" }, "STORAGE_ACCOUNT_NAME": { "type": "string", "metadata": { "description": "Name of the storage account." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_storage_account'), '2022-09-01').outputs.storageName.value]" + "value": "[reference('avmStorageAccount').outputs.name.value]" }, "STORAGE_CONTAINER_NAME": { "type": "string", "metadata": { "description": "Name of the storage container." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_storage_account'), '2022-09-01').outputs.storageContainer.value]" + "value": "data" }, "KEY_VAULT_NAME": { "type": "string", "metadata": { "description": "Name of the Key Vault." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]" + "value": "[reference('keyvault').outputs.name.value]" }, "COSMOSDB_ACCOUNT_NAME": { "type": "string", "metadata": { "description": "Name of the Cosmos DB account." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" + "value": "[reference('cosmosDb').outputs.name.value]" }, "RESOURCE_GROUP_NAME": { "type": "string", @@ -3119,49 +57092,49 @@ "metadata": { "description": "The resource ID of the AI Foundry instance." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryId.value]" + "value": "[reference('aiFoundryAiServices').outputs.resourceId.value]" }, "SQLDB_SERVER_NAME": { "type": "string", "metadata": { "description": "Name of the SQL Database server." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value]" + "value": "[reference('sqlDBModule').outputs.name.value]" }, "SQLDB_DATABASE": { "type": "string", "metadata": { "description": "Name of the SQL Database." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlDbName.value]" + "value": "[variables('sqlDbName')]" }, "MANAGEDIDENTITY_WEBAPP_NAME": { "type": "string", "metadata": { "description": "Name of the managed identity used by the web app." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.name]" + "value": "[reference('userAssignedIdentity').outputs.name.value]" }, "MANAGEDIDENTITY_WEBAPP_CLIENTID": { "type": "string", "metadata": { "description": "Client ID of the managed identity used by the web app." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" + "value": "[reference('userAssignedIdentity').outputs.clientId.value]" }, "AI_SEARCH_SERVICE_NAME": { "type": "string", "metadata": { "description": "Name of the AI Search service." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" + "value": "[variables('aiSearchName')]" }, "WEB_APP_NAME": { "type": "string", "metadata": { "description": "Name of the deployed web application." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_app_service'), '2022-09-01').outputs.webAppName.value]" + "value": "[reference('webSite').outputs.name.value]" }, "APP_ENV": { "type": "string", @@ -3175,14 +57148,14 @@ "metadata": { "description": "The Application Insights instrumentation key." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.instrumentationKey.value]" + "value": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.instrumentationKey.value, '')]" }, "APPLICATIONINSIGHTS_CONNECTION_STRING": { "type": "string", "metadata": { "description": "The Application Insights connection string." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsConnectionString.value]" + "value": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.connectionString.value, '')]" }, "AZURE_AI_AGENT_API_VERSION": { "type": "string", @@ -3196,7 +57169,7 @@ "metadata": { "description": "The endpoint URL of the Azure AI Agent project." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryProjectEndpoint.value]" + "value": "[reference('aiFoundryAiServices').outputs.aiProjectInfo.value.apiEndpoint]" }, "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": { "type": "string", @@ -3210,7 +57183,7 @@ "metadata": { "description": "The endpoint URL of the Azure AI Search service." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchTarget.value]" + "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" }, "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT": { "type": "string", @@ -3224,21 +57197,21 @@ "metadata": { "description": "The name of the Azure Cosmos DB account." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosAccountName.value]" + "value": "[reference('cosmosDb').outputs.name.value]" }, "AZURE_COSMOSDB_CONVERSATIONS_CONTAINER": { "type": "string", "metadata": { "description": "The name of the Azure Cosmos DB container for storing conversations." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosContainerName.value]" + "value": "[variables('collectionName')]" }, "AZURE_COSMOSDB_DATABASE": { "type": "string", "metadata": { "description": "The name of the Azure Cosmos DB database." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_cosmos_db'), '2022-09-01').outputs.cosmosDatabaseName.value]" + "value": "[variables('cosmosDbDatabaseName')]" }, "AZURE_COSMOSDB_ENABLE_FEEDBACK": { "type": "string", @@ -3252,7 +57225,7 @@ "metadata": { "description": "The endpoint URL for the Azure OpenAI Embedding model." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" + "value": "[reference('aiFoundryAiServices').outputs.aiProjectInfo.value.apiEndpoint]" }, "AZURE_OPENAI_EMBEDDING_NAME": { "type": "string", @@ -3266,7 +57239,7 @@ "metadata": { "description": "The endpoint URL for the Azure OpenAI service." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aoaiEndpoint.value]" + "value": "[reference('aiFoundryAiServices').outputs.endpoint.value]" }, "AZURE_OPENAI_MAX_TOKENS": { "type": "string", @@ -3294,7 +57267,7 @@ "metadata": { "description": "The Azure OpenAI resource name." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiFoundryName.value]" + "value": "[reference('aiFoundryAiServices').outputs.name.value]" }, "AZURE_OPENAI_STOP_SEQUENCE": { "type": "string", @@ -3343,7 +57316,7 @@ "metadata": { "description": "The name of the Azure AI Search connection." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchFoundryConnectionName.value]" + "value": "[format('foundry-search-connection-{0}', variables('solutionSuffix'))]" }, "AZURE_SEARCH_CONTENT_COLUMNS": { "type": "string", @@ -3399,7 +57372,7 @@ "metadata": { "description": "The name of the Azure AI Search service." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.aiSearchService.value]" + "value": "[variables('aiSearchName')]" }, "AZURE_SEARCH_STRICTNESS": { "type": "string", @@ -3455,14 +57428,14 @@ "metadata": { "description": "The fully qualified domain name (FQDN) of the Azure SQL Server." }, - "value": "[format('{0}.database.windows.net', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_sql_db'), '2022-09-01').outputs.sqlServerName.value)]" + "value": "[variables('sqlServerFqdn')]" }, "SQLDB_USER_MID": { "type": "string", "metadata": { "description": "The client ID of the managed identity for the web application." }, - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityWebAppOutput.value.clientId]" + "value": "[reference('userAssignedIdentity').outputs.clientId.value]" }, "USE_AI_PROJECT_CLIENT": { "type": "string", diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json new file mode 100644 index 000000000..06d26fb49 --- /dev/null +++ b/infra/main.waf.parameters.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "solutionName": { + "value": "${AZURE_ENV_NAME}" + }, + "cosmosLocation": { + "value": "${AZURE_ENV_COSMOS_LOCATION}" + }, + "deploymentType": { + "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" + }, + "gptModelName": { + "value": "${AZURE_ENV_MODEL_NAME}" + }, + "azureOpenaiAPIVersion": { + "value": "${AZURE_ENV_MODEL_VERSION}" + }, + "gptDeploymentCapacity": { + "value": "${AZURE_ENV_MODEL_CAPACITY}" + }, + "embeddingModel": { + "value": "${AZURE_ENV_EMBEDDING_MODEL_NAME}" + }, + "embeddingDeploymentCapacity": { + "value": "${AZURE_ENV_EMBEDDING_MODEL_CAPACITY}" + }, + "imageTag": { + "value": "${AZURE_ENV_IMAGETAG}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID}" + }, + "azureExistingAIProjectResourceId": { + "value": "${AZURE_EXISTING_AI_PROJECT_RESOURCE_ID}" + }, + "tags": { + "value": { + "solutionName": "${AZURE_ENV_NAME}" + } + }, + "enableTelemetry" :{ + "value": "${AZURE_ENV_ENABLE_TELEMETRY}" + }, + "enableMonitoring": { + "value": true + }, + "enablePrivateNetworking": { + "value": true + }, + "enableScalability": { + "value": true + } + } +} \ No newline at end of file diff --git a/infra/modules/network.bicep b/infra/modules/network.bicep index 55a161fbe..7a69101fd 100644 --- a/infra/modules/network.bicep +++ b/infra/modules/network.bicep @@ -121,7 +121,7 @@ module network 'network/main.bicep' = { } ] } - delegation: 'Microsoft.App/environments' + delegation: 'Microsoft.Web/serverFarms' } { name: 'peps' diff --git a/src/App/app.py b/src/App/app.py index 515eb1342..9a9ea2982 100644 --- a/src/App/app.py +++ b/src/App/app.py @@ -147,7 +147,6 @@ async def assets(path): } # Enable Microsoft Defender for Cloud Integration MS_DEFENDER_ENABLED = os.environ.get("MS_DEFENDER_ENABLED", "false").lower() == "true" - # VITE_POWERBI_EMBED_URL = os.environ.get("VITE_POWERBI_EMBED_URL") @@ -195,7 +194,7 @@ def init_openai_client(use_data=SHOULD_USE_DATA): if not aoai_api_key: logging.debug("No AZURE_OPENAI_KEY found, using Azure AD auth") ad_token_provider = get_bearer_token_provider( - get_azure_credential(), "https://cognitiveservices.azure.com/.default" + get_azure_credential(config.MID_ID), "https://cognitiveservices.azure.com/.default" ) # Deployment @@ -243,7 +242,7 @@ def init_cosmosdb_client(): ) if not config.AZURE_COSMOSDB_ACCOUNT_KEY: - credential = get_azure_credential() + credential = get_azure_credential(config.MID_ID) else: credential = config.AZURE_COSMOSDB_ACCOUNT_KEY diff --git a/src/App/backend/agents/agent_factory.py b/src/App/backend/agents/agent_factory.py index d1f3956b8..faacd7c32 100644 --- a/src/App/backend/agents/agent_factory.py +++ b/src/App/backend/agents/agent_factory.py @@ -37,7 +37,7 @@ async def get_wealth_advisor_agent(cls): async with cls._lock: if cls._wealth_advisor_agent is None: ai_agent_settings = AzureAIAgentSettings() - creds = await get_azure_credential_async() + creds = await get_azure_credential_async(config.MID_ID) client = AzureAIAgent.create_client( credential=creds, endpoint=ai_agent_settings.endpoint ) @@ -78,7 +78,7 @@ async def get_search_agent(cls): project_client = AIProjectClient( endpoint=config.AI_PROJECT_ENDPOINT, - credential=get_azure_credential(), + credential=get_azure_credential(config.MID_ID), api_version="2025-05-01", ) @@ -188,7 +188,7 @@ async def get_sql_agent(cls) -> dict: project_client = AIProjectClient( endpoint=config.AI_PROJECT_ENDPOINT, - credential=get_azure_credential(), + credential=get_azure_credential(config.MID_ID), api_version="2025-05-01", ) diff --git a/src/App/backend/plugins/chat_with_data_plugin.py b/src/App/backend/plugins/chat_with_data_plugin.py index 605ca5d42..b2f5a211f 100644 --- a/src/App/backend/plugins/chat_with_data_plugin.py +++ b/src/App/backend/plugins/chat_with_data_plugin.py @@ -225,7 +225,7 @@ async def get_answers_from_calltranscripts( def get_openai_client(self): token_provider = get_bearer_token_provider( - get_azure_credential(), "https://cognitiveservices.azure.com/.default" + get_azure_credential(config.MID_ID), "https://cognitiveservices.azure.com/.default" ) openai_client = openai.AzureOpenAI( azure_endpoint=config.AZURE_OPENAI_ENDPOINT, @@ -236,7 +236,7 @@ def get_openai_client(self): def get_project_openai_client(self): project = AIProjectClient( - endpoint=config.AI_PROJECT_ENDPOINT, credential=get_azure_credential() + endpoint=config.AI_PROJECT_ENDPOINT, credential=get_azure_credential(config.MID_ID) ) openai_client = project.inference.get_azure_openai_client( api_version=config.AZURE_OPENAI_PREVIEW_API_VERSION From ccc16defdcdfb4d9b8b192c3f471e8ffe77735df Mon Sep 17 00:00:00 2001 From: Prekshith-Microsoft Date: Mon, 1 Sep 2025 11:26:04 +0530 Subject: [PATCH 50/84] The total asset value and current asset value are responding correctly, and the total value matches the asset value displayed in the left panel. (#651) --- infra/main.bicep | 1 + src/App/backend/agents/agent_factory.py | 1 + 2 files changed, 2 insertions(+) diff --git a/infra/main.bicep b/infra/main.bicep index c1c4906e8..cc2941012 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -158,6 +158,7 @@ var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for t ALWAYS select Client Name (Column: Client) in the query. Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed. When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons. + For asset values: if question is about total \"asset value\"/\"portfolio value\"/\"AUM\" → return SUM of latest investments; if about \"current asset/investment value\" → return all latest investments without SUM. Only return the generated SQL query. Do not return anything else.''' var functionAppCallTranscriptSystemPrompt = '''You are an assistant who supports wealth advisors in preparing for client meetings. diff --git a/src/App/backend/agents/agent_factory.py b/src/App/backend/agents/agent_factory.py index d1f3956b8..0e4ac3467 100644 --- a/src/App/backend/agents/agent_factory.py +++ b/src/App/backend/agents/agent_factory.py @@ -183,6 +183,7 @@ async def get_sql_agent(cls) -> dict: - Do not use client name for filtering - Assets table contains snapshots by date; do not sum values across dates - Use StartTime for time-based filtering (meetings) + - For asset values: if question is about total "asset value"/"portfolio value"/"AUM" → return SUM of latest investments; if about "current asset/investment value" → return all latest investments without SUM. - Only return the raw T-SQL query. No explanations or comments. """ From 1565d51cd6ab2b431607fecca14d8f15385b69b2 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 2 Sep 2025 06:41:18 +0000 Subject: [PATCH 51/84] fix: Update OpenAI endpoint reference and improve resource naming conventions in Bicep modules --- infra/main.bicep | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 5713ad3ec..77bc9e2ae 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -543,7 +543,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { } { name: 'AZURE-OPENAI-ENDPOINT' - value: aiFoundryAiServices.outputs.endpoint + value: aiFoundryAiServices.outputs.endpoints['OpenAI Language Model Instance API'] } { name: 'AZURE-OPENAI-EMBEDDING-MODEL' @@ -965,7 +965,7 @@ module saveStorageAccountSecretsInKeyVault 'br/public:avm/res/key-vault/vault:0. // ========== SQL module ========== // var sqlDbName = 'sqldb-${solutionSuffix}' module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { - name: 'serverDeployment' + name: take('avm.res.sql.server.${sqlDbName}', 64) params: { // Required parameters name: 'sql-${solutionSuffix}' @@ -1301,7 +1301,7 @@ module webSite 'modules/web-sites.bicep' = { var aiSearchName = 'srch-${solutionName}' module searchService 'br/public:avm/res/search/search-service:0.11.1' = { - name: 'searchServiceDeployment' + name: take('avm.res.search.search-service.${aiSearchName}', 64) params: { // Required parameters name: aiSearchName From a0f39442d5f379816c27f91cb595f345e4e33146 Mon Sep 17 00:00:00 2001 From: NirajC-Microsoft Date: Wed, 3 Sep 2025 15:18:17 +0530 Subject: [PATCH 52/84] Bicep file changes to add CreatedBy tag (#655) --- infra/main.bicep | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infra/main.bicep b/infra/main.bicep index cc2941012..c9a2df6c7 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -177,6 +177,7 @@ param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' +var deployerInfo = deployer() // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -184,6 +185,7 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { tags: { ...tags TemplateName: 'Client Advisor' + CreatedBy: split(deployerInfo.userPrincipalName, '@')[0] } } } From 18c2cd2fe1cb57cca5420cd5d9b351e87060fa7c Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Wed, 3 Sep 2025 15:49:22 +0000 Subject: [PATCH 53/84] Add role assignments for Search Service to AI Services in Bicep modules --- infra/main.bicep | 14 +++++++++++++- infra/modules/project.bicep | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 77bc9e2ae..89b290280 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1268,7 +1268,7 @@ module webSite 'modules/web-sites.bicep' = { AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME: gptModelName AZURE_AI_AGENT_API_VERSION: azureOpenaiAPIVersion // AZURE_SEARCH_CONNECTION_NAME: '' //aiSearchProjectConnectionName - AZURE_SEARCH_CONNECTION_NAME: 'foundry-search-connection-${solutionName}' + AZURE_SEARCH_CONNECTION_NAME: aiSearchName AZURE_CLIENT_ID: userAssignedIdentity.outputs.clientId } // WAF aligned configuration for Monitoring @@ -1385,6 +1385,18 @@ resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/project } } +// ========== Search Service to AI Services Role Assignment ========== // +resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(aiSearchName, '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd', aiFoundryAiServicesResourceName) + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') // Cognitive Services OpenAI User + principalId: searchService.outputs.systemAssignedMIPrincipalId! + principalType: 'ServicePrincipal' + } +} + + + @description('URL of the deployed web application.') output WEB_APP_URL string = 'https://${webSite.outputs.name}.azurewebsites.net' diff --git a/infra/modules/project.bicep b/infra/modules/project.bicep index f2964c875..06f9be187 100644 --- a/infra/modules/project.bicep +++ b/infra/modules/project.bicep @@ -41,6 +41,36 @@ resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' = } } +@description('This is the built-in Search Index Data Reader role.') +resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' +} + +// ========== Search Service to AI Services Role Assignment ========== // +resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(aiProject.id, '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') + properties: { + roleDefinitionId: searchIndexDataReaderRoleDefinition.id + principalId: aiProject.identity.principalId + principalType: 'ServicePrincipal' + } +} + +@description('This is the built-in Search Service Contributor role.') +resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { + name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' +} + +resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(aiProject.id, searchServiceContributorRoleDefinition.id) + properties: { + roleDefinitionId: searchServiceContributorRoleDefinition.id + principalId: aiProject.identity.principalId + principalType: 'ServicePrincipal' + } +} + + @description('AI Project metadata including name, resource ID, and API endpoint.') output aiProjectInfo aiProjectOutputType = { name: useExistingProject ? existingProjName : aiProject.name From c900215ebcfc65aae75088651780865618570dd8 Mon Sep 17 00:00:00 2001 From: NirajC-Microsoft Date: Thu, 4 Sep 2025 11:07:40 +0530 Subject: [PATCH 54/84] Update docs/TroubleShootingSteps.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/TroubleShootingSteps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md index 899be78cf..e2858f87c 100644 --- a/docs/TroubleShootingSteps.md +++ b/docs/TroubleShootingSteps.md @@ -309,7 +309,7 @@ The subscription 'xxxx-xxxx' cannot have more than 1 Container App Environments -

ParentResourceNotfound +
ParentResourceNotFound - You can refer to the [Parent Resource Not found](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/error-parent-resource?tabs=bicep) documentation if you encounter this error. From aaf927df7d31b23e6bf58beda34306a6cb65fd3b Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Thu, 4 Sep 2025 07:02:32 +0000 Subject: [PATCH 55/84] fix: Update search service network access settings to enable public access and disable private endpoints --- infra/main.bicep | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 89b290280..1d5ad84e8 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1351,8 +1351,8 @@ module searchService 'br/public:avm/res/search/search-service:0.11.1' = { semanticSearch: 'free' // Use the deployment tags provided to the template tags: tags - publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' - privateEndpoints: enablePrivateNetworking + publicNetworkAccess: 'Enabled' + privateEndpoints: false ? [ { name: 'pep-${aiSearchName}' From 3310a81fda7e794dc35744e94bdc2f9c1e7547b8 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Thu, 4 Sep 2025 17:56:58 +0000 Subject: [PATCH 56/84] Exp changes to Use Existing Log Analytics Workspace ID --- infra/main.bicep | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 1d5ad84e8..b79005b03 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -109,7 +109,7 @@ var solutionSuffix= toLower(trim(replace( param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') -param enableMonitoring bool = false +param enableMonitoring bool = true @description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableScalability bool = false @@ -280,6 +280,12 @@ resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) } +// Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) +// var logAnalyticsWorkspaceName = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.name : logAnalyticsWorkspace!.outputs.name +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace!.outputs.resourceId +// var logAnalyticsPrimarySharedKey = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey +// var logAnalyticsWorkspaceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -295,7 +301,7 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { // WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics // WAF PSRules for Log Analytics: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#azure-monitor-logs var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}' -module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring) { +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.12.0' = if (enableMonitoring && !useExistingLogAnalytics) { name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) params: { name: logAnalyticsWorkspaceResourceName @@ -354,12 +360,6 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 } } -// Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) -// var logAnalyticsWorkspaceName = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.name : logAnalyticsWorkspace!.outputs.name -var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace!.outputs.resourceId -// var logAnalyticsPrimarySharedKey = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey -// var logAnalyticsWorkspaceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId - // ========== Application Insights ========== // // WAF best practices for Application Insights: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/application-insights // WAF PSRules for Application Insights: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#application-insights @@ -412,7 +412,7 @@ module network 'modules/network.bicep' = if (enablePrivateNetworking) { params: { resourcesName: resourcesName // logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId - logAnalyticsWorkSpaceResourceId: useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : resourceId('Microsoft.OperationalInsights/workspaces', logAnalyticsWorkspaceResourceName) + logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspaceResourceId vmAdminUsername: vmAdminUsername ?? 'JumpboxAdminUser' vmAdminPassword: vmAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!' vmSize: vmSize ?? 'Standard_DS2_v2' // Default VM size @@ -503,7 +503,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { enableSoftDelete: true enablePurgeProtection: enablePurgeProtection softDeleteRetentionInDays: 7 - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : [] + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : [] // WAF aligned configuration for Private Networking privateEndpoints: enablePrivateNetworking ? [ @@ -785,7 +785,7 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { } ] // WAF aligned configuration for Monitoring - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null // WAF aligned configuration for Private Networking networkRestrictions: { networkAclBypass: 'None' @@ -995,7 +995,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { } collation: 'SQL_Latin1_General_CP1_CI_AS' diagnosticSettings: enableMonitoring - ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] + ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null elasticPoolResourceId: resourceId('Microsoft.Sql/servers/elasticPools', 'sql-${solutionSuffix}', 'sqlswaf-ep-001') licenseType: 'LicenseIncluded' @@ -1316,7 +1316,7 @@ module searchService 'br/public:avm/res/search/search-service:0.11.1' = { // Wire up diagnostic settings to the Log Analytics workspace when monitoring is enabled diagnosticSettings: enableMonitoring ? [ { - workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId + workspaceResourceId: logAnalyticsWorkspaceResourceId } ] : null disableLocalAuth: false From 81381652b89f9421bfe0e8e39c731fe2fc38d4a0 Mon Sep 17 00:00:00 2001 From: NirajC-Microsoft Date: Fri, 5 Sep 2025 15:05:56 +0530 Subject: [PATCH 57/84] fix: Adding a hardcoded createdBy parameter to the CAdeploy.yml file to prevent validation pipeline failures caused by the deployer function in the Bicep file. (#659) * Adding a hardcoded createdBy parameter to the CAdeploy.yml file to prevent validation pipeline failures caused by the deployer function in the Bicep file. * Consolidate parameters in CAdeploy.yml --- .github/workflows/CAdeploy.yml | 2 +- infra/main.bicep | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index db65714e7..6822facb9 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -144,7 +144,7 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ + --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT" diff --git a/infra/main.bicep b/infra/main.bicep index c9a2df6c7..2b8858531 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -177,7 +177,9 @@ param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' -var deployerInfo = deployer() +@description('Optional created by user name') +param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -185,7 +187,7 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { tags: { ...tags TemplateName: 'Client Advisor' - CreatedBy: split(deployerInfo.userPrincipalName, '@')[0] + CreatedBy: createdBy } } } From 71eb134aaafbb3473fcf4078d1de52672fc95250 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 8 Sep 2025 04:20:45 +0000 Subject: [PATCH 58/84] added AIProject_SearchConnection and Cognitive Services OpenAI User role assignments to the existing AiFoundryProject. --- infra/main.bicep | 58 +++++++++++++------ infra/main.parameters.json | 2 +- .../deploy_aifp_aisearch_connection.bicep | 33 +++++++++++ infra/modules/project.bicep | 7 ++- infra/modules/role-assignment.bicep | 19 ++++++ 5 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 infra/modules/deploy_aifp_aisearch_connection.bicep create mode 100644 infra/modules/role-assignment.bicep diff --git a/infra/main.bicep b/infra/main.bicep index b79005b03..8c024fd3c 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -586,23 +586,23 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { // ========== AI Foundry: AI Services ========== // // WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai -// var aiFoundryAiServicesSubscriptionId = useExistingAiFoundryAiProject -// ? split(azureExistingAIProjectResourceId, '/')[2] -// : subscription().id -// var useExistingAiFoundryAiProject = !empty(azureExistingAIProjectResourceId) -// var aiFoundryAiServicesResourceGroupName = useExistingAiFoundryAiProject -// ? split(azureExistingAIProjectResourceId, '/')[4] -// : 'rg-${solutionSuffix}' -// var aiFoundryAiServicesResourceName = useExistingAiFoundryAiProject -// ? split(azureExistingAIProjectResourceId, '/')[8] -// : 'aif-${solutionSuffix}' -// var aiFoundryAiProjectResourceName = useExistingAiFoundryAiProject -// ? split(azureExistingAIProjectResourceId, '/')[10] -// : 'proj-${solutionSuffix}' +var aiFoundryAiServicesSubscriptionId = useExistingAiFoundryAiProject + ? split(existingFoundryProjectResourceId, '/')[2] + : subscription().id +var useExistingAiFoundryAiProject = !empty(existingFoundryProjectResourceId) +var aiFoundryAiServicesResourceGroupName = useExistingAiFoundryAiProject + ? split(existingFoundryProjectResourceId, '/')[4] + : 'rg-${solutionSuffix}' +var aiFoundryAiServicesResourceName = useExistingAiFoundryAiProject + ? split(existingFoundryProjectResourceId, '/')[8] + : 'aif-${solutionSuffix}' +var aiFoundryAiProjectResourceName = useExistingAiFoundryAiProject + ? split(existingFoundryProjectResourceId, '/')[10] + : 'proj-${solutionSuffix}' // AI Project resource id: /subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts//projects/ // NOTE: Required version 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' not available in AVM -var aiFoundryAiServicesResourceName = 'aif-${solutionSuffix}' +// var aiFoundryAiServicesResourceName = 'aif-${solutionSuffix}' var aiFoundryAiServicesAiProjectResourceName = 'proj-${solutionSuffix}' var aiFoundryAIservicesEnabled = true var aiFoundryAiServicesModelDeployment = { @@ -1370,7 +1370,7 @@ module searchService 'br/public:avm/res/search/search-service:0.11.1' = { } } -resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { +resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (!useExistingAiFoundryAiProject) { name: '${aiFoundryAiServicesResourceName}/${aiFoundryAiServicesAiProjectResourceName}/${aiSearchName}' properties: { category: 'CognitiveSearch' @@ -1385,8 +1385,21 @@ resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/project } } +module existing_AIProject_SearchConnectionModule 'modules/deploy_aifp_aisearch_connection.bicep' = if (useExistingAiFoundryAiProject) { + name: 'aiProjectSearchConnectionDeployment' + scope: resourceGroup(aiFoundryAiServicesSubscriptionId, aiFoundryAiServicesResourceGroupName) + params: { + existingAIProjectName: aiFoundryAiProjectResourceName + existingAIFoundryName: aiFoundryAiServicesResourceName + aiSearchName: aiSearchName + aiSearchResourceId: searchService.outputs.resourceId + aiSearchLocation: searchService.outputs.location + aiSearchConnectionName: aiSearchName + } +} + // ========== Search Service to AI Services Role Assignment ========== // -resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { +resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAiFoundryAiProject) { name: guid(aiSearchName, '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd', aiFoundryAiServicesResourceName) properties: { roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') // Cognitive Services OpenAI User @@ -1395,9 +1408,18 @@ resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAs } } +// Role assignment for existing AI Services scenario +module searchServiceToExistingAiServicesRoleAssignment 'modules/role-assignment.bicep' = if (useExistingAiFoundryAiProject) { + name: 'searchToExistingAiServices-roleAssignment' + scope: resourceGroup(aiFoundryAiServicesSubscriptionId, aiFoundryAiServicesResourceGroupName) + params: { + principalId: searchService.outputs.systemAssignedMIPrincipalId! + roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User + targetResourceName: aiFoundryAiServices.outputs.name + } +} - - +// ========== Outputs ========== // @description('URL of the deployed web application.') output WEB_APP_URL string = 'https://${webSite.outputs.name}.azurewebsites.net' diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 8fe1de47a..2219f100e 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -35,7 +35,7 @@ "existingLogAnalyticsWorkspaceId": { "value": "${AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID}" }, - "azureExistingAIProjectResourceId": { + "existingFoundryProjectResourceId": { "value": "${AZURE_EXISTING_AI_PROJECT_RESOURCE_ID}" }, "tags": { diff --git a/infra/modules/deploy_aifp_aisearch_connection.bicep b/infra/modules/deploy_aifp_aisearch_connection.bicep new file mode 100644 index 000000000..99e66c95b --- /dev/null +++ b/infra/modules/deploy_aifp_aisearch_connection.bicep @@ -0,0 +1,33 @@ +@description('Required. Existing AI Project Name') +param existingAIProjectName string + +@description('Required. Existing AI Foundry Name') +param existingAIFoundryName string + +@description('Required. AI Search Name') +param aiSearchName string + +@description('Required. AI Search Resource ID') +param aiSearchResourceId string + +@description('Required. AI Search Location') +param aiSearchLocation string + +@description('Required. AI Search Connection Name') +param aiSearchConnectionName string + + +resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { + name: '${existingAIFoundryName}/${existingAIProjectName}/${aiSearchConnectionName}' + properties: { + category: 'CognitiveSearch' + target: 'https://${aiSearchName}.search.windows.net' + authType: 'AAD' + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: aiSearchResourceId + location: aiSearchLocation + } + } +} diff --git a/infra/modules/project.bicep b/infra/modules/project.bicep index 06f9be187..16d5858f4 100644 --- a/infra/modules/project.bicep +++ b/infra/modules/project.bicep @@ -41,14 +41,15 @@ resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' = } } + @description('This is the built-in Search Index Data Reader role.') resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' } // ========== Search Service to AI Services Role Assignment ========== // -resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(aiProject.id, '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') +resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if(!useExistingProject) { + name: guid(aiProject.id, searchIndexDataReaderRoleDefinition.id) properties: { roleDefinitionId: searchIndexDataReaderRoleDefinition.id principalId: aiProject.identity.principalId @@ -61,7 +62,7 @@ resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDef name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' } -resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = { +resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingProject) { name: guid(aiProject.id, searchServiceContributorRoleDefinition.id) properties: { roleDefinitionId: searchServiceContributorRoleDefinition.id diff --git a/infra/modules/role-assignment.bicep b/infra/modules/role-assignment.bicep new file mode 100644 index 000000000..e0fd98e8a --- /dev/null +++ b/infra/modules/role-assignment.bicep @@ -0,0 +1,19 @@ +@description('The principal ID to assign the role to') +param principalId string + +@description('The role definition ID to assign') +param roleDefinitionId string + +@description('The name of the target resource') +param targetResourceName string + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(principalId, roleDefinitionId, targetResourceName) + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) + principalId: principalId + principalType: 'ServicePrincipal' + } +} + +output roleAssignmentId string = roleAssignment.id From fee0aa6e48f0b09a7a472684b67ac676d8c80c06 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 8 Sep 2025 07:13:31 +0000 Subject: [PATCH 59/84] Enhance existing AI Foundry project support with resource referencing and role assignments --- infra/main.bicep | 32 +++++++++++++-- infra/modules/project.bicep | 81 ++++++++++++++++++++++++------------- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 8c024fd3c..98941f84a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -585,11 +585,11 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { // ========== AI Foundry: AI Services ========== // // WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai +var useExistingAiFoundryAiProject = !empty(existingFoundryProjectResourceId) var aiFoundryAiServicesSubscriptionId = useExistingAiFoundryAiProject ? split(existingFoundryProjectResourceId, '/')[2] : subscription().id -var useExistingAiFoundryAiProject = !empty(existingFoundryProjectResourceId) var aiFoundryAiServicesResourceGroupName = useExistingAiFoundryAiProject ? split(existingFoundryProjectResourceId, '/')[4] : 'rg-${solutionSuffix}' @@ -748,7 +748,7 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { params: { // Required parameters name: cosmosDbResourceName - location: solutionLocation + location: cosmosLocation tags: tags enableTelemetry: enableTelemetry sqlDatabases: [ @@ -1299,6 +1299,17 @@ module webSite 'modules/web-sites.bicep' = { } } +resource existingAiFoundryAiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = if (useExistingAiFoundryAiProject) { + name: aiFoundryAiServicesResourceName + scope: resourceGroup(aiFoundryAiServicesSubscriptionId, aiFoundryAiServicesResourceGroupName) +} + +resource existingAiFoundryAiServicesProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (useExistingAiFoundryAiProject) { + name: aiFoundryAiProjectResourceName + parent: existingAiFoundryAiServices +} + + var aiSearchName = 'srch-${solutionName}' module searchService 'br/public:avm/res/search/search-service:0.11.1' = { name: take('avm.res.search.search-service.${aiSearchName}', 64) @@ -1340,10 +1351,25 @@ module searchService 'br/public:avm/res/search/search-service:0.11.1' = { // principalType: 'ServicePrincipal' // } { - roleDefinitionIdOrName: 'Search Index Data Contributor' // 1407120a-92aa-4202-b7e9-c0e197c71c8f + roleDefinitionIdOrName: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // Search Index Data Reader principalId: userAssignedIdentity.outputs.principalId principalType: 'ServicePrincipal' } + { + roleDefinitionIdOrName: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Search Service Contributor + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // Search Index Data Reader + principalId: !useExistingAiFoundryAiProject ? aiFoundryAiServices.outputs.aiProjectInfo.aiprojectSystemAssignedMIPrincipalId : existingAiFoundryAiServicesProject!.identity.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Search Service Contributor + principalId: !useExistingAiFoundryAiProject ? aiFoundryAiServices.outputs.aiProjectInfo.aiprojectSystemAssignedMIPrincipalId : existingAiFoundryAiServicesProject!.identity.principalId + principalType: 'ServicePrincipal' + } ] partitionCount: 1 replicaCount: 1 diff --git a/infra/modules/project.bicep b/infra/modules/project.bicep index 16d5858f4..953c56e13 100644 --- a/infra/modules/project.bicep +++ b/infra/modules/project.bicep @@ -19,8 +19,11 @@ param existingFoundryProjectResourceId string = '' // // Extract components from existing AI Project Resource ID if provided var useExistingProject = !empty(existingFoundryProjectResourceId) var existingProjName = useExistingProject ? last(split(existingFoundryProjectResourceId, '/')) : '' -var existingProjEndpoint = useExistingProject ? format('https://{0}.services.ai.azure.com/api/projects/{1}', aiServicesName, existingProjName) : '' - +var existingAiFoundryAiServicesSubscriptionId = useExistingProject ? split(existingFoundryProjectResourceId, '/')[2] : '' +var existingAiFoundryAiServicesResourceGroupName = useExistingProject ? split(existingFoundryProjectResourceId, '/')[4] : '' +var existingAiFoundryAiServicesServiceName = useExistingProject ? split(existingFoundryProjectResourceId, '/')[8] : '' +// Example endpoint (only if existing project provided) +var existingProjEndpoint = useExistingProject ? format('https://{0}.services.ai.azure.com/api/projects/{1}', existingAiFoundryAiServicesServiceName, existingProjName) : '' // Reference to cognitive service in current resource group for new projects resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = { name: aiServicesName @@ -41,35 +44,51 @@ resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' = } } - -@description('This is the built-in Search Index Data Reader role.') -resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' -} - -// ========== Search Service to AI Services Role Assignment ========== // -resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if(!useExistingProject) { - name: guid(aiProject.id, searchIndexDataReaderRoleDefinition.id) - properties: { - roleDefinitionId: searchIndexDataReaderRoleDefinition.id - principalId: aiProject.identity.principalId - principalType: 'ServicePrincipal' - } -} - -@description('This is the built-in Search Service Contributor role.') -resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' +// Reference the existing AI Foundry project if reusing +resource existingAiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' existing = if (useExistingProject){ + name: '${existingAiFoundryAiServicesServiceName}/${existingProjName}' + scope: resourceGroup(existingAiFoundryAiServicesSubscriptionId, existingAiFoundryAiServicesResourceGroupName) } -resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingProject) { - name: guid(aiProject.id, searchServiceContributorRoleDefinition.id) - properties: { - roleDefinitionId: searchServiceContributorRoleDefinition.id - principalId: aiProject.identity.principalId - principalType: 'ServicePrincipal' - } -} +// @description('This is the built-in Search Index Data Reader role.') +// resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { +// name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' +// } + +// // ========== Search Service to AI Services Role Assignment ========== // +// resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if(!useExistingProject) { +// name: guid(aiProject.id, searchIndexDataReaderRoleDefinition.id) +// properties: { +// roleDefinitionId: searchIndexDataReaderRoleDefinition.id +// principalId: aiProject!.identity.principalId +// principalType: 'ServicePrincipal' +// } +// } + +// // Assign Search Index Data Reader role to existing AI Foundry project principal +// resource searchIndexDataReaderRoleAssignmentForExisting 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingProject) { +// name: guid(existingAiProject.id, searchIndexDataReaderRoleDefinition.id) +// scope: resourceGroup() +// properties: { +// roleDefinitionId: searchIndexDataReaderRoleDefinition.id +// principalId: existingAiProject!.identity.principalId +// principalType: 'ServicePrincipal' +// } +// } + +// @description('This is the built-in Search Service Contributor role.') +// resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { +// name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' +// } + +// resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingProject) { +// name: guid(aiProject.id, searchServiceContributorRoleDefinition.id) +// properties: { +// roleDefinitionId: searchServiceContributorRoleDefinition.id +// principalId: aiProject.identity.principalId +// principalType: 'ServicePrincipal' +// } +// } @description('AI Project metadata including name, resource ID, and API endpoint.') @@ -77,6 +96,7 @@ output aiProjectInfo aiProjectOutputType = { name: useExistingProject ? existingProjName : aiProject.name resourceId: useExistingProject ? existingFoundryProjectResourceId : aiProject.id apiEndpoint: useExistingProject ? existingProjEndpoint : aiProject!.properties.endpoints['AI Foundry API'] + aiprojectSystemAssignedMIPrincipalId : useExistingProject ? existingAiProject!.identity.principalId : aiProject!.identity.principalId } @export() @@ -90,4 +110,7 @@ type aiProjectOutputType = { @description('Required. API endpoint for the AI project.') apiEndpoint: string + + @description('Required. System Assigned Managed Identity Principal Id of the AI project.') + aiprojectSystemAssignedMIPrincipalId: string } From 553027d33359f15b1930b6ab244c8487c62d87ca Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 8 Sep 2025 07:40:43 +0000 Subject: [PATCH 60/84] Code Clean up --- infra/main.bicep | 142 ------------------ ...rameters.json => main.parameters.waf.json} | 6 +- infra/modules/project.bicep | 41 ----- 3 files changed, 3 insertions(+), 186 deletions(-) rename infra/{main.waf.parameters.json => main.parameters.waf.json} (92%) diff --git a/infra/main.bicep b/infra/main.bicep index 98941f84a..2f78fcf02 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -9,9 +9,6 @@ param solutionName string = 'clientadvisor' @description('Optional. Existing Log Analytics Workspace Resource ID') param existingLogAnalyticsWorkspaceId string = '' -@description('Optional. Resource ID of an existing Foundry project') -param azureExistingAIProjectResourceId string = '' - @description('Optional. CosmosDB Location') param cosmosLocation string = 'eastus2' @@ -394,18 +391,6 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id } } -// ========== Key Vault ========== // -// module keyvaultModule 'deploy_keyvault.bicep' = { -// name: 'deploy_keyvault' -// params: { -// solutionName: solutionSuffix -// solutionLocation: solutionLocation -// managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId -// kvName: 'kv-${solutionSuffix}' -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } module network 'modules/network.bicep' = if (enablePrivateNetworking) { name: take('network-${resourcesName}-deployment', 64) @@ -562,27 +547,6 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { } } -// ==========AI Foundry and related resources ========== // -// module aifoundry 'deploy_ai_foundry.bicep' = { -// name: 'deploy_ai_foundry' -// params: { -// solutionName: solutionSuffix -// solutionLocation: aiDeploymentsLocation -// keyVaultName: keyvault.outputs.name -// deploymentType: gptModelDeploymentType -// gptModelName: gptModelName -// azureOpenaiAPIVersion: azureOpenaiAPIVersion -// gptDeploymentCapacity: gptModelCapacity -// embeddingModel: embeddingModel -// embeddingDeploymentCapacity: embeddingDeploymentCapacity -// existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId -// azureExistingAIProjectResourceId: azureExistingAIProjectResourceId -// aiFoundryAiServicesAiProjectResourceName: 'proj-${solutionSuffix}' -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } - // ========== AI Foundry: AI Services ========== // // WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai var useExistingAiFoundryAiProject = !empty(existingFoundryProjectResourceId) @@ -725,17 +689,6 @@ module aiFoundryAiServices 'modules/ai-services.bicep' = if (aiFoundryAIservices } } -// ========== CosmosDB ========== // -// module cosmosDBModule 'deploy_cosmos_db.bicep' = { -// name: 'deploy_cosmos_db' -// params: { -// solutionLocation: cosmosLocation -// cosmosDBName: 'cosmos-${solutionSuffix}' -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } - //========== AVM WAF ========== // //========== Cosmos DB module ========== // var cosmosDbResourceName = 'cosmos-${solutionSuffix}' @@ -755,14 +708,6 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { { name: cosmosDbDatabaseName containers: [ - // { - // name: cosmosDbDatabaseMemoryContainerName - // paths: [ - // '/session_id' - // ] - // kind: 'Hash' - // version: 2 - // } { name: collectionName paths: [ @@ -834,19 +779,6 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { scope: resourceGroup(resourceGroup().name) } -// ========== Storage Account Module ========== // -// module storageAccountModule 'deploy_storage_account.bicep' = { -// name: 'deploy_storage_account' -// params: { -// solutionLocation: solutionLocation -// managedIdentityObjectId: userAssignedIdentity.outputs.principalId -// saName: 'st${solutionSuffix}' -// keyVaultName: keyvault.outputs.name -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } - // ========== AVM WAF ========== // // ========== Storage account module ========== // var storageAccountName = 'st${solutionSuffix}' @@ -1113,66 +1045,6 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { } } -// // ========== App Service Module ========== // -// module appserviceModule 'deploy_app_service.bicep' = { -// name: 'deploy_app_service' -// params: { -// solutionLocation: solutionLocation -// hostingPlanName: hostingPlanName -// websiteName: websiteName -// appEnvironment: appEnvironment -// azureSearchService: aifoundry.outputs.aiSearchService -// azureSearchIndex: azureSearchIndex -// azureSearchUseSemanticSearch: azureSearchUseSemanticSearch -// azureSearchSemanticSearchConfig: azureSearchSemanticSearchConfig -// azureSearchTopK: azureSearchTopK -// azureSearchContentColumns: azureSearchContentColumns -// azureSearchFilenameColumn: azureSearchFilenameColumn -// azureSearchTitleColumn: azureSearchTitleColumn -// azureSearchUrlColumn: azureSearchUrlColumn -// azureOpenAIResource: aifoundry.outputs.aiFoundryName -// azureOpenAIEndpoint: aifoundry.outputs.aoaiEndpoint -// azureOpenAIModel: gptModelName -// azureOpenAITemperature: azureOpenAITemperature -// azureOpenAITopP: azureOpenAITopP -// azureOpenAIMaxTokens: azureOpenAIMaxTokens -// azureOpenAIStopSequence: azureOpenAIStopSequence -// azureOpenAISystemMessage: azureOpenAISystemMessage -// azureOpenAIApiVersion: azureOpenaiAPIVersion -// azureOpenAIStream: azureOpenAIStream -// azureSearchQueryType: azureSearchQueryType -// azureSearchVectorFields: azureSearchVectorFields -// azureSearchPermittedGroupsField: azureSearchPermittedGroupsField -// azureSearchStrictness: azureSearchStrictness -// azureOpenAIEmbeddingName: embeddingModel -// azureOpenAIEmbeddingEndpoint: aifoundry.outputs.aoaiEndpoint -// USE_INTERNAL_STREAM: useInternalStream -// SQLDB_SERVER: sqlServerFqdn -// SQLDB_DATABASE: sqlDBModule.outputs.sqlDbName -// AZURE_COSMOSDB_ACCOUNT: cosmosDb.outputs.name -// AZURE_COSMOSDB_CONVERSATIONS_CONTAINER: collectionName -// AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName -// AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback -// //VITE_POWERBI_EMBED_URL: 'TBD' -// imageTag: imageTag -// userassignedIdentityClientId: userAssignedIdentity.outputs.clientId -// userassignedIdentityId: userAssignedIdentity.outputs.principalId -// applicationInsightsId: aifoundry.outputs.applicationInsightsId -// azureSearchServiceEndpoint: aifoundry.outputs.aiSearchTarget -// sqlSystemPrompt: functionAppSqlPrompt -// callTranscriptSystemPrompt: functionAppCallTranscriptSystemPrompt -// streamTextSystemPrompt: functionAppStreamTextSystemPrompt -// //aiFoundryProjectName:aifoundry.outputs.aiFoundryProjectName -// aiFoundryProjectEndpoint: aifoundry.outputs.aiFoundryProjectEndpoint -// aiFoundryName: aifoundry.outputs.aiFoundryName -// applicationInsightsConnectionString: aifoundry.outputs.applicationInsightsConnectionString -// azureExistingAIProjectResourceId: azureExistingAIProjectResourceId -// aiSearchProjectConnectionName: aifoundry.outputs.aiSearchFoundryConnectionName -// tags: tags -// } -// scope: resourceGroup(resourceGroup().name) -// } - // ========== Frontend server farm ========== // // WAF best practices for Web Application Services: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps // PSRule for Web Server Farm: https://azure.github.io/PSRule.Rules.Azure/en/rules/resource/#app-service @@ -1340,16 +1212,6 @@ module searchService 'br/public:avm/res/search/search-service:0.11.1' = { ipRules: [] } roleAssignments: [ - // { - // roleDefinitionIdOrName: 'Cognitive Services Contributor' // Cognitive Search Contributor - // principalId: userAssignedIdentity.outputs.principalId - // principalType: 'ServicePrincipal' - // } - // { - // roleDefinitionIdOrName: 'Cognitive Services OpenAI User'//'5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'// Cognitive Services OpenAI User - // principalId: userAssignedIdentity.outputs.principalId - // principalType: 'ServicePrincipal' - // } { roleDefinitionIdOrName: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // Search Index Data Reader principalId: userAssignedIdentity.outputs.principalId @@ -1479,7 +1341,6 @@ output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name @description('Client ID of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId @description('Name of the AI Search service.') -// output AI_SEARCH_SERVICE_NAME string = '' //aifoundry.outputs.aiSearchService output AI_SEARCH_SERVICE_NAME string = aiSearchName //aifoundry.outputs.aiSearchService @description('Name of the deployed web application.') @@ -1505,7 +1366,6 @@ output AZURE_AI_AGENT_ENDPOINT string = aiFoundryAiServices.outputs.aiProjectInf output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName @description('The endpoint URL of the Azure AI Search service.') -// output AZURE_AI_SEARCH_ENDPOINT string = '' //aifoundry.outputs.aiSearchTarget output AZURE_AI_SEARCH_ENDPOINT string = 'https://${aiSearchName}.search.windows.net' //aifoundry.outputs.aiSearchTarget @description('The system prompt used for call transcript processing in Azure Functions.') @@ -1563,7 +1423,6 @@ output AZURE_OPENAI_TEMPERATURE string = azureOpenAITemperature output AZURE_OPENAI_TOP_P string = azureOpenAITopP @description('The name of the Azure AI Search connection.') -// output AZURE_SEARCH_CONNECTION_NAME string = '' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName output AZURE_SEARCH_CONNECTION_NAME string = 'foundry-search-connection-${solutionSuffix}' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName @description('The columns in Azure AI Search that contain content.') @@ -1588,7 +1447,6 @@ output AZURE_SEARCH_QUERY_TYPE string = azureSearchQueryType output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = azureSearchSemanticSearchConfig @description('The name of the Azure AI Search service.') -// output AZURE_SEARCH_SERVICE string = '' //aifoundry.outputs.aiSearchService output AZURE_SEARCH_SERVICE string = aiSearchName //aifoundry.outputs.aiSearchService @description('The strictness setting for Azure AI Search semantic ranking.') diff --git a/infra/main.waf.parameters.json b/infra/main.parameters.waf.json similarity index 92% rename from infra/main.waf.parameters.json rename to infra/main.parameters.waf.json index 06d26fb49..35ac9feab 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.parameters.waf.json @@ -35,7 +35,7 @@ "existingLogAnalyticsWorkspaceId": { "value": "${AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID}" }, - "azureExistingAIProjectResourceId": { + "existingFoundryProjectResourceId": { "value": "${AZURE_EXISTING_AI_PROJECT_RESOURCE_ID}" }, "tags": { @@ -43,8 +43,8 @@ "solutionName": "${AZURE_ENV_NAME}" } }, - "enableTelemetry" :{ - "value": "${AZURE_ENV_ENABLE_TELEMETRY}" + "enableTelemetry": { + "value": "${AZURE_ENV_ENABLE_TELEMETRY}" }, "enableMonitoring": { "value": true diff --git a/infra/modules/project.bicep b/infra/modules/project.bicep index 953c56e13..dfe79810b 100644 --- a/infra/modules/project.bicep +++ b/infra/modules/project.bicep @@ -50,47 +50,6 @@ resource existingAiProject 'Microsoft.CognitiveServices/accounts/projects@2025-0 scope: resourceGroup(existingAiFoundryAiServicesSubscriptionId, existingAiFoundryAiServicesResourceGroupName) } -// @description('This is the built-in Search Index Data Reader role.') -// resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { -// name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' -// } - -// // ========== Search Service to AI Services Role Assignment ========== // -// resource searchServiceToAiServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if(!useExistingProject) { -// name: guid(aiProject.id, searchIndexDataReaderRoleDefinition.id) -// properties: { -// roleDefinitionId: searchIndexDataReaderRoleDefinition.id -// principalId: aiProject!.identity.principalId -// principalType: 'ServicePrincipal' -// } -// } - -// // Assign Search Index Data Reader role to existing AI Foundry project principal -// resource searchIndexDataReaderRoleAssignmentForExisting 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingProject) { -// name: guid(existingAiProject.id, searchIndexDataReaderRoleDefinition.id) -// scope: resourceGroup() -// properties: { -// roleDefinitionId: searchIndexDataReaderRoleDefinition.id -// principalId: existingAiProject!.identity.principalId -// principalType: 'ServicePrincipal' -// } -// } - -// @description('This is the built-in Search Service Contributor role.') -// resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { -// name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' -// } - -// resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingProject) { -// name: guid(aiProject.id, searchServiceContributorRoleDefinition.id) -// properties: { -// roleDefinitionId: searchServiceContributorRoleDefinition.id -// principalId: aiProject.identity.principalId -// principalType: 'ServicePrincipal' -// } -// } - - @description('AI Project metadata including name, resource ID, and API endpoint.') output aiProjectInfo aiProjectOutputType = { name: useExistingProject ? existingProjName : aiProject.name From 70ba90624b454d1a5eddfe213198075d9a6f7480 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 8 Sep 2025 11:33:46 +0000 Subject: [PATCH 61/84] Moved outadated Bicep files to Old folder and added createdBy Tag --- .github/workflows/CAdeploy.yml | 2 +- infra/main.bicep | 4 ++++ infra/{main.parameters.waf.json => main.waf.parameters.json} | 0 infra/{ => old}/deploy_ai_foundry.bicep | 0 infra/{ => old}/deploy_aifp_aisearch_connection.bicep | 0 infra/{ => old}/deploy_app_service.bicep | 0 infra/{ => old}/deploy_cosmos_db.bicep | 0 infra/{ => old}/deploy_foundry_model_role_assignment.bicep | 0 infra/{ => old}/deploy_keyvault.bicep | 0 infra/{ => old}/deploy_managed_identity.bicep | 0 infra/{ => old}/deploy_sql_db.bicep | 0 infra/{ => old}/deploy_storage_account.bicep | 0 12 files changed, 5 insertions(+), 1 deletion(-) rename infra/{main.parameters.waf.json => main.waf.parameters.json} (100%) rename infra/{ => old}/deploy_ai_foundry.bicep (100%) rename infra/{ => old}/deploy_aifp_aisearch_connection.bicep (100%) rename infra/{ => old}/deploy_app_service.bicep (100%) rename infra/{ => old}/deploy_cosmos_db.bicep (100%) rename infra/{ => old}/deploy_foundry_model_role_assignment.bicep (100%) rename infra/{ => old}/deploy_keyvault.bicep (100%) rename infra/{ => old}/deploy_managed_identity.bicep (100%) rename infra/{ => old}/deploy_sql_db.bicep (100%) rename infra/{ => old}/deploy_storage_account.bicep (100%) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index db65714e7..6822facb9 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -144,7 +144,7 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} \ + --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT" diff --git a/infra/main.bicep b/infra/main.bicep index 2f78fcf02..a8a304970 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -283,6 +283,9 @@ var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnal // var logAnalyticsPrimarySharedKey = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey // var logAnalyticsWorkspaceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId +@description('Optional created by user name') +param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] + // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' @@ -290,6 +293,7 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { tags: { ...tags TemplateName: 'Client Advisor' + CreatedBy: createdBy } } } diff --git a/infra/main.parameters.waf.json b/infra/main.waf.parameters.json similarity index 100% rename from infra/main.parameters.waf.json rename to infra/main.waf.parameters.json diff --git a/infra/deploy_ai_foundry.bicep b/infra/old/deploy_ai_foundry.bicep similarity index 100% rename from infra/deploy_ai_foundry.bicep rename to infra/old/deploy_ai_foundry.bicep diff --git a/infra/deploy_aifp_aisearch_connection.bicep b/infra/old/deploy_aifp_aisearch_connection.bicep similarity index 100% rename from infra/deploy_aifp_aisearch_connection.bicep rename to infra/old/deploy_aifp_aisearch_connection.bicep diff --git a/infra/deploy_app_service.bicep b/infra/old/deploy_app_service.bicep similarity index 100% rename from infra/deploy_app_service.bicep rename to infra/old/deploy_app_service.bicep diff --git a/infra/deploy_cosmos_db.bicep b/infra/old/deploy_cosmos_db.bicep similarity index 100% rename from infra/deploy_cosmos_db.bicep rename to infra/old/deploy_cosmos_db.bicep diff --git a/infra/deploy_foundry_model_role_assignment.bicep b/infra/old/deploy_foundry_model_role_assignment.bicep similarity index 100% rename from infra/deploy_foundry_model_role_assignment.bicep rename to infra/old/deploy_foundry_model_role_assignment.bicep diff --git a/infra/deploy_keyvault.bicep b/infra/old/deploy_keyvault.bicep similarity index 100% rename from infra/deploy_keyvault.bicep rename to infra/old/deploy_keyvault.bicep diff --git a/infra/deploy_managed_identity.bicep b/infra/old/deploy_managed_identity.bicep similarity index 100% rename from infra/deploy_managed_identity.bicep rename to infra/old/deploy_managed_identity.bicep diff --git a/infra/deploy_sql_db.bicep b/infra/old/deploy_sql_db.bicep similarity index 100% rename from infra/deploy_sql_db.bicep rename to infra/old/deploy_sql_db.bicep diff --git a/infra/deploy_storage_account.bicep b/infra/old/deploy_storage_account.bicep similarity index 100% rename from infra/deploy_storage_account.bicep rename to infra/old/deploy_storage_account.bicep From dd22c3e9e531bca52a97d4395348ef9bf7d92a44 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Mon, 8 Sep 2025 12:01:49 +0000 Subject: [PATCH 62/84] Remove duplicate 'createdBy' parameter and add VM admin credentials to parameters file --- infra/main.bicep | 3 --- infra/main.waf.parameters.json | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index bbbc3bfc6..89b8652ed 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -287,9 +287,6 @@ var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnal @description('Optional created by user name') param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] -@description('Optional created by user name') -param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] - // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index 35ac9feab..b92d12734 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -54,6 +54,12 @@ }, "enableScalability": { "value": true + }, + "vmAdminUsername": { + "value": "${AZURE_ENV_VM_ADMIN_USERNAME}" + }, + "vmAdminPassword": { + "value": "${AZURE_ENV_VM_ADMIN_PASSWORD}" } } } \ No newline at end of file From 0d8118149fa4f7ebf94458742653e35138f558a2 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 9 Sep 2025 07:01:49 +0000 Subject: [PATCH 63/84] readme changes --- docs/DeploymentGuide.md | 46 +- infra/main.bicep | 1 + infra/main.json | 42428 +++++++++++++++++++------------------- 3 files changed, 21438 insertions(+), 21037 deletions(-) diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index b710c7e4f..c1bb82b2c 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -14,12 +14,6 @@ Check the [Azure Products by Region](https://azure.microsoft.com/en-us/explore/g Here are some example regions where the services are available: East US, East US2, Australia East, UK South, France Central. - -### **Important: Check Azure OpenAI Quota Availability** - -⚠️ To ensure sufficient quota is available in your subscription, please follow [quota check instructions guide](./QuotaCheck.md) before you deploy the solution. - - ### [Optional] Quota Recommendations By default, the **Gpt-4o-mini model capacity** in deployment is set to **30k tokens**, so we recommend updating the following: @@ -29,6 +23,46 @@ Depending on your subscription quota and capacity, you can [adjust quota setting ­ ## Deployment Options & Steps +### Sandbox or WAF Aligned Deployment Options + +The [`infra`](../infra) folder of the Build-your-own-copilot-Solution-Accelerator contains the [`main.bicep`](../infra/main.bicep) Bicep script, which defines all Azure infrastructure components for this solution. + +By default, the `azd up` command uses the [`main.parameters.json`](../infra/main.parameters.json) file to deploy the solution. This file is pre-configured for a **sandbox environment** — ideal for development and proof-of-concept scenarios, with minimal security and cost controls for rapid iteration. + +For **production deployments**, the repository also provides [`main.waf.parameters.json`](../infra/main.waf.parameters.json), which applies a [Well-Architected Framework (WAF) aligned](https://learn.microsoft.com/en-us/azure/well-architected/) configuration. This option enables additional Azure best practices for reliability, security, cost optimization, operational excellence, and performance efficiency, such as: + + - Enhanced network security (e.g., Network protection with private endpoints) + - Stricter access controls and managed identities + - Logging, monitoring, and diagnostics enabled by default + - Resource tagging and cost management recommendations + +**How to choose your deployment configuration:** + +* Use the default `main.parameters.json` file for a **sandbox/dev environment** +* For a **WAF-aligned, production-ready deployment**, copy the contents of `main.waf.parameters.json` into `main.parameters.json` before running `azd up` + +--- + +### VM Credentials Configuration + +By default, the solution sets the VM administrator username and password from environment variables. + +To set your own VM credentials before deployment, use: + +```sh +azd env set AZURE_ENV_VM_ADMIN_USERNAME +azd env set AZURE_ENV_VM_ADMIN_PASSWORD +``` + +> [!TIP] +> Always review and adjust parameter values (such as region, capacity, security settings and log analytics workspace configuration) to match your organization’s requirements before deploying. For production, ensure you have sufficient quota and follow the principle of least privilege for all identities and role assignments. + + +> [!IMPORTANT] +> The WAF-aligned configuration is under active development. More Azure Well-Architected recommendations will be added in future updates. + +## Deployment Options & Steps + Pick from the options below to see step-by-step instructions for GitHub Codespaces, VS Code Dev Containers, and Local Environments. | [![Open in Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?repo=microsoft/Build-your-own-copilot-Solution-Accelerator) | [![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/Build-your-own-copilot-Solution-Accelerator) | diff --git a/infra/main.bicep b/infra/main.bicep index 89b8652ed..94510313b 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -777,6 +777,7 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { { locationName: solutionLocation failoverPriority: 0 + isZoneRedundant: enableRedundancy } ] } diff --git a/infra/main.json b/infra/main.json index d5e1a5614..d81dc2a6f 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "958310235447252556" + "templateHash": "1330795539540033187" } }, "parameters": { @@ -152,7 +152,7 @@ }, "enableMonitoring": { "type": "bool", - "defaultValue": false, + "defaultValue": true, "metadata": { "description": "Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false." } @@ -180,7 +180,7 @@ }, "containerRegistryHostname": { "type": "string", - "defaultValue": "biabcontainerreg.azurecr.io", + "defaultValue": "bycwacontainerreg.azurecr.io", "metadata": { "description": "Optional. The Container Registry hostname where the docker images for the frontend are located." } @@ -243,6 +243,13 @@ "description": "Optional. The tags to apply to all deployed Azure resources." }, "defaultValue": {} + }, + "createdBy": { + "type": "string", + "defaultValue": "[if(empty(deployer().userPrincipalName), '', split(deployer().userPrincipalName, '@')[0])]", + "metadata": { + "description": "Optional created by user name" + } } }, "variables": { @@ -272,7 +279,7 @@ "useInternalStream": "True", "useAIProjectClientFlag": "False", "sqlServerFqdn": "[format('sql-{0}.database.windows.net', variables('solutionSuffix'))]", - "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n Only return the generated SQL query. Do not return anything else.", + "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n For asset values: if question is about total \\\"asset value\\\"/\\\"portfolio value\\\"/\\\"AUM\\\" → return SUM of latest investments; if about \\\"current asset/investment value\\\" → return all latest investments without SUM.\n Only return the generated SQL query. Do not return anything else.", "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \n You have access to the client’s past meeting call transcripts. \n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \n If no data is available, state 'No relevant data found for previous meetings.", "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\n Always send clientId as '{client_id}'.", "replicaRegionPairs": { @@ -303,6 +310,12 @@ "allTags": "[union(createObject('azd-env-name', parameters('solutionName')), parameters('tags'))]", "resourcesName": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", "cosmosDbHaLocation": "[variables('cosmosDbZoneRedundantHaRegionPairs')[resourceGroup().location]]", + "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", + "existingLawSubscription": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[2], '')]", + "existingLawResourceGroup": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[4], '')]", + "existingLawName": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[8], '')]", + "logAnalyticsWorkspaceResourceName": "[format('log-{0}', variables('solutionSuffix'))]", + "applicationInsightsResourceName": "[format('appi-{0}', variables('solutionSuffix'))]", "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]", "privateDnsZones": [ "privatelink.cognitiveservices.azure.com", @@ -314,7 +327,8 @@ "[format('privatelink.file.{0}', environment().suffixes.storage)]", "privatelink.documents.azure.com", "privatelink.vaultcore.azure.net", - "[format('privatelink.{0}', environment().suffixes.sqlServerHostname)]" + "[format('privatelink{0}', environment().suffixes.sqlServerHostname)]", + "privatelink.search.windows.net" ], "dnsZoneIndex": { "cognitiveServices": 0, @@ -326,21 +340,20 @@ "storageFile": 6, "cosmosDB": 7, "keyVault": 8, - "sqlServer": 9 + "sqlServer": 9, + "searchService": 10 }, "aiRelatedDnsZoneIndices": [ "[variables('dnsZoneIndex').cognitiveServices]", "[variables('dnsZoneIndex').openAI]", "[variables('dnsZoneIndex').aiServices]" ], - "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", - "existingLawSubscription": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[2], '')]", - "existingLawResourceGroup": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[4], '')]", - "existingLawName": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[8], '')]", - "logAnalyticsWorkspaceResourceName": "[format('log-{0}', variables('solutionSuffix'))]", - "applicationInsightsResourceName": "[format('appi-{0}', variables('solutionSuffix'))]", "keyVaultName": "[format('KV-{0}', variables('solutionSuffix'))]", - "aiFoundryAiServicesResourceName": "[format('aif-{0}', variables('solutionSuffix'))]", + "useExistingAiFoundryAiProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", + "aiFoundryAiServicesSubscriptionId": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], subscription().id)]", + "aiFoundryAiServicesResourceGroupName": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], format('rg-{0}', variables('solutionSuffix')))]", + "aiFoundryAiServicesResourceName": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], format('aif-{0}', variables('solutionSuffix')))]", + "aiFoundryAiProjectResourceName": "[if(variables('useExistingAiFoundryAiProject'), split(parameters('existingFoundryProjectResourceId'), '/')[10], format('proj-{0}', variables('solutionSuffix')))]", "aiFoundryAiServicesAiProjectResourceName": "[format('proj-{0}', variables('solutionSuffix'))]", "aiFoundryAIservicesEnabled": true, "aiFoundryAiServicesModelDeployment": { @@ -372,14 +385,6 @@ "aiSearchName": "[format('srch-{0}', parameters('solutionName'))]" }, "resources": { - "resourceGroupTags": { - "type": "Microsoft.Resources/tags", - "apiVersion": "2021-04-01", - "name": "default", - "properties": { - "tags": "[shallowMerge(createArray(parameters('tags'), createObject('TemplateName', 'Client Advisor')))]" - } - }, "existingLogAnalyticsWorkspace": { "condition": "[variables('useExistingLogAnalytics')]", "existing": true, @@ -389,10 +394,71 @@ "resourceGroup": "[variables('existingLawResourceGroup')]", "name": "[variables('existingLawName')]" }, - "userAssignedIdentity": { + "resourceGroupTags": { + "type": "Microsoft.Resources/tags", + "apiVersion": "2021-04-01", + "name": "default", + "properties": { + "tags": "[shallowMerge(createArray(parameters('tags'), createObject('TemplateName', 'Client Advisor', 'CreatedBy', parameters('createdBy'))))]" + } + }, + "existingAiFoundryAiServices": { + "condition": "[variables('useExistingAiFoundryAiProject')]", + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-04-01-preview", + "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", + "name": "[variables('aiFoundryAiServicesResourceName')]" + }, + "existingAiFoundryAiServicesProject": { + "condition": "[variables('useExistingAiFoundryAiProject')]", + "existing": true, + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-04-01-preview", + "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", + "name": "[format('{0}/{1}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiProjectResourceName'))]" + }, + "projectAISearchConnection": { + "condition": "[not(variables('useExistingAiFoundryAiProject'))]", + "type": "Microsoft.CognitiveServices/accounts/projects/connections", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}/{2}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiServicesAiProjectResourceName'), variables('aiSearchName'))]", + "properties": { + "category": "CognitiveSearch", + "target": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[reference('searchService').outputs.resourceId.value]", + "location": "[reference('searchService').outputs.location.value]" + } + }, + "dependsOn": [ + "searchService" + ] + }, + "searchServiceToAiServicesRoleAssignment": { + "condition": "[not(variables('useExistingAiFoundryAiProject'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(variables('aiSearchName'), '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd', variables('aiFoundryAiServicesResourceName'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "principalId": "[reference('searchService').outputs.systemAssignedMIPrincipalId.value]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "searchService" + ] + }, + "logAnalyticsWorkspace": { + "condition": "[and(parameters('enableMonitoring'), not(variables('useExistingLogAnalytics')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('userAssignedIdentityResourceName')), 64)]", + "name": "[take(format('avm.res.operational-insights.workspace.{0}', variables('logAnalyticsWorkspaceResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -400,14 +466,40 @@ "mode": "Incremental", "parameters": { "name": { - "value": "[variables('userAssignedIdentityResourceName')]" + "value": "[variables('logAnalyticsWorkspaceResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" }, "location": { "value": "[variables('solutionLocation')]" }, - "tags": { - "value": "[parameters('tags')]" - } + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "skuName": { + "value": "PerGB2018" + }, + "dataRetention": { + "value": 365 + }, + "features": { + "value": { + "enableLogAccessUsingOnlyResourcePermissions": true + } + }, + "diagnosticSettings": { + "value": [ + { + "useThisWorkspace": true + } + ] + }, + "dailyQuotaGb": "[if(parameters('enableRedundancy'), createObject('value', 10), createObject('value', null()))]", + "replication": "[if(parameters('enableRedundancy'), createObject('value', createObject('enabled', true(), 'location', variables('replicaLocation'))), createObject('value', null()))]", + "publicNetworkAccessForIngestion": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "publicNetworkAccessForQuery": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "dataSources": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('tags', parameters('tags'), 'eventLogName', 'Application', 'eventTypes', createArray(createObject('eventType', 'Error'), createObject('eventType', 'Warning'), createObject('eventType', 'Information')), 'kind', 'WindowsEvent', 'name', 'applicationEvent'), createObject('counterName', '% Processor Time', 'instanceName', '*', 'intervalSeconds', 60, 'kind', 'WindowsPerformanceCounter', 'name', 'windowsPerfCounter1', 'objectName', 'Processor'), createObject('kind', 'IISLogs', 'name', 'sampleIISLog1', 'state', 'OnPremiseEnabled'))), createObject('value', null()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -416,16815 +508,3230 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "16707109626832623586" + "version": "0.36.1.42791", + "templateHash": "1749032521457140145" }, - "name": "User Assigned Identities", - "description": "This module deploys a User Assigned Identity." + "name": "Log Analytics Workspaces", + "description": "This module deploys a Log Analytics Workspace." }, "definitions": { - "federatedIdentityCredentialType": { + "diagnosticSettingType": { "type": "object", "properties": { "name": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The name of the federated identity credential." + "description": "Optional. The name of diagnostic setting." } }, - "audiences": { + "logCategoriesAndGroups": { "type": "array", "items": { - "type": "string" + "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": "Required. The list of audiences that can appear in the issued token." + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." } }, - "issuer": { + "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." + } + }, + "useThisWorkspace": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. If set to `true`, the `workspaceResourceId` property is ignored." + } + }, + "workspaceResourceId": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The URL of the issuer to be trusted." + "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." } }, - "subject": { + "storageAccountResourceId": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The identifier of the external identity." + "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." + } + } + } + }, + "gallerySolutionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + } + }, + "plan": { + "$ref": "#/definitions/solutionPlanType", + "metadata": { + "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." } } }, "metadata": { "__bicep_export!": true, - "description": "The type for the federated identity credential." + "description": "Properties of the gallery solutions to be created in the log analytics workspace." } }, - "lockType": { + "storageInsightsConfigType": { + "type": "object", + "properties": { + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the storage account to be linked." + } + }, + "containers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the blob containers that the workspace should read." + } + }, + "tables": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of tables to be read by the workspace." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the storage insights configuration." + } + }, + "linkedServiceType": { "type": "object", "properties": { "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the linked service." + } + }, + "resourceId": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. Specify the name of lock." + "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require read access." } }, - "kind": { + "writeAccessResourceId": { "type": "string", - "allowedValues": [ - "CanNotDelete", - "None", - "ReadOnly" - ], "nullable": true, "metadata": { - "description": "Optional. Specify the type of lock." + "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require write access." } } }, "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "__bicep_export!": true, + "description": "Properties of the linked service." + } + }, + "linkedStorageAccountType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the link." + } + }, + "storageAccountIds": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. Linked storage accounts resources Ids." + } } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the linked storage account." } }, - "roleAssignmentType": { + "savedSearchType": { "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." + "description": "Required. Name of the saved search." } }, - "roleDefinitionIdOrName": { + "etag": { "type": "string", + "nullable": true, "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'." + "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." } }, - "principalId": { + "category": { "type": "string", "metadata": { - "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + "description": "Required. The category of the saved search. This helps the user to find a saved search faster." } }, - "principalType": { + "displayName": { "type": "string", - "allowedValues": [ - "Device", - "ForeignGroup", - "Group", - "ServicePrincipal", - "User" - ], - "nullable": true, "metadata": { - "description": "Optional. The principal type of the assigned principal ID." + "description": "Required. Display name for the search." } }, - "description": { + "functionAlias": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. The description of the role assignment." + "description": "Optional. The function alias if query serves as a function." } }, - "condition": { + "functionParameters": { "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\"." + "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: 'param-name1:type1 = default_value1, param-name2:type2 = default_value2'. For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." } }, - "conditionVersion": { + "query": { "type": "string", - "allowedValues": [ - "2.0" - ], + "metadata": { + "description": "Required. The query expression for the saved search." + } + }, + "tags": { + "type": "array", "nullable": true, "metadata": { - "description": "Optional. Version of the condition." + "description": "Optional. The tags attached to the saved search." } }, - "delegatedManagedIdentityResourceId": { - "type": "string", + "version": { + "type": "int", "nullable": true, "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." + "description": "Optional. The version number of the query language. The current version is 2 and is the default." } } }, "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/federatedIdentityCredentialType" - }, - "nullable": true, - "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", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." + "__bicep_export!": true, + "description": "Properties of the saved search." } }, - "tags": { + "dataExportType": { "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')]", - "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": "[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.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" - } + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data export." + } + }, + "destination": { + "$ref": "#/definitions/destinationType", + "nullable": true, + "metadata": { + "description": "Optional. The destination of the data export." + } + }, + "enable": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the data export." + } + }, + "tableNames": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of table names to export." } } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the data export." } }, - "userAssignedIdentity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2024-11-30", - "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')))]", + "dataSourceType": { + "type": "object", "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(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 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')]" + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data source." + } + }, + "kind": { + "type": "string", + "metadata": { + "description": "Required. The kind of data source." + } + }, + "linkedResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace." + } + }, + "eventLogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the event log to configure when kind is WindowsEvent." + } + }, + "eventTypes": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The event types to configure when kind is WindowsEvent." + } + }, + "objectName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "instanceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "intervalSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "performanceCounters": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." + } + }, + "counterName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." + } + }, + "state": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." + } + }, + "syslogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. System log to configure when kind is LinuxSyslog." + } + }, + "syslogSeverities": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } }, - "dependsOn": [ - "userAssignedIdentity" - ] + "metadata": { + "__bicep_export!": true, + "description": "Properties of the data source." + } }, - "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-FederatedIdentityCred-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "tableType": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } }, - "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]" + "plan": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The plan for the table." } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", + "restoredLogs": { + "$ref": "#/definitions/restoredLogsType", + "nullable": true, "metadata": { - "_generator": { - "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "13656021764446440473" - }, - "name": "User Assigned Identity Federated Identity Credential", - "description": "This module deploys a User Assigned Identity Federated Identity Credential." - }, - "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." - } - } + "description": "Optional. The restored logs for the table." + } + }, + "schema": { + "$ref": "#/definitions/schemaType", + "nullable": true, + "metadata": { + "description": "Optional. The schema for the table." + } + }, + "searchResults": { + "$ref": "#/definitions/searchResultsType", + "nullable": true, + "metadata": { + "description": "Optional. The search results for the table." + } + }, + "retentionInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The retention in days for the table." + } + }, + "totalRetentionInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The total retention in days for the table." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" }, - "resources": [ - { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials", - "apiVersion": "2024-11-30", - "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]" - } + "nullable": true, + "metadata": { + "description": "Optional. The role assignments for the table." } } }, - "dependsOn": [ - "userAssignedIdentity" - ] - } - }, - "outputs": { - "name": { - "type": "string", "metadata": { - "description": "The name of the user assigned identity." - }, - "value": "[parameters('name')]" + "__bicep_export!": true, + "description": "Properties of the custom table." + } }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the user assigned identity." + "workspaceFeaturesType": { + "type": "object", + "properties": { + "disableLocalAuth": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Disable Non-EntraID based Auth. Default is true." + } + }, + "enableDataExport": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag that indicate if data should be exported." + } + }, + "enableLogAccessUsingOnlyResourcePermissions": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable log access using only resource permissions. Default is false." + } + }, + "immediatePurgeDataOn30Days": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag that describes if we want to remove the data after 30 days." + } + } }, - "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]" + "__bicep_export!": true, + "description": "Features of the workspace." + } }, - "clientId": { - "type": "string", - "metadata": { - "description": "The client ID (application ID) of the user assigned identity." + "workspaceReplicationType": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether the replication is enabled or not. When true, workspace configuration and data is replicated to the specified location." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The location to which the workspace is replicated. Required if replication is enabled." + } + } }, - "value": "[reference('userAssignedIdentity').clientId]" - }, - "resourceGroupName": { - "type": "string", "metadata": { - "description": "The resource group the user assigned identity was deployed into." - }, - "value": "[resourceGroup().name]" + "__bicep_export!": true, + "description": "Replication properties of the workspace." + } }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." + "_1.columnType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The column name." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "boolean", + "dateTime", + "dynamic", + "guid", + "int", + "long", + "real", + "string" + ], + "metadata": { + "description": "Required. The column type." + } + }, + "dataTypeHint": { + "type": "string", + "allowedValues": [ + "armPath", + "guid", + "ip", + "uri" + ], + "nullable": true, + "metadata": { + "description": "Optional. The column data type logical hint." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The column description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Column display name." + } + } }, - "value": "[reference('userAssignedIdentity', '2024-11-30', 'full').location]" - } - } - } - } - }, - "network": { - "condition": "[parameters('enablePrivateNetworking')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('network-{0}-deployment', variables('resourcesName')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "resourcesName": { - "value": "[variables('resourcesName')]" - }, - "logAnalyticsWorkSpaceResourceId": { - "value": "[reference('logAnalyticsWorkspace').outputs.resourceId.value]" - }, - "vmAdminUsername": { - "value": "[coalesce(parameters('vmAdminUsername'), 'JumpboxAdminUser')]" - }, - "vmAdminPassword": { - "value": "[coalesce(parameters('vmAdminPassword'), 'JumpboxAdminP@ssw0rd1234!')]" - }, - "vmSize": { - "value": "[coalesce(parameters('vmSize'), 'Standard_DS2_v2')]" - }, - "location": { - "value": "[variables('solutionLocation')]" - }, - "tags": { - "value": "[variables('allTags')]" - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "8046497849303973351" - } - }, - "parameters": { - "resourcesName": { - "type": "string", "metadata": { - "description": "Required. Named used for all resource naming." + "description": "The parameters of the table column.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } } }, - "logAnalyticsWorkSpaceResourceId": { - "type": "string", + "destinationType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The destination resource ID." + } + }, + "metaData": { + "type": "object", + "properties": { + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination metadata." + } + } + }, "metadata": { - "description": "Required. Resource ID of the Log Analytics Workspace for monitoring and diagnostics." + "description": "The data export destination properties.", + "__bicep_imported_from!": { + "sourceTemplate": "data-export/main.bicep" + } } }, - "location": { - "type": "string", - "minLength": 3, + "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." + } + } + }, "metadata": { - "description": "Required. Azure region for all services." + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "tags": { + "managedIdentityAllType": { "type": "object", - "defaultValue": {}, + "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." + } + } + }, "metadata": { - "description": "Optional. Tags to be applied to the resources." + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, + "restoredLogsType": { + "type": "object", + "properties": { + "sourceTable": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table to restore data from." + } + }, + "startRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the restore from (UTC)." + } + }, + "endRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the restore by (UTC)." + } + } + }, "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." + "description": "The parameters of the restore operation that initiated the table.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } } }, - "vmAdminUsername": { - "type": "securestring", + "roleAssignmentType": { + "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." + } + } + }, "metadata": { - "description": "Required. Admin username for the VM." + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } }, - "vmAdminPassword": { - "type": "securestring", + "schemaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The table name." + } + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.columnType" + }, + "metadata": { + "description": "Required. A list of table custom columns." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table display name." + } + } + }, "metadata": { - "description": "Required. Admin password for the VM." + "description": "The table schema.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } } }, - "vmSize": { - "type": "string", + "searchResultsType": { + "type": "object", + "properties": { + "query": { + "type": "string", + "metadata": { + "description": "Required. The search job query." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The search description." + } + }, + "limit": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Limit the search job to return up to specified number of rows." + } + }, + "startSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the search from (UTC)." + } + }, + "endSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the search by (UTC)." + } + } + }, "metadata": { - "description": "Required. VM size for the Jumpbox VM." + "description": "The parameters of the search job that initiated the table.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } } - } - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('network-{0}-create', parameters('resourcesName')), 64)]", + }, + "solutionPlanType": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + } }, - "mode": "Incremental", - "parameters": { - "resourcesName": { - "value": "[parameters('resourcesName')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "logAnalyticsWorkSpaceResourceId": { - "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "addressPrefixes": { - "value": [ - "10.0.0.0/20" - ] - }, - "subnets": { - "value": [ - { - "name": "web", - "addressPrefixes": [ - "10.0.0.0/23" - ], - "networkSecurityGroup": { - "name": "nsg-web", - "securityRules": [ - { - "name": "AllowHttpsInbound", - "properties": { - "access": "Allow", - "direction": "Inbound", - "priority": 100, - "protocol": "Tcp", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefixes": [ - "0.0.0.0/0" - ], - "destinationAddressPrefixes": [ - "10.0.0.0/23" - ] - } - }, - { - "name": "AllowIntraSubnetTraffic", - "properties": { - "access": "Allow", - "direction": "Inbound", - "priority": 200, - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefixes": [ - "10.0.0.0/23" - ], - "destinationAddressPrefixes": [ - "10.0.0.0/23" - ] - } - }, - { - "name": "AllowAzureLoadBalancer", - "properties": { - "access": "Allow", - "direction": "Inbound", - "priority": 300, - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "AzureLoadBalancer", - "destinationAddressPrefix": "10.0.0.0/23" - } - } - ] - }, - "delegation": "Microsoft.App/environments" - }, - { - "name": "peps", - "addressPrefixes": [ - "10.0.2.0/23" - ], - "privateEndpointNetworkPolicies": "Disabled", - "privateLinkServiceNetworkPolicies": "Disabled", - "networkSecurityGroup": { - "name": "nsg-peps", - "securityRules": [] - } - } - ] - }, - "bastionConfiguration": { - "value": { - "name": "[format('bas-{0}', parameters('resourcesName'))]", - "subnet": { - "name": "AzureBastionSubnet", - "addressPrefixes": [ - "10.0.10.0/26" - ], - "networkSecurityGroup": { - "name": "nsg-AzureBastionSubnet", - "securityRules": [ - { - "name": "AllowGatewayManager", - "properties": { - "access": "Allow", - "direction": "Inbound", - "priority": 2702, - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefix": "GatewayManager", - "destinationAddressPrefix": "*" - } - }, - { - "name": "AllowHttpsInBound", - "properties": { - "access": "Allow", - "direction": "Inbound", - "priority": 2703, - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefix": "Internet", - "destinationAddressPrefix": "*" - } - }, - { - "name": "AllowSshRdpOutbound", - "properties": { - "access": "Allow", - "direction": "Outbound", - "priority": 100, - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRanges": [ - "22", - "3389" - ], - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowAzureCloudOutbound", - "properties": { - "access": "Allow", - "direction": "Outbound", - "priority": 110, - "protocol": "Tcp", - "sourcePortRange": "*", - "destinationPortRange": "443", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "AzureCloud" - } - } - ] - } - } - } - }, - "jumpboxConfiguration": { - "value": { - "name": "[format('vm-jumpbox-{0}', parameters('resourcesName'))]", - "size": "[parameters('vmSize')]", - "username": "[parameters('vmAdminUsername')]", - "password": "[parameters('vmAdminPassword')]", - "subnet": { - "name": "jumpbox", - "addressPrefixes": [ - "10.0.12.0/23" - ], - "networkSecurityGroup": { - "name": "nsg-jumbox", - "securityRules": [ - { - "name": "AllowRdpFromBastion", - "properties": { - "access": "Allow", - "direction": "Inbound", - "priority": 100, - "protocol": "Tcp", - "sourcePortRange": "*", - "destinationPortRange": "3389", - "sourceAddressPrefixes": [ - "10.0.10.0/26" - ], - "destinationAddressPrefixes": [ - "10.0.12.0/23" - ] - } - } - ] - } - } - } - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" + "product": { + "type": "string", + "metadata": { + "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", + "publisher": { + "type": "string", + "nullable": true, "metadata": { - "_generator": { - "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "10645092392603021381" - } - }, - "definitions": { - "_1.networkSecurityGroupType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the network security group." - } - }, - "securityRules": { - "type": "array", - "items": { - "type": "object" - }, - "metadata": { - "description": "Required. The security rules for the network security group." - } - } - }, - "metadata": { - "description": "Custom type definition for network security group configuration", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" - } + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/operations-management/solution:0.3.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Log Analytics workspace." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "skuName": { + "type": "string", + "defaultValue": "PerGB2018", + "allowedValues": [ + "CapacityReservation", + "Free", + "LACluster", + "PerGB2018", + "PerNode", + "Premium", + "Standalone", + "Standard" + ], + "metadata": { + "description": "Optional. The name of the SKU." + } + }, + "skuCapacityReservationLevel": { + "type": "int", + "defaultValue": 100, + "minValue": 100, + "maxValue": 5000, + "metadata": { + "description": "Optional. The capacity reservation level in GB for this workspace, when CapacityReservation sku is selected. Must be in increments of 100 between 100 and 5000." + } + }, + "storageInsightsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/storageInsightsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of storage accounts to be read by the workspace." + } + }, + "linkedServices": { + "type": "array", + "items": { + "$ref": "#/definitions/linkedServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of services to be linked." + } + }, + "linkedStorageAccounts": { + "type": "array", + "items": { + "$ref": "#/definitions/linkedStorageAccountType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of Storage Accounts to be linked. Required if 'forceCmkForQuery' is set to 'true' and 'savedSearches' is not empty." + } + }, + "savedSearches": { + "type": "array", + "items": { + "$ref": "#/definitions/savedSearchType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Kusto Query Language searches to save." + } + }, + "dataExports": { + "type": "array", + "items": { + "$ref": "#/definitions/dataExportType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW data export instances to be deployed." + } + }, + "dataSources": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSourceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW data sources to configure." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW custom tables to be deployed." + } + }, + "gallerySolutions": { + "type": "array", + "items": { + "$ref": "#/definitions/gallerySolutionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of gallerySolutions to be created in the log analytics workspace." + } + }, + "onboardWorkspaceToSentinel": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Onboard the Log Analytics Workspace to Sentinel. Requires 'SecurityInsights' solution to be in gallerySolutions." + } + }, + "dataRetention": { + "type": "int", + "defaultValue": 365, + "minValue": 0, + "maxValue": 730, + "metadata": { + "description": "Optional. Number of days data will be retained for." + } + }, + "dailyQuotaGb": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "metadata": { + "description": "Optional. The workspace daily quota for ingestion." + } + }, + "publicNetworkAccessForIngestion": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The network access type for accessing Log Analytics ingestion." + } + }, + "publicNetworkAccessForQuery": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. The network access type for accessing Log Analytics query." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource. Only one type of identity is supported: system-assigned or user-assigned, but not both." + } + }, + "features": { + "$ref": "#/definitions/workspaceFeaturesType", + "nullable": true, + "metadata": { + "description": "Optional. The workspace features." + } + }, + "replication": { + "$ref": "#/definitions/workspaceReplicationType", + "nullable": true, + "metadata": { + "description": "Optional. The workspace replication properties." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "forceCmkForQuery": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether customer managed storage is mandatory for query management." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces@2025-02-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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()), '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')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "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": "[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')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.12.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" + } + } + } + } + }, + "logAnalyticsWorkspace": { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "features": { + "searchVersion": 1, + "enableLogAccessUsingOnlyResourcePermissions": "[coalesce(tryGet(parameters('features'), 'enableLogAccessUsingOnlyResourcePermissions'), false())]", + "disableLocalAuth": "[coalesce(tryGet(parameters('features'), 'disableLocalAuth'), true())]", + "enableDataExport": "[tryGet(parameters('features'), 'enableDataExport')]", + "immediatePurgeDataOn30Days": "[tryGet(parameters('features'), 'immediatePurgeDataOn30Days')]" + }, + "sku": { + "name": "[parameters('skuName')]", + "capacityReservationLevel": "[if(equals(parameters('skuName'), 'CapacityReservation'), parameters('skuCapacityReservationLevel'), null())]" + }, + "retentionInDays": "[parameters('dataRetention')]", + "workspaceCapping": { + "dailyQuotaGb": "[parameters('dailyQuotaGb')]" + }, + "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]", + "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]", + "forceCmkForQuery": "[parameters('forceCmkForQuery')]", + "replication": "[parameters('replication')]" + }, + "identity": "[variables('identity')]" + }, + "logAnalyticsWorkspace_diagnosticSettings": { + "copy": { + "name": "logAnalyticsWorkspace_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{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": "[if(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'useThisWorkspace'), false()), resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), 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": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_sentinelOnboarding": { + "condition": "[and(not(empty(filter(coalesce(parameters('gallerySolutions'), createArray()), lambda('item', startsWith(lambdaVariables('item').name, 'SecurityInsights'))))), parameters('onboardWorkspaceToSentinel'))]", + "type": "Microsoft.SecurityInsights/onboardingStates", + "apiVersion": "2024-03-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "default", + "properties": {}, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_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.OperationalInsights/workspaces/{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": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_roleAssignments": { + "copy": { + "name": "logAnalyticsWorkspace_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces', 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": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_storageInsightConfigs": { + "copy": { + "name": "logAnalyticsWorkspace_storageInsightConfigs", + "count": "[length(coalesce(parameters('storageInsightsConfigs'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-StorageInsightsConfig-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'containers')]" + }, + "tables": { + "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'tables')]" + }, + "storageAccountResourceId": { + "value": "[coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()].storageAccountResourceId]" + } + }, + "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.36.1.42791", + "templateHash": "1306323182548882150" + }, + "name": "Log Analytics Workspace Storage Insight Configs", + "description": "This module deploys a Log Analytics Workspace Storage Insight Config." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." } }, - "bastionHostConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the Bastion Host resource." - } - }, - "subnet": { - "$ref": "#/definitions/subnetType", - "nullable": true, - "metadata": { - "description": "Optional. Subnet configuration for the Jumpbox VM." - } - } + "name": { + "type": "string", + "defaultValue": "[format('{0}-stinsconfig', last(split(parameters('storageAccountResourceId'), '/')))]", + "metadata": { + "description": "Optional. The name of the storage insights config." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Azure Resource Manager ID of the storage account resource." + } + }, + "containers": { + "type": "array", + "items": { + "type": "string" }, + "nullable": true, "metadata": { - "description": "Custom type definition for establishing Bastion Host for remote connection.", - "__bicep_imported_from!": { - "sourceTemplate": "bastionHost.bicep" - } + "description": "Optional. The names of the blob containers that the workspace should read." } }, - "jumpBoxConfigurationType": { + "tables": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the Azure tables that the workspace should read." + } + }, + "tags": { "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the Virtual Machine." - } - }, - "size": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The size of the VM." - } - }, - "username": { - "type": "string", - "metadata": { - "description": "Username to access VM." - } - }, - "password": { - "type": "securestring", - "metadata": { - "description": "Password to access VM." - } + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-02-01#properties/tags" }, - "subnet": { - "$ref": "#/definitions/subnetType", - "nullable": true, - "metadata": { - "description": "Optional. Subnet configuration for the Jumpbox VM." - } - } + "description": "Optional. Tags to configure in the resource." }, - "metadata": { - "description": "Custom type definition for establishing Jumpbox Virtual Machine and its associated resources.", - "__bicep_imported_from!": { - "sourceTemplate": "jumpbox.bicep" - } - } + "nullable": true + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2024-01-01", + "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" }, - "subnetOutputType": { - "type": "object", + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "storageinsightconfig": { + "type": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the subnet." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the subnet." - } - }, - "nsgName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The name of the associated network security group, if any." - } - }, - "nsgResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The resource ID of the associated network security group, if any." - } - } - }, - "metadata": { - "description": "Custom type definition for subnet resource information as output", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" + "containers": "[parameters('containers')]", + "tables": "[parameters('tables')]", + "storageAccount": { + "id": "[parameters('storageAccountResourceId')]", + "key": "[listKeys('storageAccount', '2024-01-01').keys[0].value]" } } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage insights configuration." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/storageInsightConfigs', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" }, - "subnetType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The Name of the subnet resource." - } - }, - "addressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. Prefixes for the subnet." - } - }, - "delegation": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The delegation to enable on the subnet." - } - }, - "privateEndpointNetworkPolicies": { - "type": "string", - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "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." - } - }, - "networkSecurityGroup": { - "$ref": "#/definitions/_1.networkSecurityGroupType", - "nullable": true, - "metadata": { - "description": "Optional. Network Security Group configuration for the subnet." - } - }, - "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." - } - } + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the storage insight configuration is deployed." }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", "metadata": { - "description": "Custom type definition for subnet configuration", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" - } - } + "description": "The name of the storage insights configuration." + }, + "value": "[parameters('name')]" } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_linkedServices": { + "copy": { + "name": "logAnalyticsWorkspace_linkedServices", + "count": "[length(coalesce(parameters('linkedServices'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-LinkedService-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('linkedServices'), createArray())[copyIndex()].name]" + }, + "resourceId": { + "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'resourceId')]" + }, + "writeAccessResourceId": { + "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'writeAccessResourceId')]" + } + }, + "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.36.1.42791", + "templateHash": "5230241501765697269" + }, + "name": "Log Analytics Workspace Linked Services", + "description": "This module deploys a Log Analytics Workspace Linked Service." }, "parameters": { - "resourcesName": { + "logAnalyticsWorkspaceName": { "type": "string", - "minLength": 6, - "maxLength": 25, "metadata": { - "description": "Name used for naming all network resources." + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." } }, - "location": { + "name": { "type": "string", - "minLength": 3, "metadata": { - "description": "Azure region for all services." + "description": "Required. Name of the link." } }, - "logAnalyticsWorkSpaceResourceId": { + "resourceId": { "type": "string", + "nullable": true, "metadata": { - "description": "Resource ID of the Log Analytics Workspace for monitoring and diagnostics." + "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require read access." } }, - "addressPrefixes": { - "type": "array", + "writeAccessResourceId": { + "type": "string", + "nullable": true, "metadata": { - "description": "Networking address prefix for the VNET." + "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require write access." } }, - "subnets": { - "type": "array", - "items": { - "$ref": "#/definitions/subnetType" - }, + "tags": { + "type": "object", "metadata": { - "description": "Array of subnets to be created within the VNET." + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "linkedService": { + "type": "Microsoft.OperationalInsights/workspaces/linkedServices", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resourceId": "[parameters('resourceId')]", + "writeAccessResourceId": "[parameters('writeAccessResourceId')]" } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed linked service." + }, + "value": "[parameters('name')]" }, - "jumpboxConfiguration": { - "$ref": "#/definitions/jumpBoxConfigurationType", - "nullable": true, + "resourceId": { + "type": "string", "metadata": { - "description": "Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation." - } + "description": "The resource ID of the deployed linked service." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedServices', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" }, - "bastionConfiguration": { - "$ref": "#/definitions/bastionHostConfigurationType", - "nullable": true, + "resourceGroupName": { + "type": "string", "metadata": { - "description": "Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation." + "description": "The resource group where the linked service is deployed." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_linkedStorageAccounts": { + "copy": { + "name": "logAnalyticsWorkspace_linkedStorageAccounts", + "count": "[length(coalesce(parameters('linkedStorageAccounts'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-LinkedStorageAccount-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].name]" + }, + "storageAccountIds": { + "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].storageAccountIds]" + } + }, + "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.36.1.42791", + "templateHash": "10372135754202496594" + }, + "name": "Log Analytics Workspace Linked Storage Accounts", + "description": "This module deploys a Log Analytics Workspace Linked Storage Account." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." } }, - "tags": { - "type": "object", - "defaultValue": {}, + "name": { + "type": "string", + "allowedValues": [ + "Query", + "Alerts", + "CustomLogs", + "AzureWatson" + ], "metadata": { - "description": "Optional. Tags to be applied to the resources." + "description": "Required. Name of the link." } }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, + "storageAccountIds": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." + "description": "Required. Linked storage accounts resources Ids." } } }, "resources": { - "virtualNetwork": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-virtualNetwork', parameters('resourcesName'))]", + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "linkedStorageAccount": { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('vnet-{0}', parameters('resourcesName'))]" - }, - "addressPrefixes": { - "value": "[parameters('addressPrefixes')]" - }, - "subnets": { - "value": "[parameters('subnets')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "logAnalyticsWorkspaceId": { - "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" - }, - "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.37.4.10188", - "templateHash": "1936468723755149871" - } - }, - "definitions": { - "subnetOutputType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the subnet." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the subnet." - } - }, - "nsgName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The name of the associated network security group, if any." - } - }, - "nsgResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The resource ID of the associated network security group, if any." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Custom type definition for subnet resource information as output" - } - }, - "subnetType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The Name of the subnet resource." - } - }, - "addressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. Prefixes for the subnet." - } - }, - "delegation": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The delegation to enable on the subnet." - } - }, - "privateEndpointNetworkPolicies": { - "type": "string", - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "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." - } - }, - "networkSecurityGroup": { - "$ref": "#/definitions/networkSecurityGroupType", - "nullable": true, - "metadata": { - "description": "Optional. Network Security Group configuration for the subnet." - } - }, - "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." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Custom type definition for subnet configuration" - } - }, - "networkSecurityGroupType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the network security group." - } - }, - "securityRules": { - "type": "array", - "items": { - "type": "object" - }, - "metadata": { - "description": "Required. The security rules for the network security group." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Custom type definition for network security group configuration" - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Name of the virtual network." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Azure region to deploy resources." - } - }, - "addressPrefixes": { - "type": "array", - "metadata": { - "description": "Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`." - } - }, - "subnets": { - "type": "array", - "items": { - "$ref": "#/definitions/subnetType" - }, - "metadata": { - "description": "An array of subnets to be created within the virtual network. Each subnet can have its own configuration and associated Network Security Group (NSG)." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to be applied to the resources." - } - }, - "logAnalyticsWorkspaceId": { + "storageAccountIds": "[parameters('storageAccountIds')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed linked storage account." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed linked storage account." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedStorageAccounts', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the linked storage account is deployed." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_savedSearches": { + "copy": { + "name": "logAnalyticsWorkspace_savedSearches", + "count": "[length(coalesce(parameters('savedSearches'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-SavedSearch-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[format('{0}{1}', coalesce(parameters('savedSearches'), createArray())[copyIndex()].name, uniqueString(deployment().name))]" + }, + "etag": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'etag')]" + }, + "displayName": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].displayName]" + }, + "category": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].category]" + }, + "query": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].query]" + }, + "functionAlias": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionAlias')]" + }, + "functionParameters": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionParameters')]" + }, + "tags": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'tags')]" + }, + "version": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'version')]" + } + }, + "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.36.1.42791", + "templateHash": "9015459905306126128" + }, + "name": "Log Analytics Workspace Saved Searches", + "description": "This module deploys a Log Analytics Workspace Saved Search." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the saved search." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name for the search." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. Query category." + } + }, + "query": { + "type": "string", + "metadata": { + "description": "Required. Kusto Query to be stored." + } + }, + "tags": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-02-01#properties/properties/properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + }, + "functionAlias": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The function alias if query serves as a function." + } + }, + "functionParameters": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: \"param-name1:type1 = default_value1, param-name2:type2 = default_value2\". For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." + } + }, + "version": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The version number of the query language." + } + }, + "etag": { + "type": "string", + "defaultValue": "*", + "metadata": { + "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "savedSearch": { + "type": "Microsoft.OperationalInsights/workspaces/savedSearches", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "properties": { + "etag": "[parameters('etag')]", + "tags": "[coalesce(parameters('tags'), createArray())]", + "displayName": "[parameters('displayName')]", + "category": "[parameters('category')]", + "query": "[parameters('query')]", + "functionAlias": "[parameters('functionAlias')]", + "functionParameters": "[parameters('functionParameters')]", + "version": "[parameters('version')]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed saved search." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the saved search is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed saved search." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace", + "logAnalyticsWorkspace_linkedStorageAccounts" + ] + }, + "logAnalyticsWorkspace_dataExports": { + "copy": { + "name": "logAnalyticsWorkspace_dataExports", + "count": "[length(coalesce(parameters('dataExports'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-DataExport-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "workspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('dataExports'), createArray())[copyIndex()].name]" + }, + "destination": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'destination')]" + }, + "enable": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'enable')]" + }, + "tableNames": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'tableNames')]" + } + }, + "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.36.1.42791", + "templateHash": "8586520532175356447" + }, + "name": "Log Analytics Workspace Data Exports", + "description": "This module deploys a Log Analytics Workspace Data Export." + }, + "definitions": { + "destinationType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The destination resource ID." + } + }, + "metaData": { + "type": "object", + "properties": { + "eventHubName": { "type": "string", + "nullable": true, "metadata": { - "description": "Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to." - } - }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." + "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." } } }, - "resources": { - "nsgs": { - "copy": { - "name": "nsgs", - "count": "[length(parameters('subnets'))]", - "mode": "serial", - "batchSize": 1 - }, - "condition": "[not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('{0}-{1}-networksecuritygroup', parameters('name'), tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('{0}-{1}', tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), parameters('name'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "securityRules": { - "value": "[tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'securityRules')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "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.33.93.31351", - "templateHash": "2305747478751645177" - }, - "name": "Network Security Groups", - "description": "This module deploys a Network security Group (NSG)." - }, - "definitions": { - "securityRuleType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the security rule." - } - }, - "properties": { - "type": "object", - "properties": { - "access": { - "type": "string", - "allowedValues": [ - "Allow", - "Deny" - ], - "metadata": { - "description": "Required. Whether network traffic is allowed or denied." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The description of the security rule." - } - }, - "destinationAddressPrefix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." - } - }, - "destinationAddressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." - } - }, - "destinationApplicationSecurityGroupResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource IDs of the application security groups specified as destination." - } - }, - "destinationPortRange": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." - } - }, - "destinationPortRanges": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The destination port ranges." - } - }, - "direction": { - "type": "string", - "allowedValues": [ - "Inbound", - "Outbound" - ], - "metadata": { - "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." - } - }, - "priority": { - "type": "int", - "minValue": 100, - "maxValue": 4096, - "metadata": { - "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "*", - "Ah", - "Esp", - "Icmp", - "Tcp", - "Udp" - ], - "metadata": { - "description": "Required. Network protocol this rule applies to." - } - }, - "sourceAddressPrefix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." - } - }, - "sourceAddressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The CIDR or source IP ranges." - } - }, - "sourceApplicationSecurityGroupResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource IDs of the application security groups specified as source." - } - }, - "sourcePortRange": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." - } - }, - "sourcePortRanges": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The source port ranges." - } - } - }, - "metadata": { - "description": "Required. The properties of the security rule." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type of a security rule." - } - }, - "diagnosticSettingLogsOnlyType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. Name of the Network Security Group." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "securityRules": { - "type": "array", - "items": { - "$ref": "#/definitions/securityRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." - } - }, - "flushConnection": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingLogsOnlyType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" - } - } - } - } - }, - "networkSecurityGroup": { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "copy": [ - { - "name": "securityRules", - "count": "[length(coalesce(parameters('securityRules'), createArray()))]", - "input": { - "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", - "properties": { - "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", - "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", - "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", - "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", - "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", - "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", - "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", - "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", - "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", - "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", - "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", - "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", - "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", - "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", - "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" - } - } - } - ], - "flushConnection": "[parameters('flushConnection')]" - } - }, - "networkSecurityGroup_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/networkSecurityGroups/{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": [ - "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(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ - "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": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('{0}-virtualNetwork', parameters('name')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[parameters('name')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "addressPrefixes": { - "value": "[parameters('addressPrefixes')]" - }, - "subnets": { - "copy": [ - { - "name": "value", - "count": "[length(parameters('subnets'))]", - "input": "[createObject('name', parameters('subnets')[copyIndex('value')].name, 'addressPrefixes', tryGet(parameters('subnets')[copyIndex('value')], 'addressPrefixes'), 'networkSecurityGroupResourceId', if(not(empty(tryGet(parameters('subnets')[copyIndex('value')], 'networkSecurityGroup'))), reference(format('nsgs[{0}]', copyIndex('value'))).outputs.resourceId.value, null()), 'privateEndpointNetworkPolicies', tryGet(parameters('subnets')[copyIndex('value')], 'privateEndpointNetworkPolicies'), 'privateLinkServiceNetworkPolicies', tryGet(parameters('subnets')[copyIndex('value')], 'privateLinkServiceNetworkPolicies'), 'delegation', tryGet(parameters('subnets')[copyIndex('value')], 'delegation'))]" - } - ] - }, - "diagnosticSettings": { - "value": [ - { - "name": "vnetDiagnostics", - "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", - "logCategoriesAndGroups": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metricCategories": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - } - ] - }, - "tags": { - "value": "[parameters('tags')]" - }, - "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.35.1.17967", - "templateHash": "16195883788906927531" - }, - "name": "Virtual Networks", - "description": "This module deploys a Virtual Network (vNet)." - }, - "definitions": { - "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." - } - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "prefixItems": [ - { - "type": "object", - "properties": { - "pool": { - "type": "object", - "properties": { - "id": { - "type": "string", - "metadata": { - "description": "Required. The Resource ID of the IPAM pool." - } - } - }, - "metadata": { - "description": "Required. The Resource ID of the IPAM pool." - } - }, - "numberOfIpAddresses": { - "type": "string", - "metadata": { - "description": "Required. Number of IP addresses allocated from the pool." - } - } - } - } - ], - "items": false, - "nullable": true, - "metadata": { - "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` is empty and the VNet address space configured to use IPAM Pool." - } - }, - "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", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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." - } - } - } - }, - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "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 OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`." - } - }, - "ipamPoolNumberOfIpAddresses": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Number of IP addresses allocated from the pool. To be used only when the addressPrefix param is defined with a resource ID of an IPAM pool." - } - }, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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)))))]" - } - ], - "enableReferencedModulesTelemetry": false, - "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.7.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-05-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "addressSpace": "[if(contains(parameters('addressPrefixes')[0], '/Microsoft.Network/networkManagers/'), createObject('ipamPoolPrefixAllocations', createArray(createObject('pool', createObject('id', parameters('addressPrefixes')[0]), 'numberOfIpAddresses', parameters('ipamPoolNumberOfIpAddresses')))), createObject('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')]" - }, - "ipamPoolPrefixAllocations": { - "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'ipamPoolPrefixAllocations')]" - }, - "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')]" - }, - "enableTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" - } - }, - "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.35.1.17967", - "templateHash": "9728353654559466189" - }, - "name": "Virtual Network Subnets", - "description": "This module deploys a Virtual Network Subnet." - }, - "definitions": { - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. 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." - } - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "items": { - "type": "object" - }, - "nullable": true, - "metadata": { - "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", - "nullable": true, - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "metadata": { - "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." - } - }, - "privateLinkServiceNetworkPolicies": { - "type": "string", - "nullable": true, - "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 the 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 the 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": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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)))))]" - } - ], - "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { - "existing": true, - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-01-01", - "name": "[parameters('virtualNetworkName')]" - }, - "subnet": { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-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')]", - "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", - "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": "[parameters('privateEndpointNetworkPolicies')]", - "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", - "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", - "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", - "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", - "sharingScope": "[parameters('sharingScope')]" - } - }, - "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())]" - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "metadata": { - "description": "The IPAM pool prefix allocations for the subnet." - }, - "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), 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.35.1.17967", - "templateHash": "11179987886456111827" - }, - "name": "Virtual Network Peerings", - "description": "This module deploys a Virtual Network Peering." - }, - "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_subnets" - ] - }, - "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.35.1.17967", - "templateHash": "11179987886456111827" - }, - "name": "Virtual Network Peerings", - "description": "This module deploys a Virtual Network Peering." - }, - "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_subnets" - ] - } - }, - "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-05-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "nsgs" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "value": "[reference('virtualNetwork').outputs.name.value]" - }, - "resourceId": { - "type": "string", - "value": "[reference('virtualNetwork').outputs.resourceId.value]" - }, - "subnets": { - "type": "array", - "items": { - "$ref": "#/definitions/subnetOutputType" - }, - "copy": { - "count": "[length(parameters('subnets'))]", - "input": { - "name": "[parameters('subnets')[copyIndex()].name]", - "resourceId": "[reference('virtualNetwork').outputs.subnetResourceIds.value[copyIndex()]]", - "nsgName": "[if(not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup'))), tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), null())]", - "nsgResourceId": "[if(not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup'))), reference(format('nsgs[{0}]', copyIndex())).outputs.resourceId.value, null())]" - } - } - } - } - } - } - }, - "bastionHost": { - "condition": "[not(empty(parameters('bastionConfiguration')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-bastionHost', parameters('resourcesName'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[coalesce(tryGet(parameters('bastionConfiguration'), 'name'), format('bas-{0}', parameters('resourcesName')))]" - }, - "vnetId": { - "value": "[reference('virtualNetwork').outputs.resourceId.value]" - }, - "vnetName": { - "value": "[reference('virtualNetwork').outputs.name.value]" - }, - "location": { - "value": "[parameters('location')]" - }, - "logAnalyticsWorkspaceId": { - "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" - }, - "subnet": { - "value": "[tryGet(parameters('bastionConfiguration'), 'subnet')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "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.37.4.10188", - "templateHash": "3082168335446205769" - } - }, - "definitions": { - "bastionHostConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the Bastion Host resource." - } - }, - "subnet": { - "$ref": "#/definitions/subnetType", - "nullable": true, - "metadata": { - "description": "Optional. Subnet configuration for the Jumpbox VM." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Custom type definition for establishing Bastion Host for remote connection." - } - }, - "_1.networkSecurityGroupType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the network security group." - } - }, - "securityRules": { - "type": "array", - "items": { - "type": "object" - }, - "metadata": { - "description": "Required. The security rules for the network security group." - } - } - }, - "metadata": { - "description": "Custom type definition for network security group configuration", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" - } - } - }, - "subnetType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The Name of the subnet resource." - } - }, - "addressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. Prefixes for the subnet." - } - }, - "delegation": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The delegation to enable on the subnet." - } - }, - "privateEndpointNetworkPolicies": { - "type": "string", - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "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." - } - }, - "networkSecurityGroup": { - "$ref": "#/definitions/_1.networkSecurityGroupType", - "nullable": true, - "metadata": { - "description": "Optional. Network Security Group configuration for the subnet." - } - }, - "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." - } - } - }, - "metadata": { - "description": "Custom type definition for subnet configuration", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Name of the Azure Bastion Host resource." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Azure region to deploy resources." - } - }, - "vnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Virtual Network where the Azure Bastion Host will be deployed." - } - }, - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the Virtual Network where the Azure Bastion Host will be deployed." - } - }, - "logAnalyticsWorkspaceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Log Analytics Workspace for monitoring and diagnostics." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to the resources." - } - }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." - } - }, - "subnet": { - "$ref": "#/definitions/subnetType", - "nullable": true, - "metadata": { - "description": "Optional. Subnet configuration for the Jumpbox VM." - } - } - }, - "resources": { - "nsg": { - "condition": "[not(empty(parameters('subnet')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-{1}', parameters('vnetName'), tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('{0}-{1}', tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'), parameters('vnetName'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "securityRules": { - "value": "[tryGet(parameters('subnet'), 'networkSecurityGroup', 'securityRules')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "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.33.93.31351", - "templateHash": "2305747478751645177" - }, - "name": "Network Security Groups", - "description": "This module deploys a Network security Group (NSG)." - }, - "definitions": { - "securityRuleType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the security rule." - } - }, - "properties": { - "type": "object", - "properties": { - "access": { - "type": "string", - "allowedValues": [ - "Allow", - "Deny" - ], - "metadata": { - "description": "Required. Whether network traffic is allowed or denied." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The description of the security rule." - } - }, - "destinationAddressPrefix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." - } - }, - "destinationAddressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." - } - }, - "destinationApplicationSecurityGroupResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource IDs of the application security groups specified as destination." - } - }, - "destinationPortRange": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." - } - }, - "destinationPortRanges": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The destination port ranges." - } - }, - "direction": { - "type": "string", - "allowedValues": [ - "Inbound", - "Outbound" - ], - "metadata": { - "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." - } - }, - "priority": { - "type": "int", - "minValue": 100, - "maxValue": 4096, - "metadata": { - "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "*", - "Ah", - "Esp", - "Icmp", - "Tcp", - "Udp" - ], - "metadata": { - "description": "Required. Network protocol this rule applies to." - } - }, - "sourceAddressPrefix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." - } - }, - "sourceAddressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The CIDR or source IP ranges." - } - }, - "sourceApplicationSecurityGroupResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource IDs of the application security groups specified as source." - } - }, - "sourcePortRange": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." - } - }, - "sourcePortRanges": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The source port ranges." - } - } - }, - "metadata": { - "description": "Required. The properties of the security rule." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type of a security rule." - } - }, - "diagnosticSettingLogsOnlyType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. Name of the Network Security Group." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "securityRules": { - "type": "array", - "items": { - "$ref": "#/definitions/securityRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." - } - }, - "flushConnection": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingLogsOnlyType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" - } - } - } - } - }, - "networkSecurityGroup": { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "copy": [ - { - "name": "securityRules", - "count": "[length(coalesce(parameters('securityRules'), createArray()))]", - "input": { - "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", - "properties": { - "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", - "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", - "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", - "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", - "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", - "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", - "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", - "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", - "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", - "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", - "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", - "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", - "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", - "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", - "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" - } - } - } - ], - "flushConnection": "[parameters('flushConnection')]" - } - }, - "networkSecurityGroup_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/networkSecurityGroups/{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": [ - "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(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ - "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]" - } - } - } - } - }, - "bastionSubnet": { - "condition": "[not(empty(parameters('subnet')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('bastionSubnet-{0}', parameters('vnetName')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualNetworkName": { - "value": "[parameters('vnetName')]" - }, - "name": { - "value": "AzureBastionSubnet" - }, - "addressPrefixes": { - "value": "[tryGet(parameters('subnet'), 'addressPrefixes')]" - }, - "networkSecurityGroupResourceId": { - "value": "[reference('nsg').outputs.resourceId.value]" - }, - "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.35.1.17967", - "templateHash": "9728353654559466189" - }, - "name": "Virtual Network Subnets", - "description": "This module deploys a Virtual Network Subnet." - }, - "definitions": { - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. 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." - } - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "items": { - "type": "object" - }, - "nullable": true, - "metadata": { - "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", - "nullable": true, - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "metadata": { - "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." - } - }, - "privateLinkServiceNetworkPolicies": { - "type": "string", - "nullable": true, - "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 the 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 the 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": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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)))))]" - } - ], - "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { - "existing": true, - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-01-01", - "name": "[parameters('virtualNetworkName')]" - }, - "subnet": { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-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')]", - "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", - "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": "[parameters('privateEndpointNetworkPolicies')]", - "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", - "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", - "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", - "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", - "sharingScope": "[parameters('sharingScope')]" - } - }, - "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())]" - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "metadata": { - "description": "The IPAM pool prefix allocations for the subnet." - }, - "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]" - } - } - } - }, - "dependsOn": [ - "nsg" - ] - }, - "bastionHost": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('bastionHost-{0}-{1}', parameters('vnetName'), parameters('name')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[parameters('name')]" - }, - "skuName": { - "value": "Standard" - }, - "location": { - "value": "[parameters('location')]" - }, - "virtualNetworkResourceId": { - "value": "[parameters('vnetId')]" - }, - "diagnosticSettings": { - "value": [ - { - "name": "bastionDiagnostics", - "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", - "logCategoriesAndGroups": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ] - } - ] - }, - "tags": { - "value": "[parameters('tags')]" - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - }, - "publicIPAddressObject": { - "value": { - "name": "[format('pip-{0}', parameters('name'))]", - "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.33.93.31351", - "templateHash": "2586599138991803385" - }, - "name": "Bastion Hosts", - "description": "This module deploys a Bastion Host." - }, - "definitions": { - "diagnosticSettingLogsOnlyType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "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. This parameter is ignored when enablePrivateOnlyBastion is true." - } - }, - "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. This parameter is ignored when enablePrivateOnlyBastion is true." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingLogsOnlyType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "skuName": { - "type": "string", - "defaultValue": "Basic", - "allowedValues": [ - "Basic", - "Developer", - "Premium", - "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. For Basic and Developer SKU Copy/Paste is always enabled." - } - }, - "enableFileCopy": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Choose to disable or enable File Copy. Not supported for Basic and Developer SKU." - } - }, - "enableIpConnect": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Choose to disable or enable IP Connect. Not supported for Basic and Developer SKU." - } - }, - "enableKerberos": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Choose to disable or enable Kerberos authentication. Not supported for Developer SKU." - } - }, - "enableShareableLink": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Choose to disable or enable Shareable Link. Not supported for Basic and Developer SKU." - } - }, - "enableSessionRecording": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Choose to disable or enable Session Recording feature. The Premium SKU is required for this feature. If Session Recording is enabled, the Native client support will be disabled." - } - }, - "enablePrivateOnlyBastion": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Choose to disable or enable Private-only Bastion deployment. The Premium SKU is required for this feature." - } - }, - "scaleUnits": { - "type": "int", - "defaultValue": 2, - "metadata": { - "description": "Optional. The scale units for the Bastion Host resource. The Basic and Developer SKU only support 2 scale units." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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." - } - }, - "zones": { - "type": "array", - "items": { - "type": "int" - }, - "defaultValue": [], - "allowedValues": [ - 1, - 2, - 3 - ], - "metadata": { - "description": "Optional. A list of availability zones denoting where the Bastion Host resource needs to come from. This is not supported for the Developer SKU." - } - } - }, - "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)))))]" - } - ], - "enableReferencedModulesTelemetry": false, - "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.6.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" - } - } - } - } - }, - "azureBastion": { - "type": "Microsoft.Network/bastionHosts", - "apiVersion": "2024-05-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[coalesce(parameters('tags'), createObject())]", - "sku": { - "name": "[parameters('skuName')]" - }, - "zones": "[if(equals(parameters('skuName'), 'Developer'), createArray(), map(parameters('zones'), lambda('zone', string(lambdaVariables('zone')))))]", - "properties": "[union(createObject('scaleUnits', if(or(equals(parameters('skuName'), 'Basic'), equals(parameters('skuName'), 'Developer')), 2, parameters('scaleUnits')), 'ipConfigurations', if(equals(parameters('skuName'), 'Developer'), createArray(), createArray(createObject('name', 'IpConfAzureBastionSubnet', 'properties', union(createObject('subnet', createObject('id', format('{0}/subnets/AzureBastionSubnet', parameters('virtualNetworkResourceId')))), if(not(parameters('enablePrivateOnlyBastion')), createObject('publicIPAddress', createObject('id', if(not(empty(parameters('bastionSubnetPublicIpResourceId'))), parameters('bastionSubnetPublicIpResourceId'), reference('publicIPAddress').outputs.resourceId.value))), createObject())))))), if(equals(parameters('skuName'), 'Developer'), createObject('virtualNetwork', createObject('id', parameters('virtualNetworkResourceId'))), createObject()), if(or(or(equals(parameters('skuName'), 'Basic'), equals(parameters('skuName'), 'Standard')), equals(parameters('skuName'), 'Premium')), createObject('enableKerberos', parameters('enableKerberos')), createObject()), if(or(equals(parameters('skuName'), 'Standard'), equals(parameters('skuName'), 'Premium')), createObject('enableTunneling', if(equals(parameters('skuName'), 'Standard'), true(), if(parameters('enableSessionRecording'), false(), true())), 'disableCopyPaste', parameters('disableCopyPaste'), 'enableFileCopy', parameters('enableFileCopy'), 'enableIpConnect', parameters('enableIpConnect'), 'enableShareableLink', parameters('enableShareableLink')), createObject()), if(equals(parameters('skuName'), 'Premium'), createObject('enableSessionRecording', parameters('enableSessionRecording'), 'enablePrivateOnlyBastion', parameters('enablePrivateOnlyBastion')), 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": "[and(and(empty(parameters('bastionSubnetPublicIpResourceId')), not(equals(parameters('skuName'), 'Developer'))), not(parameters('enablePrivateOnlyBastion')))]", - "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": "[variables('enableReferencedModulesTelemetry')]" - }, - "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": "[coalesce(tryGet(parameters('publicIPAddressObject'), 'zones'), if(greater(length(parameters('zones')), 0), parameters('zones'), null()))]" - } - }, - "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.33.93.31351", - "templateHash": "5168739580767459761" - }, - "name": "Public IP Addresses", - "description": "This module deploys a Public IP Address." - }, - "definitions": { - "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" - ], - "nullable": true, - "metadata": { - "description": "Optional. 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." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "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." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "ipTagType": { - "type": "object", - "properties": { - "ipTagType": { - "type": "string", - "metadata": { - "description": "Required. The IP tag type." - } - }, - "tag": { - "type": "string", - "metadata": { - "description": "Required. The IP tag." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "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." - } - }, - "ipTags": { - "type": "array", - "items": { - "$ref": "#/definitions/ipTagType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The list of tags associated with the public IP address." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "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": "[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.8.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" - } - } - } - } - }, - "publicIpAddress": { - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2024-05-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": "[parameters('ipTags')]" - } - }, - "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', '2024-05-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', '2024-05-01', 'full').location]" - }, - "ipConfAzureBastionSubnet": { - "type": "object", - "metadata": { - "description": "The Public IPconfiguration object for the AzureBastionSubnet." - }, - "value": "[if(equals(parameters('skuName'), 'Developer'), createObject(), reference('azureBastion').ipConfigurations[0])]" - } - } - } - }, - "dependsOn": [ - "bastionSubnet" - ] - } - }, - "outputs": { - "resourceId": { - "type": "string", - "value": "[reference('bastionHost').outputs.resourceId.value]" - }, - "name": { - "type": "string", - "value": "[reference('bastionHost').outputs.name.value]" - }, - "subnetId": { - "type": "string", - "value": "[reference('bastionSubnet').outputs.resourceId.value]" - }, - "subnetName": { - "type": "string", - "value": "[reference('bastionSubnet').outputs.name.value]" - } - } - } - }, - "dependsOn": [ - "virtualNetwork" - ] - }, - "jumpbox": { - "condition": "[not(empty(parameters('jumpboxConfiguration')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-jumpbox', parameters('resourcesName'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'name'), format('vm-jumpbox-{0}', parameters('resourcesName')))]" - }, - "vnetName": { - "value": "[reference('virtualNetwork').outputs.name.value]" - }, - "size": { - "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'size'), 'Standard_D2s_v3')]" - }, - "logAnalyticsWorkspaceId": { - "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "subnet": { - "value": "[tryGet(parameters('jumpboxConfiguration'), 'subnet')]" - }, - "username": { - "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'username'), '')]" - }, - "password": { - "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'password'), '')]" - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - }, - "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.37.4.10188", - "templateHash": "40464970328764907" - } - }, - "definitions": { - "jumpBoxConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the Virtual Machine." - } - }, - "size": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The size of the VM." - } - }, - "username": { - "type": "string", - "metadata": { - "description": "Username to access VM." - } - }, - "password": { - "type": "securestring", - "metadata": { - "description": "Password to access VM." - } - }, - "subnet": { - "$ref": "#/definitions/subnetType", - "nullable": true, - "metadata": { - "description": "Optional. Subnet configuration for the Jumpbox VM." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Custom type definition for establishing Jumpbox Virtual Machine and its associated resources." - } - }, - "_1.networkSecurityGroupType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the network security group." - } - }, - "securityRules": { - "type": "array", - "items": { - "type": "object" - }, - "metadata": { - "description": "Required. The security rules for the network security group." - } - } - }, - "metadata": { - "description": "Custom type definition for network security group configuration", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" - } - } - }, - "subnetType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The Name of the subnet resource." - } - }, - "addressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. Prefixes for the subnet." - } - }, - "delegation": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The delegation to enable on the subnet." - } - }, - "privateEndpointNetworkPolicies": { - "type": "string", - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "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." - } - }, - "networkSecurityGroup": { - "$ref": "#/definitions/_1.networkSecurityGroupType", - "nullable": true, - "metadata": { - "description": "Optional. Network Security Group configuration for the subnet." - } - }, - "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." - } - } - }, - "metadata": { - "description": "Custom type definition for subnet configuration", - "__bicep_imported_from!": { - "sourceTemplate": "virtualNetwork.bicep" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Name of the Jumpbox Virtual Machine." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Azure region to deploy resources." - } - }, - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the Virtual Network where the Jumpbox VM will be deployed." - } - }, - "size": { - "type": "string", - "metadata": { - "description": "Size of the Jumpbox Virtual Machine." - } - }, - "subnet": { - "$ref": "#/definitions/subnetType", - "nullable": true, - "metadata": { - "description": "Optional. Subnet configuration for the Jumpbox VM." - } - }, - "username": { - "type": "string", - "metadata": { - "description": "Username to access the Jumpbox VM." - } - }, - "password": { - "type": "securestring", - "metadata": { - "description": "Password to access the Jumpbox VM." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to the resources." - } - }, - "logAnalyticsWorkspaceId": { - "type": "string", - "metadata": { - "description": "Log Analytics Workspace Resource ID for VM diagnostics." - } - }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." - } - } - }, - "variables": { - "vmName": "[take(parameters('name'), 15)]" - }, - "resources": { - "nsg": { - "condition": "[not(empty(parameters('subnet')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-{1}', parameters('vnetName'), tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('{0}-{1}', tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'), parameters('vnetName'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "securityRules": { - "value": "[tryGet(parameters('subnet'), 'networkSecurityGroup', 'securityRules')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "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.33.93.31351", - "templateHash": "2305747478751645177" - }, - "name": "Network Security Groups", - "description": "This module deploys a Network security Group (NSG)." - }, - "definitions": { - "securityRuleType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the security rule." - } - }, - "properties": { - "type": "object", - "properties": { - "access": { - "type": "string", - "allowedValues": [ - "Allow", - "Deny" - ], - "metadata": { - "description": "Required. Whether network traffic is allowed or denied." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The description of the security rule." - } - }, - "destinationAddressPrefix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." - } - }, - "destinationAddressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." - } - }, - "destinationApplicationSecurityGroupResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource IDs of the application security groups specified as destination." - } - }, - "destinationPortRange": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." - } - }, - "destinationPortRanges": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The destination port ranges." - } - }, - "direction": { - "type": "string", - "allowedValues": [ - "Inbound", - "Outbound" - ], - "metadata": { - "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." - } - }, - "priority": { - "type": "int", - "minValue": 100, - "maxValue": 4096, - "metadata": { - "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "*", - "Ah", - "Esp", - "Icmp", - "Tcp", - "Udp" - ], - "metadata": { - "description": "Required. Network protocol this rule applies to." - } - }, - "sourceAddressPrefix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." - } - }, - "sourceAddressPrefixes": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The CIDR or source IP ranges." - } - }, - "sourceApplicationSecurityGroupResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource IDs of the application security groups specified as source." - } - }, - "sourcePortRange": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." - } - }, - "sourcePortRanges": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The source port ranges." - } - } - }, - "metadata": { - "description": "Required. The properties of the security rule." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type of a security rule." - } - }, - "diagnosticSettingLogsOnlyType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. Name of the Network Security Group." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "securityRules": { - "type": "array", - "items": { - "$ref": "#/definitions/securityRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." - } - }, - "flushConnection": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingLogsOnlyType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" - } - } - } - } - }, - "networkSecurityGroup": { - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "copy": [ - { - "name": "securityRules", - "count": "[length(coalesce(parameters('securityRules'), createArray()))]", - "input": { - "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", - "properties": { - "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", - "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", - "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", - "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", - "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", - "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", - "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", - "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", - "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", - "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", - "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", - "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", - "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", - "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", - "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" - } - } - } - ], - "flushConnection": "[parameters('flushConnection')]" - } - }, - "networkSecurityGroup_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/networkSecurityGroups/{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": [ - "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(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ - "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]" - } - } - } - } - }, - "subnetResource": { - "condition": "[not(empty(parameters('subnet')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[coalesce(tryGet(parameters('subnet'), 'name'), format('{0}-jumpbox-subnet', parameters('vnetName')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualNetworkName": { - "value": "[parameters('vnetName')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('subnet'), 'name'), '')]" - }, - "addressPrefixes": { - "value": "[tryGet(parameters('subnet'), 'addressPrefixes')]" - }, - "networkSecurityGroupResourceId": { - "value": "[reference('nsg').outputs.resourceId.value]" - }, - "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.35.1.17967", - "templateHash": "9728353654559466189" - }, - "name": "Virtual Network Subnets", - "description": "This module deploys a Virtual Network Subnet." - }, - "definitions": { - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. 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." - } - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "items": { - "type": "object" - }, - "nullable": true, - "metadata": { - "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", - "nullable": true, - "allowedValues": [ - "Disabled", - "Enabled", - "NetworkSecurityGroupEnabled", - "RouteTableEnabled" - ], - "metadata": { - "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." - } - }, - "privateLinkServiceNetworkPolicies": { - "type": "string", - "nullable": true, - "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 the 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 the 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": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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)))))]" - } - ], - "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { - "existing": true, - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2024-01-01", - "name": "[parameters('virtualNetworkName')]" - }, - "subnet": { - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2024-05-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')]", - "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", - "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": "[parameters('privateEndpointNetworkPolicies')]", - "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", - "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", - "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", - "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", - "sharingScope": "[parameters('sharingScope')]" - } - }, - "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())]" - }, - "ipamPoolPrefixAllocations": { - "type": "array", - "metadata": { - "description": "The IPAM pool prefix allocations for the subnet." - }, - "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]" - } - } - } - }, - "dependsOn": [ - "nsg" - ] - }, - "vm": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('{0}-jumpbox', variables('vmName')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[variables('vmName')]" - }, - "vmSize": { - "value": "[parameters('size')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "adminUsername": { - "value": "[parameters('username')]" - }, - "adminPassword": { - "value": "[parameters('password')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "zone": { - "value": 0 - }, - "imageReference": { - "value": { - "offer": "WindowsServer", - "publisher": "MicrosoftWindowsServer", - "sku": "2019-datacenter", - "version": "latest" - } - }, - "osType": { - "value": "Windows" - }, - "osDisk": { - "value": { - "name": "[format('osdisk-{0}', variables('vmName'))]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - } - } - }, - "encryptionAtHost": { - "value": false - }, - "nicConfigurations": { - "value": [ - { - "name": "[format('nic-{0}', variables('vmName'))]", - "ipConfigurations": [ - { - "name": "ipconfig1", - "subnetResourceId": "[reference('subnetResource').outputs.resourceId.value]" - } - ], - "networkSecurityGroupResourceId": "[reference('nsg').outputs.resourceId.value]", - "diagnosticSettings": [ - { - "name": "jumpboxDiagnostics", - "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", - "logCategoriesAndGroups": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "metricCategories": [ - { - "category": "AllMetrics", - "enabled": true - } - ] - } - ] - } - ] - }, - "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.34.44.8038", - "templateHash": "1057634180502804806" - }, - "name": "Virtual Machines", - "description": "This module deploys a Virtual Machine with one or multiple NICs and optionally one or multiple public IPs." - }, - "definitions": { - "osDiskType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The disk name." - } - }, - "diskSizeGB": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the size of an empty data disk in gigabytes." - } - }, - "createOption": { - "type": "string", - "allowedValues": [ - "Attach", - "Empty", - "FromImage" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies how the virtual machine should be created." - } - }, - "deleteOption": { - "type": "string", - "allowedValues": [ - "Delete", - "Detach" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion." - } - }, - "caching": { - "type": "string", - "allowedValues": [ - "None", - "ReadOnly", - "ReadWrite" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies the caching requirements." - } - }, - "diffDiskSettings": { - "type": "object", - "properties": { - "placement": { - "type": "string", - "allowedValues": [ - "CacheDisk", - "NvmeDisk", - "ResourceDisk" - ], - "metadata": { - "description": "Required. Specifies the ephemeral disk placement for the operating system disk." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Specifies the ephemeral Disk Settings for the operating system disk." - } - }, - "managedDisk": { - "type": "object", - "properties": { - "storageAccountType": { - "type": "string", - "allowedValues": [ - "PremiumV2_LRS", - "Premium_LRS", - "Premium_ZRS", - "StandardSSD_LRS", - "StandardSSD_ZRS", - "Standard_LRS", - "UltraSSD_LRS" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies the storage account type for the managed disk." - } - }, - "diskEncryptionSetResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." - } - } - }, - "metadata": { - "description": "Required. The managed disk parameters." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing an OS disk." - } - }, - "dataDiskType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The disk name. When attaching a pre-existing disk, this name is ignored and the name of the existing disk is used." - } - }, - "lun": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the logical unit number of the data disk." - } - }, - "diskSizeGB": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the size of an empty data disk in gigabytes. This property is ignored when attaching a pre-existing disk." - } - }, - "createOption": { - "type": "string", - "allowedValues": [ - "Attach", - "Empty", - "FromImage" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies how the virtual machine should be created. This property is automatically set to 'Attach' when attaching a pre-existing disk." - } - }, - "deleteOption": { - "type": "string", - "allowedValues": [ - "Delete", - "Detach" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion. This property is automatically set to 'Detach' when attaching a pre-existing disk." - } - }, - "caching": { - "type": "string", - "allowedValues": [ - "None", - "ReadOnly", - "ReadWrite" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies the caching requirements. This property is automatically set to 'None' when attaching a pre-existing disk." - } - }, - "diskIOPSReadWrite": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The number of IOPS allowed for this disk; only settable for UltraSSD disks. One operation can transfer between 4k and 256k bytes. Ignored when attaching a pre-existing disk." - } - }, - "diskMBpsReadWrite": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The bandwidth allowed for this disk; only settable for UltraSSD disks. MBps means millions of bytes per second - MB here uses the ISO notation, of powers of 10. Ignored when attaching a pre-existing disk." - } - }, - "managedDisk": { - "type": "object", - "properties": { - "storageAccountType": { - "type": "string", - "allowedValues": [ - "PremiumV2_LRS", - "Premium_LRS", - "Premium_ZRS", - "StandardSSD_LRS", - "StandardSSD_ZRS", - "Standard_LRS", - "UltraSSD_LRS" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies the storage account type for the managed disk. Ignored when attaching a pre-existing disk." - } - }, - "diskEncryptionSetResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." - } - }, - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the resource id of a pre-existing managed disk. If the disk should be created, this property should be empty." - } - } - }, - "metadata": { - "description": "Required. The managed disk parameters." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The tags of the public IP address. Valid only when creating a new managed disk." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing a data disk." - } - }, - "publicKeyType": { - "type": "object", - "properties": { - "keyData": { - "type": "string", - "metadata": { - "description": "Required. Specifies the SSH public key data used to authenticate through ssh." - } - }, - "path": { - "type": "string", - "metadata": { - "description": "Required. Specifies the full path on the created VM where ssh public key is stored. If the file already exists, the specified key is appended to the file." - } - } - } - }, - "nicConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the NIC configuration." - } - }, - "nicSuffix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The suffix to append to the NIC name." - } - }, - "enableIPForwarding": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Indicates whether IP forwarding is enabled on this network interface." - } - }, - "enableAcceleratedNetworking": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. If the network interface is accelerated networking enabled." - } - }, - "deleteOption": { - "type": "string", - "allowedValues": [ - "Delete", - "Detach" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specify what happens to the network interface when the VM is deleted." - } - }, - "dnsServers": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. List of DNS servers IP addresses. Use 'AzureProvidedDNS' to switch to azure provided DNS resolution. 'AzureProvidedDNS' value cannot be combined with other IPs, it must be the only value in dnsServers collection." - } - }, - "networkSecurityGroupResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The network security group (NSG) to attach to the network interface." - } - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/ipConfigurationType" - }, - "metadata": { - "description": "Required. The IP configurations of the network interface." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The tags of the public IP address." - } - }, - "enableTelemetry": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for the module." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the IP configuration." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the NIC configuration." - } - }, - "imageReferenceType": { - "type": "object", - "properties": { - "communityGalleryImageId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specified the community gallery image unique id for vm deployment. This can be fetched from community gallery image GET call." - } - }, - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource Id of the image reference." - } - }, - "offer": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the offer of the platform image or marketplace image used to create the virtual machine." - } - }, - "publisher": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The image publisher." - } - }, - "sku": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The SKU of the image." - } - }, - "version": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the version of the platform image or marketplace image used to create the virtual machine. The allowed formats are Major.Minor.Build or 'latest'. Even if you use 'latest', the VM image will not automatically update after deploy time even if a new version becomes available." - } - }, - "sharedGalleryImageId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specified the shared gallery image unique id for vm deployment. This can be fetched from shared gallery image GET call." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing the image reference." - } - }, - "planType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the plan." - } - }, - "product": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the product of the image from the marketplace." - } - }, - "publisher": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The publisher ID." - } - }, - "promotionCode": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The promotion code." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Specifies information about the marketplace image used to create the virtual machine." - } - }, - "autoShutDownConfigType": { - "type": "object", - "properties": { - "status": { - "type": "string", - "allowedValues": [ - "Disabled", - "Enabled" - ], - "nullable": true, - "metadata": { - "description": "Optional. The status of the auto shutdown configuration." - } - }, - "timeZone": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The time zone ID (e.g. China Standard Time, Greenland Standard Time, Pacific Standard time, etc.)." - } - }, - "dailyRecurrenceTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The time of day the schedule will occur." - } - }, - "notificationSettings": { - "type": "object", - "properties": { - "status": { - "type": "string", - "allowedValues": [ - "Disabled", - "Enabled" - ], - "nullable": true, - "metadata": { - "description": "Optional. The status of the notification settings." - } - }, - "emailRecipient": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The email address to send notifications to (can be a list of semi-colon separated email addresses)." - } - }, - "notificationLocale": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." - } - }, - "webhookUrl": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The webhook URL to which the notification will be sent." - } - }, - "timeInMinutes": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The time in minutes before shutdown to send notifications." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the schedule." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing the configuration profile." - } - }, - "vaultSecretGroupType": { - "type": "object", - "properties": { - "sourceVault": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. The relative URL of the Key Vault containing all of the certificates in VaultCertificates." - } - }, - "vaultCertificates": { - "type": "array", - "items": { - "type": "object", - "properties": { - "certificateStore": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. For Windows VMs, specifies the certificate store on the Virtual Machine to which the certificate should be added. The specified certificate store is implicitly in the LocalMachine account. For Linux VMs, the certificate file is placed under the /var/lib/waagent directory, with the file name .crt for the X509 certificate file and .prv for private key. Both of these files are .pem formatted." - } - }, - "certificateUrl": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. This is the URL of a certificate that has been uploaded to Key Vault as a secret." - } - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. The list of key vault references in SourceVault which contain certificates." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing the set of certificates that should be installed onto the virtual machine." - } - }, - "vmGalleryApplicationType": { - "type": "object", - "properties": { - "packageReferenceId": { - "type": "string", - "metadata": { - "description": "Required. Specifies the GalleryApplicationVersion resource id on the form of /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Compute/galleries/{galleryName}/applications/{application}/versions/{version}." - } - }, - "configurationReference": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the uri to an azure blob that will replace the default configuration for the package if provided." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. If set to true, when a new Gallery Application version is available in PIR/SIG, it will be automatically updated for the VM/VMSS." - } - }, - "order": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the order in which the packages have to be installed." - } - }, - "tags": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies a passthrough value for more generic context." - } - }, - "treatFailureAsDeploymentFailure": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. If true, any failure for any operation in the VmApplication will fail the deployment." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing the gallery application that should be made available to the VM/VMSS." - } - }, - "additionalUnattendContentType": { - "type": "object", - "properties": { - "settingName": { - "type": "string", - "allowedValues": [ - "AutoLogon", - "FirstLogonCommands" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies the name of the setting to which the content applies." - } - }, - "content": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specifies the XML formatted content that is added to the unattend.xml file for the specified path and component. The XML must be less than 4KB and must include the root element for the setting or feature that is being inserted." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing additional base-64 encoded XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup." - } - }, - "winRMListenerType": { - "type": "object", - "properties": { - "certificateUrl": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The URL of a certificate that has been uploaded to Key Vault as a secret." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "Http", - "Https" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specifies the protocol of WinRM listener." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing a Windows Remote Management listener." - } - }, - "nicConfigurationOutputType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the NIC configuration." - } - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" - }, - "metadata": { - "description": "Required. List of IP configurations of the NIC configuration." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type describing the network interface configuration output." - } - }, - "_1.applicationGatewayBackendAddressPoolsType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the backend address pool." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." - } - }, - "properties": { - "type": "object", - "properties": { - "backendAddresses": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipAddress": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. IP address of the backend address." - } - }, - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN of the backend address." - } - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Backend addresses." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Properties of the application gateway backend address pool." - } - } - }, - "metadata": { - "description": "The type for the application gateway backend address pool.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "_1.applicationSecurityGroupType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the application security group." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Location of the application security group." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Properties of the application security group." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the application security group." - } - } - }, - "metadata": { - "description": "The type for the application security group.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "_1.backendAddressPoolType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the backend address pool." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the backend address pool." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The properties of the backend address pool." - } - } - }, - "metadata": { - "description": "The type for a backend address pool.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "_1.inboundNatRuleType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the inbound NAT rule." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." - } - }, - "properties": { - "type": "object", - "properties": { - "backendAddressPool": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. A reference to backendAddressPool resource." - } - }, - "backendPort": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." - } - }, - "enableFloatingIP": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." - } - }, - "enableTcpReset": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." - } - }, - "frontendIPConfiguration": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. A reference to frontend IP addresses." - } - }, - "frontendPort": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." - } - }, - "frontendPortRangeStart": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." - } - }, - "frontendPortRangeEnd": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "All", - "Tcp", - "Udp" - ], - "nullable": true, - "metadata": { - "description": "Optional. The reference to the transport protocol used by the load balancing rule." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Properties of the inbound NAT rule." - } - } - }, - "metadata": { - "description": "The type for the inbound NAT rule.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "_1.virtualNetworkTapType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the virtual network tap." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Location of the virtual network tap." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Properties of the virtual network tap." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the virtual network tap." - } - } - }, - "metadata": { - "description": "The type for the virtual network tap.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "_2.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." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" - } - } - }, - "_2.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" - ], - "nullable": true, - "metadata": { - "description": "Optional. 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." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" - } - } - }, - "_3.publicIPConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the Public IP Address." - } - }, - "publicIPAddressResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the public IP address." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Diagnostic settings for the public IP address." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The idle timeout in minutes." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the public IP address." - } - }, - "idleTimeoutInMinutes": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The idle timeout of the public IP address." - } - }, - "ddosSettings": { - "$ref": "#/definitions/_2.ddosSettingsType", - "nullable": true, - "metadata": { - "description": "Optional. The DDoS protection plan configuration associated with the public IP address." - } - }, - "dnsSettings": { - "$ref": "#/definitions/_2.dnsSettingsType", - "nullable": true, - "metadata": { - "description": "Optional. The DNS settings of the public IP address." - } - }, - "publicIPAddressVersion": { - "type": "string", - "allowedValues": [ - "IPv4", - "IPv6" - ], - "nullable": true, - "metadata": { - "description": "Optional. The public IP address version." - } - }, - "publicIPAllocationMethod": { - "type": "string", - "allowedValues": [ - "Dynamic", - "Static" - ], - "nullable": true, - "metadata": { - "description": "Optional. The public IP address allocation method." - } - }, - "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." - } - }, - "publicIpNameSuffix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name suffix of the public IP address resource." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "skuName": { - "type": "string", - "allowedValues": [ - "Basic", - "Standard" - ], - "nullable": true, - "metadata": { - "description": "Optional. The SKU name of the public IP address." - } - }, - "skuTier": { - "type": "string", - "allowedValues": [ - "Global", - "Regional" - ], - "nullable": true, - "metadata": { - "description": "Optional. The SKU tier of the public IP address." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The tags of the public IP address." - } - }, - "zones": { - "type": "array", - "allowedValues": [ - 1, - 2, - 3 - ], - "nullable": true, - "metadata": { - "description": "Optional. The zones of the public IP address." - } - }, - "enableTelemetry": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for the module." - } - } - }, - "metadata": { - "description": "The type for the public IP address configuration.", - "__bicep_imported_from!": { - "sourceTemplate": "modules/nic-configuration.bicep" - } - } - }, - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "ipConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the IP configuration." - } - }, - "privateIPAllocationMethod": { - "type": "string", - "allowedValues": [ - "Dynamic", - "Static" - ], - "nullable": true, - "metadata": { - "description": "Optional. The private IP address allocation method." - } - }, - "privateIPAddress": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The private IP address." - } - }, - "subnetResourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the subnet." - } - }, - "loadBalancerBackendAddressPools": { - "type": "array", - "items": { - "$ref": "#/definitions/_1.backendAddressPoolType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The load balancer backend address pools." - } - }, - "applicationSecurityGroups": { - "type": "array", - "items": { - "$ref": "#/definitions/_1.applicationSecurityGroupType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The application security groups." - } - }, - "applicationGatewayBackendAddressPools": { - "type": "array", - "items": { - "$ref": "#/definitions/_1.applicationGatewayBackendAddressPoolsType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The application gateway backend address pools." - } - }, - "gatewayLoadBalancer": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. The gateway load balancer settings." - } - }, - "loadBalancerInboundNatRules": { - "type": "array", - "items": { - "$ref": "#/definitions/_1.inboundNatRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The load balancer inbound NAT rules." - } - }, - "privateIPAddressVersion": { - "type": "string", - "allowedValues": [ - "IPv4", - "IPv6" - ], - "nullable": true, - "metadata": { - "description": "Optional. The private IP address version." - } - }, - "virtualNetworkTaps": { - "type": "array", - "items": { - "$ref": "#/definitions/_1.virtualNetworkTapType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The virtual network taps." - } - }, - "pipConfiguration": { - "$ref": "#/definitions/_3.publicIPConfigurationType", - "nullable": true, - "metadata": { - "description": "Optional. The public IP address configuration." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the IP configuration." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The tags of the public IP address." - } - }, - "enableTelemetry": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for the module." - } - } - }, - "metadata": { - "description": "The type for the IP configuration.", - "__bicep_imported_from!": { - "sourceTemplate": "modules/nic-configuration.bicep" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "managedIdentityAllType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "networkInterfaceIPConfigurationOutputType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the IP configuration." - } - }, - "privateIP": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The private IP address." - } - }, - "publicIP": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The public IP address." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "subResourceType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the sub resource." - } - } - }, - "metadata": { - "description": "The type for the sub resource.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine to be created. You should use a unique prefix to reduce name collisions in Active Directory." - } - }, - "computerName": { - "type": "string", - "defaultValue": "[parameters('name')]", - "metadata": { - "description": "Optional. Can be used if the computer name needs to be different from the Azure VM resource name. If not used, the resource name will be used as computer name." - } - }, - "vmSize": { - "type": "string", - "metadata": { - "description": "Required. Specifies the size for the VMs." - } - }, - "encryptionAtHost": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine. This will enable the encryption for all the disks including Resource/Temp disk at host itself. For security reasons, it is recommended to set encryptionAtHost to True. Restrictions: Cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." - } - }, - "securityType": { - "type": "string", - "defaultValue": "", - "allowedValues": [ - "", - "ConfidentialVM", - "TrustedLaunch" - ], - "metadata": { - "description": "Optional. Specifies the SecurityType of the virtual machine. It has to be set to any specified value to enable UefiSettings. The default behavior is: UefiSettings will not be enabled unless this property is set." - } - }, - "secureBootEnabled": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Specifies whether secure boot should be enabled on the virtual machine. This parameter is part of the UefiSettings. SecurityType should be set to TrustedLaunch to enable UefiSettings." - } - }, - "vTpmEnabled": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Specifies whether vTPM should be enabled on the virtual machine. This parameter is part of the UefiSettings. SecurityType should be set to TrustedLaunch to enable UefiSettings." - } - }, - "imageReference": { - "$ref": "#/definitions/imageReferenceType", - "metadata": { - "description": "Required. OS image reference. In case of marketplace images, it's the combination of the publisher, offer, sku, version attributes. In case of custom images it's the resource ID of the custom image." - } - }, - "plan": { - "$ref": "#/definitions/planType", - "nullable": true, - "metadata": { - "description": "Optional. Specifies information about the marketplace image used to create the virtual machine. This element is only used for marketplace images. Before you can use a marketplace image from an API, you must enable the image for programmatic use." - } - }, - "osDisk": { - "$ref": "#/definitions/osDiskType", - "metadata": { - "description": "Required. Specifies the OS disk. For security reasons, it is recommended to specify DiskEncryptionSet into the osDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." - } - }, - "dataDisks": { - "type": "array", - "items": { - "$ref": "#/definitions/dataDiskType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Specifies the data disks. For security reasons, it is recommended to specify DiskEncryptionSet into the dataDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." - } - }, - "ultraSSDEnabled": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. The flag that enables or disables a capability to have one or more managed data disks with UltraSSD_LRS storage account type on the VM or VMSS. Managed disks with storage account type UltraSSD_LRS can be added to a virtual machine or virtual machine scale set only if this property is enabled." - } - }, - "hibernationEnabled": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. The flag that enables or disables hibernation capability on the VM." - } - }, - "adminUsername": { - "type": "securestring", - "metadata": { - "description": "Required. Administrator username." - } - }, - "adminPassword": { - "type": "securestring", - "defaultValue": "", - "metadata": { - "description": "Optional. When specifying a Windows Virtual Machine, this value should be passed." - } - }, - "userData": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. UserData for the VM, which must be base-64 encoded. Customer should not pass any secrets in here." - } - }, - "customData": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom data associated to the VM, this value will be automatically converted into base64 to account for the expected VM format." - } - }, - "certificatesToBeInstalled": { - "type": "array", - "items": { - "$ref": "#/definitions/vaultSecretGroupType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Specifies set of certificates that should be installed onto the virtual machine." - } - }, - "priority": { - "type": "string", - "nullable": true, - "allowedValues": [ - "Regular", - "Low", - "Spot" - ], - "metadata": { - "description": "Optional. Specifies the priority for the virtual machine." - } - }, - "evictionPolicy": { - "type": "string", - "defaultValue": "Deallocate", - "allowedValues": [ - "Deallocate", - "Delete" - ], - "metadata": { - "description": "Optional. Specifies the eviction policy for the low priority virtual machine." - } - }, - "maxPriceForLowPriorityVm": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Specifies the maximum price you are willing to pay for a low priority VM/VMSS. This price is in US Dollars." - } - }, - "dedicatedHostId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Specifies resource ID about the dedicated host that the virtual machine resides in." - } - }, - "licenseType": { - "type": "string", - "defaultValue": "", - "allowedValues": [ - "RHEL_BYOS", - "SLES_BYOS", - "Windows_Client", - "Windows_Server", - "" - ], - "metadata": { - "description": "Optional. Specifies that the image or disk that is being used was licensed on-premises." - } - }, - "publicKeys": { - "type": "array", - "items": { - "$ref": "#/definitions/publicKeyType" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. The list of SSH public keys used to authenticate with linux based VMs." - } - }, - "managedIdentities": { - "$ref": "#/definitions/managedIdentityAllType", - "nullable": true, - "metadata": { - "description": "Optional. The managed identity definition for this resource. The system-assigned managed identity will automatically be enabled if extensionAadJoinConfig.enabled = \"True\"." - } - }, - "bootDiagnostics": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Whether boot diagnostics should be enabled on the Virtual Machine. Boot diagnostics will be enabled with a managed storage account if no bootDiagnosticsStorageAccountName value is provided. If bootDiagnostics and bootDiagnosticsStorageAccountName values are not provided, boot diagnostics will be disabled." - } - }, - "bootDiagnosticStorageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom storage account used to store boot diagnostic information. Boot diagnostics will be enabled with a custom storage account if a value is provided." - } - }, - "bootDiagnosticStorageAccountUri": { - "type": "string", - "defaultValue": "[format('.blob.{0}/', environment().suffixes.storage)]", - "metadata": { - "description": "Optional. Storage account boot diagnostic base URI." - } - }, - "proximityPlacementGroupResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Resource ID of a proximity placement group." - } - }, - "virtualMachineScaleSetResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Resource ID of a virtual machine scale set, where the VM should be added." - } - }, - "availabilitySetResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Resource ID of an availability set. Cannot be used in combination with availability zone nor scale set." - } - }, - "galleryApplications": { - "type": "array", - "items": { - "$ref": "#/definitions/vmGalleryApplicationType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Specifies the gallery applications that should be made available to the VM/VMSS." - } - }, - "zone": { - "type": "int", - "allowedValues": [ - 0, - 1, - 2, - 3 - ], - "metadata": { - "description": "Required. If set to 1, 2 or 3, the availability zone for all VMs is hardcoded to that value. If zero, then availability zones is not used. Cannot be used in combination with availability set nor scale set." - } - }, - "nicConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/nicConfigurationType" - }, - "metadata": { - "description": "Required. Configures NICs and PIPs." - } - }, - "backupVaultName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Recovery service vault name to add VMs to backup." - } - }, - "backupVaultResourceGroup": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Optional. Resource group of the backup recovery service vault. If not provided the current resource group name is considered by default." - } - }, - "backupPolicyName": { - "type": "string", - "defaultValue": "DefaultPolicy", - "metadata": { - "description": "Optional. Backup policy the VMs should be using for backup. If not provided, it will use the DefaultPolicy from the backup recovery service vault." - } - }, - "autoShutdownConfig": { - "$ref": "#/definitions/autoShutDownConfigType", - "defaultValue": {}, - "metadata": { - "description": "Optional. The configuration for auto-shutdown." - } - }, - "maintenanceConfigurationResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The resource Id of a maintenance configuration for this VM." - } - }, - "allowExtensionOperations": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Specifies whether extension operations should be allowed on the virtual machine. This may only be set to False when no extensions are present on the virtual machine." - } - }, - "extensionDomainJoinPassword": { - "type": "securestring", - "defaultValue": "", - "metadata": { - "description": "Optional. Required if name is specified. Password of the user specified in user parameter." - } - }, - "extensionDomainJoinConfig": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. The configuration for the [Domain Join] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionAadJoinConfig": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [AAD Join] extension. Must at least contain the [\"enabled\": true] property to be executed. To enroll in Intune, add the setting mdmId: \"0000000a-0000-0000-c000-000000000000\"." - } - }, - "extensionAntiMalwareConfig": { - "type": "object", - "defaultValue": "[if(equals(parameters('osType'), 'Windows'), createObject('enabled', true()), createObject('enabled', false()))]", - "metadata": { - "description": "Optional. The configuration for the [Anti Malware] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionMonitoringAgentConfig": { - "type": "object", - "defaultValue": { - "enabled": false, - "dataCollectionRuleAssociations": [] - }, - "metadata": { - "description": "Optional. The configuration for the [Monitoring Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionDependencyAgentConfig": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Dependency Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionNetworkWatcherAgentConfig": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Network Watcher Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionAzureDiskEncryptionConfig": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Azure Disk Encryption] extension. Must at least contain the [\"enabled\": true] property to be executed. Restrictions: Cannot be enabled on disks that have encryption at host enabled. Managed disks encrypted using Azure Disk Encryption cannot be encrypted using customer-managed keys." - } - }, - "extensionDSCConfig": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Desired State Configuration] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionCustomScriptConfig": { - "type": "object", - "defaultValue": { - "enabled": false, - "fileData": [] - }, - "metadata": { - "description": "Optional. The configuration for the [Custom Script] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionNvidiaGpuDriverWindows": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Nvidia Gpu Driver Windows] extension. Must at least contain the [\"enabled\": true] property to be executed." - } - }, - "extensionHostPoolRegistration": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Host Pool Registration] extension. Must at least contain the [\"enabled\": true] property to be executed. Needs a managed identy." - } - }, - "extensionGuestConfigurationExtension": { - "type": "object", - "defaultValue": { - "enabled": false - }, - "metadata": { - "description": "Optional. The configuration for the [Guest Configuration] extension. Must at least contain the [\"enabled\": true] property to be executed. Needs a managed identy." - } - }, - "guestConfiguration": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. The guest configuration for the virtual machine. Needs the Guest Configuration extension to be enabled." - } - }, - "extensionCustomScriptProtectedSetting": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. An object that contains the extension specific protected settings." - } - }, - "extensionGuestConfigurationExtensionProtectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. An object that contains the extension specific protected settings." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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." - } - }, - "baseTime": { - "type": "string", - "defaultValue": "[utcNow('u')]", - "metadata": { - "description": "Generated. Do not provide a value! This date value is used to generate a registration token." - } - }, - "sasTokenValidityLength": { - "type": "string", - "defaultValue": "PT8H", - "metadata": { - "description": "Optional. SAS token validity length to use to download files from storage accounts. Usage: 'PT8H' - valid for 8 hours; 'P5D' - valid for 5 days; 'P1Y' - valid for 1 year. When not provided, the SAS token will be valid for 8 hours." - } - }, - "osType": { - "type": "string", - "allowedValues": [ - "Windows", - "Linux" - ], - "metadata": { - "description": "Required. The chosen OS type." - } - }, - "disablePasswordAuthentication": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Specifies whether password authentication should be disabled." - } - }, - "provisionVMAgent": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Indicates whether virtual machine agent should be provisioned on the virtual machine. When this property is not specified in the request body, default behavior is to set it to true. This will ensure that VM Agent is installed on the VM so that extensions can be added to the VM later." - } - }, - "enableAutomaticUpdates": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Indicates whether Automatic Updates is enabled for the Windows virtual machine. Default value is true. When patchMode is set to Manual, this parameter must be set to false. For virtual machine scale sets, this property can be updated and updates will take effect on OS reprovisioning." - } - }, - "patchMode": { - "type": "string", - "defaultValue": "", - "allowedValues": [ - "AutomaticByPlatform", - "AutomaticByOS", - "Manual", - "ImageDefault", - "" - ], - "metadata": { - "description": "Optional. VM guest patching orchestration mode. 'AutomaticByOS' & 'Manual' are for Windows only, 'ImageDefault' for Linux only. Refer to 'https://learn.microsoft.com/en-us/azure/virtual-machines/automatic-vm-guest-patching'." - } - }, - "bypassPlatformSafetyChecksOnUserSchedule": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enables customer to schedule patching without accidental upgrades." - } - }, - "rebootSetting": { - "type": "string", - "defaultValue": "IfRequired", - "allowedValues": [ - "Always", - "IfRequired", - "Never", - "Unknown" - ], - "metadata": { - "description": "Optional. Specifies the reboot setting for all AutomaticByPlatform patch installation operations." - } - }, - "patchAssessmentMode": { - "type": "string", - "defaultValue": "ImageDefault", - "allowedValues": [ - "AutomaticByPlatform", - "ImageDefault" - ], - "metadata": { - "description": "Optional. VM guest patching assessment mode. Set it to 'AutomaticByPlatform' to enable automatically check for updates every 24 hours." - } - }, - "enableHotpatching": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Enables customers to patch their Azure VMs without requiring a reboot. For enableHotpatching, the 'provisionVMAgent' must be set to true and 'patchMode' must be set to 'AutomaticByPlatform'." - } - }, - "timeZone": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Specifies the time zone of the virtual machine. e.g. 'Pacific Standard Time'. Possible values can be `TimeZoneInfo.id` value from time zones returned by `TimeZoneInfo.GetSystemTimeZones`." - } - }, - "additionalUnattendContent": { - "type": "array", - "items": { - "$ref": "#/definitions/additionalUnattendContentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Specifies additional XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup. Contents are defined by setting name, component name, and the pass in which the content is applied." - } - }, - "winRMListeners": { - "type": "array", - "items": { - "$ref": "#/definitions/winRMListenerType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Specifies the Windows Remote Management listeners. This enables remote Windows PowerShell." - } - }, - "configurationProfile": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The configuration profile of automanage. Either '/providers/Microsoft.Automanage/bestPractices/AzureBestPracticesProduction', 'providers/Microsoft.Automanage/bestPractices/AzureBestPracticesDevTest' or the resource Id of custom profile." - } - } - }, - "variables": { - "copy": [ - { - "name": "publicKeysFormatted", - "count": "[length(parameters('publicKeys'))]", - "input": { - "path": "[parameters('publicKeys')[copyIndex('publicKeysFormatted')].path]", - "keyData": "[parameters('publicKeys')[copyIndex('publicKeysFormatted')].keyData]" - } - }, - { - "name": "additionalUnattendContentFormatted", - "count": "[length(coalesce(parameters('additionalUnattendContent'), createArray()))]", - "input": { - "settingName": "[coalesce(parameters('additionalUnattendContent'), createArray())[copyIndex('additionalUnattendContentFormatted')].settingName]", - "content": "[coalesce(parameters('additionalUnattendContent'), createArray())[copyIndex('additionalUnattendContentFormatted')].content]", - "componentName": "Microsoft-Windows-Shell-Setup", - "passName": "OobeSystem" - } - }, - { - "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)))))]" - } - ], - "enableReferencedModulesTelemetry": false, - "linuxConfiguration": { - "disablePasswordAuthentication": "[parameters('disablePasswordAuthentication')]", - "ssh": { - "publicKeys": "[variables('publicKeysFormatted')]" - }, - "provisionVMAgent": "[parameters('provisionVMAgent')]", - "patchSettings": "[if(and(parameters('provisionVMAgent'), or(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), equals(toLower(parameters('patchMode')), toLower('ImageDefault')))), createObject('patchMode', parameters('patchMode'), 'assessmentMode', parameters('patchAssessmentMode'), 'automaticByPlatformSettings', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), createObject('bypassPlatformSafetyChecksOnUserSchedule', parameters('bypassPlatformSafetyChecksOnUserSchedule'), 'rebootSetting', parameters('rebootSetting')), null())), null())]" - }, - "windowsConfiguration": { - "provisionVMAgent": "[parameters('provisionVMAgent')]", - "enableAutomaticUpdates": "[parameters('enableAutomaticUpdates')]", - "patchSettings": "[if(and(parameters('provisionVMAgent'), or(or(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), equals(toLower(parameters('patchMode')), toLower('AutomaticByOS'))), equals(toLower(parameters('patchMode')), toLower('Manual')))), createObject('patchMode', parameters('patchMode'), 'assessmentMode', parameters('patchAssessmentMode'), 'enableHotpatching', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), parameters('enableHotpatching'), false()), 'automaticByPlatformSettings', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), createObject('bypassPlatformSafetyChecksOnUserSchedule', parameters('bypassPlatformSafetyChecksOnUserSchedule'), 'rebootSetting', parameters('rebootSetting')), null())), null())]", - "timeZone": "[if(empty(parameters('timeZone')), null(), parameters('timeZone'))]", - "additionalUnattendContent": "[if(empty(parameters('additionalUnattendContent')), null(), variables('additionalUnattendContentFormatted'))]", - "winRM": "[if(not(empty(parameters('winRMListeners'))), createObject('listeners', parameters('winRMListeners')), null())]" - }, - "accountSasProperties": { - "signedServices": "b", - "signedPermission": "r", - "signedExpiry": "[dateTimeAdd(parameters('baseTime'), parameters('sasTokenValidityLength'))]", - "signedResourceTypes": "o", - "signedProtocol": "https" - }, - "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(if(parameters('extensionAadJoinConfig').enabled, true(), 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', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", - "builtInRoleNames": { - "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", - "Data Operator for Managed Disks": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '959f8984-c045-4866-89c7-12bf9737be2e')]", - "Desktop Virtualization Power On Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '489581de-a3bd-480d-9518-53dea7416b33')]", - "Desktop Virtualization Power On Off Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '40c5ff49-9181-41f8-ae61-143b0e78555e')]", - "Desktop Virtualization Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c')]", - "DevTest Labs User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76283e04-6283-4c54-8f91-bcf1374a3c64')]", - "Disk Backup Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3e5e47e6-65f7-47ef-90b5-e5dd4d455f24')]", - "Disk Pool Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '60fc6e62-5479-42d4-8bf4-67625fcc2840')]", - "Disk Restore Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b50d9833-a0cb-478e-945f-707fcc997c13')]", - "Disk Snapshot Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7efff54f-a5b4-42b5-a1c5-5411624893ce')]", - "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')]", - "Virtual Machine Administrator Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1c0163c0-47e6-4577-8991-ea5c82e286e4')]", - "Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9980e02c-c2be-4d73-94e8-173b1dc7cf3c')]", - "Virtual Machine User Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb879df8-f326-4884-b1cf-06f3ad86be52')]", - "VM Scanner Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd24ecba3-c1f4-40fa-a7bb-4588a071e8fd')]" - } - }, - "resources": { - "avmTelemetry": { - "condition": "[parameters('enableTelemetry')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.compute-virtualmachine.{0}.{1}', replace('0.15.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" - } - } - } - } - }, - "managedDataDisks": { - "copy": { - "name": "managedDataDisks", - "count": "[length(coalesce(parameters('dataDisks'), createArray()))]" - }, - "condition": "[empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'id'))]", - "type": "Microsoft.Compute/disks", - "apiVersion": "2024-03-02", - "name": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex(), 1), 2, '0')))]", - "location": "[parameters('location')]", - "sku": { - "name": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'storageAccountType')]" - }, - "properties": { - "diskSizeGB": "[coalesce(parameters('dataDisks'), createArray())[copyIndex()].diskSizeGB]", - "creationData": { - "createOption": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'createoption'), 'Empty')]" - }, - "diskIOPSReadWrite": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'diskIOPSReadWrite')]", - "diskMBpsReadWrite": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'diskMBpsReadWrite')]" - }, - "zones": "[if(and(not(equals(parameters('zone'), 0)), not(contains(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'storageAccountType'), 'ZRS'))), array(string(parameters('zone'))), null())]", - "tags": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" - }, - "vm": { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2024-07-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "identity": "[variables('identity')]", - "tags": "[parameters('tags')]", - "zones": "[if(not(equals(parameters('zone'), 0)), array(string(parameters('zone'))), null())]", - "plan": "[parameters('plan')]", - "properties": { - "hardwareProfile": { - "vmSize": "[parameters('vmSize')]" - }, - "securityProfile": { - "encryptionAtHost": "[if(parameters('encryptionAtHost'), parameters('encryptionAtHost'), null())]", - "securityType": "[parameters('securityType')]", - "uefiSettings": "[if(equals(parameters('securityType'), 'TrustedLaunch'), createObject('secureBootEnabled', parameters('secureBootEnabled'), 'vTpmEnabled', parameters('vTpmEnabled')), null())]" - }, - "storageProfile": { - "copy": [ - { - "name": "dataDisks", - "count": "[length(coalesce(parameters('dataDisks'), createArray()))]", - "input": { - "lun": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'lun'), copyIndex('dataDisks'))]", - "name": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), last(split(coalesce(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.id, ''), '/')), coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0'))))]", - "createOption": "[if(or(not(equals(resourceId('Microsoft.Compute/disks', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))), null())), not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id')))), 'Attach', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'createoption'), 'Empty'))]", - "deleteOption": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), 'Detach', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'deleteOption'), 'Delete'))]", - "caching": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), 'None', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'caching'), 'ReadOnly'))]", - "managedDisk": { - "id": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'), resourceId('Microsoft.Compute/disks', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))))]", - "diskEncryptionSet": "[if(contains(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'diskEncryptionSet'), createObject('id', coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.diskEncryptionSet.id), null())]" - } - } - } - ], - "imageReference": "[parameters('imageReference')]", - "osDisk": { - "name": "[coalesce(tryGet(parameters('osDisk'), 'name'), format('{0}-disk-os-01', parameters('name')))]", - "createOption": "[coalesce(tryGet(parameters('osDisk'), 'createOption'), 'FromImage')]", - "deleteOption": "[coalesce(tryGet(parameters('osDisk'), 'deleteOption'), 'Delete')]", - "diffDiskSettings": "[if(empty(coalesce(tryGet(parameters('osDisk'), 'diffDiskSettings'), createObject())), null(), createObject('option', 'Local', 'placement', parameters('osDisk').diffDiskSettings.placement))]", - "diskSizeGB": "[tryGet(parameters('osDisk'), 'diskSizeGB')]", - "caching": "[coalesce(tryGet(parameters('osDisk'), 'caching'), 'ReadOnly')]", - "managedDisk": { - "storageAccountType": "[tryGet(parameters('osDisk').managedDisk, 'storageAccountType')]", - "diskEncryptionSet": { - "id": "[tryGet(parameters('osDisk').managedDisk, 'diskEncryptionSetResourceId')]" - } - } - } - }, - "additionalCapabilities": { - "ultraSSDEnabled": "[parameters('ultraSSDEnabled')]", - "hibernationEnabled": "[parameters('hibernationEnabled')]" - }, - "osProfile": { - "computerName": "[parameters('computerName')]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]", - "customData": "[if(not(empty(parameters('customData'))), base64(parameters('customData')), null())]", - "windowsConfiguration": "[if(equals(parameters('osType'), 'Windows'), variables('windowsConfiguration'), null())]", - "linuxConfiguration": "[if(equals(parameters('osType'), 'Linux'), variables('linuxConfiguration'), null())]", - "secrets": "[parameters('certificatesToBeInstalled')]", - "allowExtensionOperations": "[parameters('allowExtensionOperations')]" - }, - "networkProfile": { - "copy": [ - { - "name": "networkInterfaces", - "count": "[length(parameters('nicConfigurations'))]", - "input": { - "properties": { - "deleteOption": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'deleteOption'), 'Delete')]", - "primary": "[if(equals(copyIndex('networkInterfaces'), 0), true(), false())]" - }, - "id": "[resourceId('Microsoft.Network/networkInterfaces', coalesce(tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'name'), format('{0}{1}', parameters('name'), tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'nicSuffix'))))]" - } - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": "[if(not(empty(parameters('bootDiagnosticStorageAccountName'))), true(), parameters('bootDiagnostics'))]", - "storageUri": "[if(not(empty(parameters('bootDiagnosticStorageAccountName'))), format('https://{0}{1}', parameters('bootDiagnosticStorageAccountName'), parameters('bootDiagnosticStorageAccountUri')), null())]" - } - }, - "applicationProfile": "[if(not(empty(parameters('galleryApplications'))), createObject('galleryApplications', parameters('galleryApplications')), null())]", - "availabilitySet": "[if(not(empty(parameters('availabilitySetResourceId'))), createObject('id', parameters('availabilitySetResourceId')), null())]", - "proximityPlacementGroup": "[if(not(empty(parameters('proximityPlacementGroupResourceId'))), createObject('id', parameters('proximityPlacementGroupResourceId')), null())]", - "virtualMachineScaleSet": "[if(not(empty(parameters('virtualMachineScaleSetResourceId'))), createObject('id', parameters('virtualMachineScaleSetResourceId')), null())]", - "priority": "[parameters('priority')]", - "evictionPolicy": "[if(and(not(empty(parameters('priority'))), not(equals(parameters('priority'), 'Regular'))), parameters('evictionPolicy'), null())]", - "billingProfile": "[if(and(not(empty(parameters('priority'))), not(empty(parameters('maxPriceForLowPriorityVm')))), createObject('maxPrice', json(parameters('maxPriceForLowPriorityVm'))), null())]", - "host": "[if(not(empty(parameters('dedicatedHostId'))), createObject('id', parameters('dedicatedHostId')), null())]", - "licenseType": "[if(not(empty(parameters('licenseType'))), parameters('licenseType'), null())]", - "userData": "[if(not(empty(parameters('userData'))), base64(parameters('userData')), null())]" - }, - "dependsOn": [ - "managedDataDisks", - "vm_nic" - ] - }, - "vm_configurationAssignment": { - "condition": "[not(empty(parameters('maintenanceConfigurationResourceId')))]", - "type": "Microsoft.Maintenance/configurationAssignments", - "apiVersion": "2023-04-01", - "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", - "name": "[format('{0}assignment', parameters('name'))]", - "location": "[parameters('location')]", - "properties": { - "maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]", - "resourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" - }, - "dependsOn": [ - "vm" - ] - }, - "vm_configurationProfileAssignment": { - "condition": "[not(empty(parameters('configurationProfile')))]", - "type": "Microsoft.Automanage/configurationProfileAssignments", - "apiVersion": "2022-05-04", - "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", - "name": "default", - "properties": { - "configurationProfile": "[parameters('configurationProfile')]" - }, - "dependsOn": [ - "vm" - ] - }, - "vm_autoShutdownConfiguration": { - "condition": "[not(empty(parameters('autoShutdownConfig')))]", - "type": "Microsoft.DevTestLab/schedules", - "apiVersion": "2018-09-15", - "name": "[format('shutdown-computevm-{0}', parameters('name'))]", - "location": "[parameters('location')]", - "properties": { - "status": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'status'), 'Disabled')]", - "targetResourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]", - "taskType": "ComputeVmShutdownTask", - "dailyRecurrence": { - "time": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'dailyRecurrenceTime'), '19:00')]" - }, - "timeZoneId": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'timeZone'), 'UTC')]", - "notificationSettings": "[if(contains(parameters('autoShutdownConfig'), 'notificationSettings'), createObject('status', coalesce(tryGet(parameters('autoShutdownConfig'), 'status'), 'Disabled'), 'emailRecipient', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'emailRecipient'), ''), 'notificationLocale', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'notificationLocale'), 'en'), 'webhookUrl', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'webhookUrl'), ''), 'timeInMinutes', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'timeInMinutes'), 30)), null())]" - }, - "dependsOn": [ - "vm" - ] - }, - "vm_dataCollectionRuleAssociations": { - "copy": { - "name": "vm_dataCollectionRuleAssociations", - "count": "[length(parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations)]" - }, - "condition": "[parameters('extensionMonitoringAgentConfig').enabled]", - "type": "Microsoft.Insights/dataCollectionRuleAssociations", - "apiVersion": "2023-03-11", - "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", - "name": "[parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations[copyIndex()].name]", - "properties": { - "dataCollectionRuleId": "[parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations[copyIndex()].dataCollectionRuleResourceId]" - }, - "dependsOn": [ - "vm", - "vm_azureMonitorAgentExtension" - ] - }, - "AzureWindowsBaseline": { - "condition": "[not(empty(parameters('guestConfiguration')))]", - "type": "Microsoft.GuestConfiguration/guestConfigurationAssignments", - "apiVersion": "2020-06-25", - "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(parameters('guestConfiguration'), 'name'), 'AzureWindowsBaseline')]", - "location": "[parameters('location')]", - "properties": { - "guestConfiguration": "[parameters('guestConfiguration')]" - }, - "dependsOn": [ - "vm", - "vm_azureGuestConfigurationExtension" - ] - }, - "vm_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/virtualMachines/{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": [ - "vm" - ] - }, - "vm_roleAssignments": { - "copy": { - "name": "vm_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Compute/virtualMachines', 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": [ - "vm" - ] - }, - "vm_nic": { - "copy": { - "name": "vm_nic", - "count": "[length(parameters('nicConfigurations'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-Nic-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "networkInterfaceName": { - "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'name'), format('{0}{1}', parameters('name'), tryGet(parameters('nicConfigurations')[copyIndex()], 'nicSuffix')))]" - }, - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "enableIPForwarding": { - "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'enableIPForwarding'), false())]" - }, - "enableAcceleratedNetworking": { - "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'enableAcceleratedNetworking'), true())]" - }, - "dnsServers": "[if(contains(parameters('nicConfigurations')[copyIndex()], 'dnsServers'), if(not(empty(tryGet(parameters('nicConfigurations')[copyIndex()], 'dnsServers'))), createObject('value', tryGet(parameters('nicConfigurations')[copyIndex()], 'dnsServers')), createObject('value', createArray())), createObject('value', createArray()))]", - "networkSecurityGroupResourceId": { - "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'networkSecurityGroupResourceId'), '')]" - }, - "ipConfigurations": { - "value": "[parameters('nicConfigurations')[copyIndex()].ipConfigurations]" - }, - "lock": { - "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'lock'), parameters('lock'))]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'tags'), parameters('tags'))]" - }, - "diagnosticSettings": { - "value": "[tryGet(parameters('nicConfigurations')[copyIndex()], 'diagnosticSettings')]" - }, - "roleAssignments": { - "value": "[tryGet(parameters('nicConfigurations')[copyIndex()], 'roleAssignments')]" - }, - "enableTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" - } - }, - "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.34.44.8038", - "templateHash": "3333482934245501039" - } - }, - "definitions": { - "publicIPConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the Public IP Address." - } - }, - "publicIPAddressResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the public IP address." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Diagnostic settings for the public IP address." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The idle timeout in minutes." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the public IP address." - } - }, - "idleTimeoutInMinutes": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The idle timeout of the public IP address." - } - }, - "ddosSettings": { - "$ref": "#/definitions/ddosSettingsType", - "nullable": true, - "metadata": { - "description": "Optional. The DDoS protection plan configuration associated with the public IP address." - } - }, - "dnsSettings": { - "$ref": "#/definitions/dnsSettingsType", - "nullable": true, - "metadata": { - "description": "Optional. The DNS settings of the public IP address." - } - }, - "publicIPAddressVersion": { - "type": "string", - "allowedValues": [ - "IPv4", - "IPv6" - ], - "nullable": true, - "metadata": { - "description": "Optional. The public IP address version." - } - }, - "publicIPAllocationMethod": { - "type": "string", - "allowedValues": [ - "Dynamic", - "Static" - ], - "nullable": true, - "metadata": { - "description": "Optional. The public IP address allocation method." - } - }, - "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." - } - }, - "publicIpNameSuffix": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name suffix of the public IP address resource." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "skuName": { - "type": "string", - "allowedValues": [ - "Basic", - "Standard" - ], - "nullable": true, - "metadata": { - "description": "Optional. The SKU name of the public IP address." - } - }, - "skuTier": { - "type": "string", - "allowedValues": [ - "Global", - "Regional" - ], - "nullable": true, - "metadata": { - "description": "Optional. The SKU tier of the public IP address." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The tags of the public IP address." - } - }, - "zones": { - "type": "array", - "allowedValues": [ - 1, - 2, - 3 - ], - "nullable": true, - "metadata": { - "description": "Optional. The zones of the public IP address." - } - }, - "enableTelemetry": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for the module." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the public IP address configuration." - } - }, - "ipConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the IP configuration." - } - }, - "privateIPAllocationMethod": { - "type": "string", - "allowedValues": [ - "Dynamic", - "Static" - ], - "nullable": true, - "metadata": { - "description": "Optional. The private IP address allocation method." - } - }, - "privateIPAddress": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The private IP address." - } - }, - "subnetResourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the subnet." - } - }, - "loadBalancerBackendAddressPools": { - "type": "array", - "items": { - "$ref": "#/definitions/backendAddressPoolType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The load balancer backend address pools." - } - }, - "applicationSecurityGroups": { - "type": "array", - "items": { - "$ref": "#/definitions/applicationSecurityGroupType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The application security groups." - } - }, - "applicationGatewayBackendAddressPools": { - "type": "array", - "items": { - "$ref": "#/definitions/applicationGatewayBackendAddressPoolsType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The application gateway backend address pools." - } - }, - "gatewayLoadBalancer": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. The gateway load balancer settings." - } - }, - "loadBalancerInboundNatRules": { - "type": "array", - "items": { - "$ref": "#/definitions/inboundNatRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The load balancer inbound NAT rules." - } - }, - "privateIPAddressVersion": { - "type": "string", - "allowedValues": [ - "IPv4", - "IPv6" - ], - "nullable": true, - "metadata": { - "description": "Optional. The private IP address version." - } - }, - "virtualNetworkTaps": { - "type": "array", - "items": { - "$ref": "#/definitions/virtualNetworkTapType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The virtual network taps." - } - }, - "pipConfiguration": { - "$ref": "#/definitions/publicIPConfigurationType", - "nullable": true, - "metadata": { - "description": "Optional. The public IP address configuration." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the IP configuration." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The tags of the public IP address." - } - }, - "enableTelemetry": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for the module." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the IP configuration." - } - }, - "applicationGatewayBackendAddressPoolsType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the backend address pool." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." - } - }, - "properties": { - "type": "object", - "properties": { - "backendAddresses": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipAddress": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. IP address of the backend address." - } - }, - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN of the backend address." - } - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Backend addresses." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Properties of the application gateway backend address pool." - } - } - }, - "metadata": { - "description": "The type for the application gateway backend address pool.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "applicationSecurityGroupType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the application security group." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Location of the application security group." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Properties of the application security group." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the application security group." - } - } - }, - "metadata": { - "description": "The type for the application security group.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "backendAddressPoolType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the backend address pool." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the backend address pool." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The properties of the backend address pool." - } - } - }, - "metadata": { - "description": "The type for a backend address pool.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" - } - } - }, - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "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" - ], - "nullable": true, - "metadata": { - "description": "Optional. 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." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" - } - } - }, - "inboundNatRuleType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the inbound NAT rule." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." - } - }, - "properties": { - "type": "object", - "properties": { - "backendAddressPool": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. A reference to backendAddressPool resource." - } - }, - "backendPort": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." - } - }, - "enableFloatingIP": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." - } - }, - "enableTcpReset": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." - } - }, - "frontendIPConfiguration": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. A reference to frontend IP addresses." - } - }, - "frontendPort": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." - } - }, - "frontendPortRangeStart": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." - } - }, - "frontendPortRangeEnd": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "All", - "Tcp", - "Udp" - ], - "nullable": true, - "metadata": { - "description": "Optional. The reference to the transport protocol used by the load balancing rule." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Properties of the inbound NAT rule." - } - } - }, - "metadata": { - "description": "The type for the inbound NAT rule.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "ipTagType": { - "type": "object", - "properties": { - "ipTagType": { - "type": "string", - "metadata": { - "description": "Required. The IP tag type." - } - }, - "tag": { - "type": "string", - "metadata": { - "description": "Required. The IP tag." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "networkInterfaceIPConfigurationOutputType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the IP configuration." - } - }, - "privateIP": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The private IP address." - } - }, - "publicIP": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The public IP address." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "subResourceType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the sub resource." - } - } - }, - "metadata": { - "description": "The type for the sub resource.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - }, - "virtualNetworkTapType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the virtual network tap." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Location of the virtual network tap." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Properties of the virtual network tap." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the virtual network tap." - } - } - }, - "metadata": { - "description": "The type for the virtual network tap.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" - } - } - } - }, - "parameters": { - "networkInterfaceName": { - "type": "string" - }, - "virtualMachineName": { - "type": "string" - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/ipConfigurationType" - } - }, - "location": { - "type": "string", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - }, - "enableIPForwarding": { - "type": "bool", - "defaultValue": false - }, - "enableAcceleratedNetworking": { - "type": "bool", - "defaultValue": false - }, - "dnsServers": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [] - }, - "enableTelemetry": { - "type": "bool", - "metadata": { - "description": "Required. Enable telemetry via a Globally Unique Identifier (GUID)." - } - }, - "networkSecurityGroupResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The network security group (NSG) to attach to the network interface." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - } - }, - "resources": { - "networkInterface_publicIPAddresses": { - "copy": { - "name": "networkInterface_publicIPAddresses", - "count": "[length(parameters('ipConfigurations'))]" - }, - "condition": "[and(not(empty(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'))), empty(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAddressResourceId')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-publicIP-{1}', deployment().name, copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'name'), format('{0}{1}', parameters('virtualMachineName'), tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIpNameSuffix')))]" - }, - "diagnosticSettings": { - "value": "[coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'diagnosticSettings'), tryGet(parameters('ipConfigurations')[copyIndex()], 'diagnosticSettings'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "lock": { - "value": "[parameters('lock')]" - }, - "idleTimeoutInMinutes": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'idleTimeoutInMinutes')]" - }, - "ddosSettings": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'ddosSettings')]" - }, - "dnsSettings": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'dnsSettings')]" - }, - "publicIPAddressVersion": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAddressVersion')]" - }, - "publicIPAllocationMethod": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAllocationMethod')]" - }, - "publicIpPrefixResourceId": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIpPrefixResourceId')]" - }, - "roleAssignments": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'roleAssignments')]" - }, - "skuName": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'skuName')]" - }, - "skuTier": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'skuTier')]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'tags'), parameters('tags'))]" - }, - "zones": { - "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'zones')]" - }, - "enableTelemetry": { - "value": "[coalesce(coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'enableTelemetry'), tryGet(parameters('ipConfigurations')[copyIndex()], '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.33.93.31351", - "templateHash": "5168739580767459761" - }, - "name": "Public IP Addresses", - "description": "This module deploys a Public IP Address." - }, - "definitions": { - "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" - ], - "nullable": true, - "metadata": { - "description": "Optional. 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." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "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." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "ipTagType": { - "type": "object", - "properties": { - "ipTagType": { - "type": "string", - "metadata": { - "description": "Required. The IP tag type." - } - }, - "tag": { - "type": "string", - "metadata": { - "description": "Required. The IP tag." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "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." - } - }, - "ipTags": { - "type": "array", - "items": { - "$ref": "#/definitions/ipTagType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The list of tags associated with the public IP address." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "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": "[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.8.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" - } - } - } - } - }, - "publicIpAddress": { - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2024-05-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": "[parameters('ipTags')]" - } - }, - "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', '2024-05-01', 'full').location]" - } - } - } - } - }, - "networkInterface": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-NetworkInterface', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[parameters('networkInterfaceName')]" - }, - "ipConfigurations": { - "copy": [ - { - "name": "value", - "count": "[length(parameters('ipConfigurations'))]", - "input": "[createObject('name', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'name'), 'privateIPAllocationMethod', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAllocationMethod'), 'privateIPAddress', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAddress'), 'publicIPAddressResourceId', if(not(empty(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'))), if(not(contains(coalesce(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), createObject()), 'publicIPAddressResourceId')), resourceId('Microsoft.Network/publicIPAddresses', coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), 'name'), format('{0}{1}', parameters('virtualMachineName'), tryGet(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), 'publicIpNameSuffix')))), tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration', 'publicIPAddressResourceId')), null()), 'subnetResourceId', parameters('ipConfigurations')[copyIndex('value')].subnetResourceId, 'loadBalancerBackendAddressPools', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'loadBalancerBackendAddressPools'), 'applicationSecurityGroups', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'applicationSecurityGroups'), 'applicationGatewayBackendAddressPools', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'applicationGatewayBackendAddressPools'), 'gatewayLoadBalancer', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'gatewayLoadBalancer'), 'loadBalancerInboundNatRules', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'loadBalancerInboundNatRules'), 'privateIPAddressVersion', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAddressVersion'), 'virtualNetworkTaps', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'virtualNetworkTaps'))]" - } - ] - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "diagnosticSettings": { - "value": "[parameters('diagnosticSettings')]" - }, - "dnsServers": { - "value": "[parameters('dnsServers')]" - }, - "enableAcceleratedNetworking": { - "value": "[parameters('enableAcceleratedNetworking')]" - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - }, - "enableIPForwarding": { - "value": "[parameters('enableIPForwarding')]" - }, - "lock": { - "value": "[parameters('lock')]" - }, - "networkSecurityGroupResourceId": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('value', parameters('networkSecurityGroupResourceId')), createObject('value', ''))]", - "roleAssignments": { - "value": "[parameters('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.34.44.8038", - "templateHash": "8196054567469390015" - }, - "name": "Network Interface", - "description": "This module deploys a Network Interface." - }, - "definitions": { - "networkInterfaceIPConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the IP configuration." - } - }, - "privateIPAllocationMethod": { - "type": "string", - "allowedValues": [ - "Dynamic", - "Static" - ], - "nullable": true, - "metadata": { - "description": "Optional. The private IP address allocation method." - } - }, - "privateIPAddress": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The private IP address." - } - }, - "publicIPAddressResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the public IP address." - } - }, - "subnetResourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the subnet." - } - }, - "loadBalancerBackendAddressPools": { - "type": "array", - "items": { - "$ref": "#/definitions/backendAddressPoolType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of load balancer backend address pools." - } - }, - "loadBalancerInboundNatRules": { - "type": "array", - "items": { - "$ref": "#/definitions/inboundNatRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. A list of references of LoadBalancerInboundNatRules." - } - }, - "applicationSecurityGroups": { - "type": "array", - "items": { - "$ref": "#/definitions/applicationSecurityGroupType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Application security groups in which the IP configuration is included." - } - }, - "applicationGatewayBackendAddressPools": { - "type": "array", - "items": { - "$ref": "#/definitions/applicationGatewayBackendAddressPoolsType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The reference to Application Gateway Backend Address Pools." - } - }, - "gatewayLoadBalancer": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. The reference to gateway load balancer frontend IP." - } - }, - "privateIPAddressVersion": { - "type": "string", - "allowedValues": [ - "IPv4", - "IPv6" - ], - "nullable": true, - "metadata": { - "description": "Optional. Whether the specific IP configuration is IPv4 or IPv6." - } - }, - "virtualNetworkTaps": { - "type": "array", - "items": { - "$ref": "#/definitions/virtualNetworkTapType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The reference to Virtual Network Taps." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The resource ID of the deployed resource." - } - }, - "backendAddressPoolType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the backend address pool." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the backend address pool." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The properties of the backend address pool." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for a backend address pool." - } - }, - "applicationSecurityGroupType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the application security group." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Location of the application security group." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Properties of the application security group." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the application security group." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the application security group." - } - }, - "applicationGatewayBackendAddressPoolsType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the backend address pool." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." - } - }, - "properties": { - "type": "object", - "properties": { - "backendAddresses": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipAddress": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. IP address of the backend address." - } - }, - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN of the backend address." - } - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Backend addresses." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Properties of the application gateway backend address pool." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the application gateway backend address pool." - } - }, - "subResourceType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the sub resource." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the sub resource." - } - }, - "inboundNatRuleType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the inbound NAT rule." - } - }, - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." - } - }, - "properties": { - "type": "object", - "properties": { - "backendAddressPool": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. A reference to backendAddressPool resource." - } - }, - "backendPort": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." - } - }, - "enableFloatingIP": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." - } - }, - "enableTcpReset": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." - } - }, - "frontendIPConfiguration": { - "$ref": "#/definitions/subResourceType", - "nullable": true, - "metadata": { - "description": "Optional. A reference to frontend IP addresses." - } - }, - "frontendPort": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." - } - }, - "frontendPortRangeStart": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." - } - }, - "frontendPortRangeEnd": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." - } - }, - "protocol": { - "type": "string", - "allowedValues": [ - "All", - "Tcp", - "Udp" - ], - "nullable": true, - "metadata": { - "description": "Optional. The reference to the transport protocol used by the load balancing rule." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Properties of the inbound NAT rule." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the inbound NAT rule." - } - }, - "virtualNetworkTapType": { - "type": "object", - "properties": { - "id": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the virtual network tap." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Location of the virtual network tap." - } - }, - "properties": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Properties of the virtual network tap." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the virtual network tap." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the virtual network tap." - } - }, - "networkInterfaceIPConfigurationOutputType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the IP configuration." - } - }, - "privateIP": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The private IP address." - } - }, - "publicIP": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The public IP address." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the network interface." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Resource tags." - } - }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." - } - }, - "enableIPForwarding": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether IP forwarding is enabled on this network interface." - } - }, - "enableAcceleratedNetworking": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If the network interface is accelerated networking enabled." - } - }, - "dnsServers": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. List of DNS servers IP addresses. Use 'AzureProvidedDNS' to switch to azure provided DNS resolution. 'AzureProvidedDNS' value cannot be combined with other IPs, it must be the only value in dnsServers collection." - } - }, - "networkSecurityGroupResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The network security group (NSG) to attach to the network interface." - } - }, - "auxiliaryMode": { - "type": "string", - "defaultValue": "None", - "allowedValues": [ - "Floating", - "MaxConnections", - "None" - ], - "metadata": { - "description": "Optional. Auxiliary mode of Network Interface resource. Not all regions are enabled for Auxiliary Mode Nic." - } - }, - "auxiliarySku": { - "type": "string", - "defaultValue": "None", - "allowedValues": [ - "A1", - "A2", - "A4", - "A8", - "None" - ], - "metadata": { - "description": "Optional. Auxiliary sku of Network Interface resource. Not all regions are enabled for Auxiliary Mode Nic." - } - }, - "disableTcpStateTracking": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to disable tcp state tracking. Subscription must be registered for the Microsoft.Network/AllowDisableTcpStateTracking feature before this property can be set to true." - } - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/networkInterfaceIPConfigurationType" - }, - "metadata": { - "description": "Required. A list of IPConfigurations of the network interface." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "diagnosticSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "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')]", - "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" - } - }, - "resources": { - "publicIp": { - "copy": { - "name": "publicIp", - "count": "[length(parameters('ipConfigurations'))]" - }, - "condition": "[and(contains(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), not(equals(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), null())))]", - "existing": true, - "type": "Microsoft.Network/publicIPAddresses", - "apiVersion": "2024-05-01", - "resourceGroup": "[split(coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), ''), '/')[4]]", - "name": "[last(split(coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), ''), '/'))]" - }, - "avmTelemetry": { - "condition": "[parameters('enableTelemetry')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.network-networkinterface.{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" - } - } - } - } - }, - "networkInterface": { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2024-05-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "copy": [ - { - "name": "ipConfigurations", - "count": "[length(parameters('ipConfigurations'))]", - "input": { - "name": "[coalesce(tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'name'), format('ipconfig0{0}', add(copyIndex('ipConfigurations'), 1)))]", - "properties": { - "primary": "[if(equals(copyIndex('ipConfigurations'), 0), true(), false())]", - "privateIPAllocationMethod": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAllocationMethod')]", - "privateIPAddress": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAddress')]", - "publicIPAddress": "[if(contains(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId'), if(not(equals(tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId'), null())), createObject('id', tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId')), null()), null())]", - "subnet": { - "id": "[parameters('ipConfigurations')[copyIndex('ipConfigurations')].subnetResourceId]" - }, - "loadBalancerBackendAddressPools": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'loadBalancerBackendAddressPools')]", - "applicationSecurityGroups": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'applicationSecurityGroups')]", - "applicationGatewayBackendAddressPools": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'applicationGatewayBackendAddressPools')]", - "gatewayLoadBalancer": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'gatewayLoadBalancer')]", - "loadBalancerInboundNatRules": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'loadBalancerInboundNatRules')]", - "privateIPAddressVersion": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAddressVersion')]", - "virtualNetworkTaps": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'virtualNetworkTaps')]" - } - } - } - ], - "auxiliaryMode": "[parameters('auxiliaryMode')]", - "auxiliarySku": "[parameters('auxiliarySku')]", - "disableTcpStateTracking": "[parameters('disableTcpStateTracking')]", - "dnsSettings": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', parameters('dnsServers')), null())]", - "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "enableIPForwarding": "[parameters('enableIPForwarding')]", - "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]" - } - }, - "networkInterface_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/networkInterfaces/{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": [ - "networkInterface" - ] - }, - "networkInterface_diagnosticSettings": { - "copy": { - "name": "networkInterface_diagnosticSettings", - "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" - }, - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Network/networkInterfaces/{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": [ - "networkInterface" - ] - }, - "networkInterface_roleAssignments": { - "copy": { - "name": "networkInterface_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/networkInterfaces/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkInterfaces', 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": [ - "networkInterface" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed resource." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed resource." - }, - "value": "[resourceId('Microsoft.Network/networkInterfaces', parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed resource." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('networkInterface', '2024-05-01', 'full').location]" - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" - }, - "metadata": { - "description": "The list of IP configurations of the network interface." - }, - "copy": { - "count": "[length(parameters('ipConfigurations'))]", - "input": { - "name": "[reference('networkInterface').ipConfigurations[copyIndex()].name]", - "privateIP": "[coalesce(tryGet(reference('networkInterface').ipConfigurations[copyIndex()].properties, 'privateIPAddress'), '')]", - "publicIP": "[if(and(contains(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), not(equals(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), null()))), coalesce(reference(format('publicIp[{0}]', copyIndex())).ipAddress, ''), '')]" - } - } - } - } - } - }, - "dependsOn": [ - "networkInterface_publicIPAddresses" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the network interface." - }, - "value": "[reference('networkInterface').outputs.name.value]" - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" - }, - "metadata": { - "description": "The list of IP configurations of the network interface." - }, - "value": "[reference('networkInterface').outputs.ipConfigurations.value]" - } - } - } - } - }, - "vm_domainJoinExtension": { - "condition": "[and(contains(parameters('extensionDomainJoinConfig'), 'enabled'), parameters('extensionDomainJoinConfig').enabled)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-DomainJoin', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'name'), 'DomainJoin')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Compute" - }, - "type": { - "value": "JsonADDomainExtension" - }, - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'typeHandlerVersion'), '1.3')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "settings": { - "value": "[parameters('extensionDomainJoinConfig').settings]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'tags'), parameters('tags'))]" - }, - "protectedSettings": { - "value": { - "Password": "[parameters('extensionDomainJoinPassword')]" - } - } - }, - "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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm" - ] - }, - "vm_aadJoinExtension": { - "condition": "[parameters('extensionAadJoinConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-AADLogin', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'name'), 'AADLogin')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Azure.ActiveDirectory" - }, - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AADLoginForWindows'), createObject('value', 'AADSSHLoginforLinux'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '2.0', '1.0'))]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "settings": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'settings'), createObject())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_domainJoinExtension" - ] - }, - "vm_microsoftAntiMalwareExtension": { - "condition": "[parameters('extensionAntiMalwareConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-MicrosoftAntiMalware', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'name'), 'MicrosoftAntiMalware')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Azure.Security" - }, - "type": { - "value": "IaaSAntimalware" - }, - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'typeHandlerVersion'), '1.3')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "settings": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'settings'), createObject('AntimalwareEnabled', 'true', 'Exclusions', createObject(), 'RealtimeProtectionEnabled', 'true', 'ScheduledScanSettings', createObject('day', '7', 'isEnabled', 'true', 'scanType', 'Quick', 'time', '120')))]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_aadJoinExtension" - ] - }, - "vm_azureMonitorAgentExtension": { - "condition": "[parameters('extensionMonitoringAgentConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-AzureMonitorAgent', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'name'), 'AzureMonitorAgent')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Azure.Monitor" - }, - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AzureMonitorWindowsAgent'), createObject('value', 'AzureMonitorLinuxAgent'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.22', '1.29'))]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_microsoftAntiMalwareExtension" - ] - }, - "vm_dependencyAgentExtension": { - "condition": "[parameters('extensionDependencyAgentConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-DependencyAgent', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'name'), 'DependencyAgent')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Azure.Monitoring.DependencyAgent" - }, - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'DependencyAgentWindows'), createObject('value', 'DependencyAgentLinux'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'typeHandlerVersion'), '9.10')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'enableAutomaticUpgrade'), true())]" - }, - "settings": { - "value": { - "enableAMA": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'enableAMA'), true())]" - } - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_azureMonitorAgentExtension" - ] - }, - "vm_networkWatcherAgentExtension": { - "condition": "[parameters('extensionNetworkWatcherAgentConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-NetworkWatcherAgent', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'name'), 'NetworkWatcherAgent')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Azure.NetworkWatcher" - }, - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'NetworkWatcherAgentWindows'), createObject('value', 'NetworkWatcherAgentLinux'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'typeHandlerVersion'), '1.4')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_dependencyAgentExtension" - ] - }, - "vm_desiredStateConfigurationExtension": { - "condition": "[parameters('extensionDSCConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-DesiredStateConfiguration', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'name'), 'DesiredStateConfiguration')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Powershell" - }, - "type": { - "value": "DSC" - }, - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'typeHandlerVersion'), '2.77')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "settings": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'settings'), createObject())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'tags'), parameters('tags'))]" - }, - "protectedSettings": { - "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'protectedSettings'), 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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_networkWatcherAgentExtension" - ] - }, - "vm_customScriptExtension": { - "condition": "[parameters('extensionCustomScriptConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-CustomScriptExtension', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'name'), 'CustomScriptExtension')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'Microsoft.Compute'), createObject('value', 'Microsoft.Azure.Extensions'))]", - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'CustomScriptExtension'), createObject('value', 'CustomScript'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.10', '2.1'))]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "settings": { - "value": { - "copy": [ - { - "name": "fileUris", - "count": "[length(parameters('extensionCustomScriptConfig').fileData)]", - "input": "[if(contains(parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')], 'storageAccountId'), format('{0}?{1}', parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].uri, listAccountSas(parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].storageAccountId, '2019-04-01', variables('accountSasProperties')).accountSasToken), parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].uri)]" - } - ] - } - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'tags'), parameters('tags'))]" - }, - "protectedSettings": { - "value": "[parameters('extensionCustomScriptProtectedSetting')]" - } - }, - "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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_desiredStateConfigurationExtension" - ] - }, - "vm_azureDiskEncryptionExtension": { - "condition": "[parameters('extensionAzureDiskEncryptionConfig').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-AzureDiskEncryption', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'name'), 'AzureDiskEncryption')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.Azure.Security" - }, - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AzureDiskEncryption'), createObject('value', 'AzureDiskEncryptionForLinux'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '2.2', '1.1'))]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'enableAutomaticUpgrade'), false())]" - }, - "forceUpdateTag": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'forceUpdateTag'), '1.0')]" - }, - "settings": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'settings'), createObject())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_customScriptExtension" - ] - }, - "vm_nvidiaGpuDriverWindowsExtension": { - "condition": "[parameters('extensionNvidiaGpuDriverWindows').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-NvidiaGpuDriverWindows', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'name'), 'NvidiaGpuDriverWindows')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.HpcCompute" - }, - "type": { - "value": "NvidiaGpuDriverWindows" - }, - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'typeHandlerVersion'), '1.4')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'enableAutomaticUpgrade'), false())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'supressFailures'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_azureDiskEncryptionExtension" - ] - }, - "vm_hostPoolRegistrationExtension": { - "condition": "[parameters('extensionHostPoolRegistration').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-HostPoolRegistration', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'name'), 'HostPoolRegistration')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.PowerShell" - }, - "type": { - "value": "DSC" - }, - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'typeHandlerVersion'), '2.77')]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'enableAutomaticUpgrade'), false())]" - }, - "settings": { - "value": { - "modulesUrl": "[parameters('extensionHostPoolRegistration').modulesUrl]", - "configurationFunction": "[parameters('extensionHostPoolRegistration').configurationFunction]", - "properties": { - "hostPoolName": "[parameters('extensionHostPoolRegistration').hostPoolName]", - "registrationInfoToken": "[parameters('extensionHostPoolRegistration').registrationInfoToken]", - "aadJoin": true - }, - "supressFailures": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'supressFailures'), false())]" - } - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_nvidiaGpuDriverWindowsExtension" - ] - }, - "vm_azureGuestConfigurationExtension": { - "condition": "[parameters('extensionGuestConfigurationExtension').enabled]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-GuestConfiguration', uniqueString(deployment().name, parameters('location')))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "virtualMachineName": { - "value": "[parameters('name')]" - }, - "name": "[if(coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'name'), equals(parameters('osType'), 'Windows')), createObject('value', 'AzurePolicyforWindows'), createObject('value', 'AzurePolicyforLinux'))]", - "location": { - "value": "[parameters('location')]" - }, - "publisher": { - "value": "Microsoft.GuestConfiguration" - }, - "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'ConfigurationforWindows'), createObject('value', 'ConfigurationForLinux'))]", - "typeHandlerVersion": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.0', '1.0'))]" - }, - "autoUpgradeMinorVersion": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'autoUpgradeMinorVersion'), true())]" - }, - "enableAutomaticUpgrade": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'enableAutomaticUpgrade'), true())]" - }, - "forceUpdateTag": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'forceUpdateTag'), '1.0')]" - }, - "settings": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'settings'), createObject())]" - }, - "supressFailures": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'supressFailures'), false())]" - }, - "protectedSettings": { - "value": "[parameters('extensionGuestConfigurationExtensionProtectedSettings')]" - }, - "tags": { - "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), '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.34.44.8038", - "templateHash": "8482591295619883067" - }, - "name": "Virtual Machine Extensions", - "description": "This module deploys a Virtual Machine Extension." - }, - "parameters": { - "virtualMachineName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the virtual machine extension." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location the extension is deployed to." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. The name of the extension handler publisher." - } - }, - "type": { - "type": "string", - "metadata": { - "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." - } - }, - "typeHandlerVersion": { - "type": "string", - "metadata": { - "description": "Required. Specifies the version of the script handler." - } - }, - "autoUpgradeMinorVersion": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." - } - }, - "settings": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific settings." - } - }, - "protectedSettings": { - "type": "secureObject", - "defaultValue": {}, - "metadata": { - "description": "Optional. Any object that contains the extension specific protected settings." - } - }, - "supressFailures": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." - } - }, - "enableAutomaticUpgrade": { - "type": "bool", - "metadata": { - "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - } - }, - "resources": { - "virtualMachine": { - "existing": true, - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2022-11-01", - "name": "[parameters('virtualMachineName')]" - }, - "extension": { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2022-11-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "publisher": "[parameters('publisher')]", - "type": "[parameters('type')]", - "typeHandlerVersion": "[parameters('typeHandlerVersion')]", - "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", - "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", - "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", - "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", - "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", - "suppressFailures": "[parameters('supressFailures')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the extension." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the extension." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the extension was created in." - }, - "value": "[resourceGroup().name]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('extension', '2022-11-01', 'full').location]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_hostPoolRegistrationExtension" - ] - }, - "vm_backup": { - "condition": "[not(empty(parameters('backupVaultName')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-VM-Backup', uniqueString(deployment().name, parameters('location')))]", - "resourceGroup": "[parameters('backupVaultResourceGroup')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('vm;iaasvmcontainerv2;{0};{1}', resourceGroup().name, parameters('name'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "policyId": { - "value": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', parameters('backupVaultName'), parameters('backupPolicyName'))]" - }, - "protectedItemType": { - "value": "Microsoft.Compute/virtualMachines" - }, - "protectionContainerName": { - "value": "[format('iaasvmcontainer;iaasvmcontainerv2;{0};{1}', resourceGroup().name, parameters('name'))]" - }, - "recoveryVaultName": { - "value": "[parameters('backupVaultName')]" - }, - "sourceResourceId": { - "value": "[resourceId('Microsoft.Compute/virtualMachines', 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.34.44.8038", - "templateHash": "7743264001610407207" - }, - "name": "Recovery Service Vaults Protection Container Protected Item", - "description": "This module deploys a Recovery Services Vault Protection Container Protected Item." - }, - "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. Name of the resource." - } - }, - "protectionContainerName": { - "type": "string", - "metadata": { - "description": "Conditional. Name of the Azure Recovery Service Vault Protection Container. Required if the template is used in a standalone deployment." - } - }, - "recoveryVaultName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Azure Recovery Service Vault. Required if the template is used in a standalone deployment." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "protectedItemType": { - "type": "string", - "allowedValues": [ - "AzureFileShareProtectedItem", - "AzureVmWorkloadSAPAseDatabase", - "AzureVmWorkloadSAPHanaDatabase", - "AzureVmWorkloadSQLDatabase", - "DPMProtectedItem", - "GenericProtectedItem", - "MabFileFolderProtectedItem", - "Microsoft.ClassicCompute/virtualMachines", - "Microsoft.Compute/virtualMachines", - "Microsoft.Sql/servers/databases" - ], - "metadata": { - "description": "Required. The backup item type." - } - }, - "policyId": { - "type": "string", - "metadata": { - "description": "Required. ID of the backup policy with which this item is backed up." - } - }, - "sourceResourceId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the resource to back up." - } - } - }, - "resources": [ - { - "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems", - "apiVersion": "2023-01-01", - "name": "[format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name'))]", - "location": "[parameters('location')]", - "properties": { - "protectedItemType": "[parameters('protectedItemType')]", - "policyId": "[parameters('policyId')]", - "sourceResourceId": "[parameters('sourceResourceId')]" - } - } - ], - "outputs": { - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the Resource Group the protected item was created in." - }, - "value": "[resourceGroup().name]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the protected item." - }, - "value": "[resourceId('Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems', split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[0], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[1], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[2], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[3])]" - }, - "name": { - "type": "string", - "metadata": { - "description": "The Name of the protected item." - }, - "value": "[format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name'))]" - } - } - } - }, - "dependsOn": [ - "vm", - "vm_azureGuestConfigurationExtension" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the VM." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the VM." - }, - "value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The name of the resource group the VM was created in." - }, - "value": "[resourceGroup().name]" - }, - "systemAssignedMIPrincipalId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The principal ID of the system assigned identity." - }, - "value": "[tryGet(tryGet(reference('vm', '2024-07-01', 'full'), 'identity'), 'principalId')]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('vm', '2024-07-01', 'full').location]" - }, - "nicConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/nicConfigurationOutputType" - }, - "metadata": { - "description": "The list of NIC configurations of the virtual machine." - }, - "copy": { - "count": "[length(parameters('nicConfigurations'))]", - "input": { - "name": "[reference(format('vm_nic[{0}]', copyIndex())).outputs.name.value]", - "ipConfigurations": "[reference(format('vm_nic[{0}]', copyIndex())).outputs.ipConfigurations.value]" - } - } - } - } - } - }, - "dependsOn": [ - "nsg", - "subnetResource" - ] - } + "nullable": true, + "metadata": { + "description": "Optional. The destination metadata." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The data export destination properties." + } + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 4, + "maxLength": 63, + "metadata": { + "description": "Required. The data export rule name." + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + } + }, + "destination": { + "$ref": "#/definitions/destinationType", + "nullable": true, + "metadata": { + "description": "Optional. Destination properties." + } + }, + "enable": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Active when enabled." + } + }, + "tableNames": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. An array of tables to export, for example: ['Heartbeat', 'SecurityEvent']." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('workspaceName')]" + }, + "dataExport": { + "type": "Microsoft.OperationalInsights/workspaces/dataExports", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "properties": { + "destination": "[parameters('destination')]", + "enable": "[parameters('enable')]", + "tableNames": "[parameters('tableNames')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the data export." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the data export." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataExports', parameters('workspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the data export was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_dataSources": { + "copy": { + "name": "logAnalyticsWorkspace_dataSources", + "count": "[length(coalesce(parameters('dataSources'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-DataSource-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].name]" + }, + "kind": { + "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].kind]" + }, + "linkedResourceId": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'linkedResourceId')]" + }, + "eventLogName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventLogName')]" + }, + "eventTypes": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventTypes')]" + }, + "objectName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'objectName')]" + }, + "instanceName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'instanceName')]" + }, + "intervalSeconds": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'intervalSeconds')]" + }, + "counterName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'counterName')]" + }, + "state": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'state')]" + }, + "syslogName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogName')]" + }, + "syslogSeverities": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogSeverities')]" + }, + "performanceCounters": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'performanceCounters')]" + }, + "tags": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], '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.36.1.42791", + "templateHash": "8336916453932906250" + }, + "name": "Log Analytics Workspace Datasources", + "description": "This module deploys a Log Analytics Workspace Data Source." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data source." + } + }, + "kind": { + "type": "string", + "defaultValue": "AzureActivityLog", + "allowedValues": [ + "AzureActivityLog", + "WindowsEvent", + "WindowsPerformanceCounter", + "IISLogs", + "LinuxSyslog", + "LinuxSyslogCollection", + "LinuxPerformanceObject", + "LinuxPerformanceCollection" + ], + "metadata": { + "description": "Optional. The kind of the data source." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + }, + "linkedResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the resource to be linked." + } + }, + "eventLogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Windows event log name to configure when kind is WindowsEvent." + } + }, + "eventTypes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Windows event types to configure when kind is WindowsEvent." + } + }, + "objectName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "instanceName": { + "type": "string", + "defaultValue": "*", + "metadata": { + "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "intervalSeconds": { + "type": "int", + "defaultValue": 60, + "metadata": { + "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "performanceCounters": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." + } + }, + "counterName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." + } + }, + "state": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." + } + }, + "syslogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. System log to configure when kind is LinuxSyslog." + } + }, + "syslogSeverities": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "dataSource": { + "type": "Microsoft.OperationalInsights/workspaces/dataSources", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "kind": "[parameters('kind')]", + "tags": "[parameters('tags')]", + "properties": { + "linkedResourceId": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'AzureActivityLog')), parameters('linkedResourceId'), null())]", + "eventLogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventLogName'), null())]", + "eventTypes": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventTypes'), null())]", + "objectName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('objectName'), null())]", + "instanceName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('instanceName'), null())]", + "intervalSeconds": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('intervalSeconds'), null())]", + "counterName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsPerformanceCounter')), parameters('counterName'), null())]", + "state": "[if(and(not(empty(parameters('kind'))), or(or(equals(parameters('kind'), 'IISLogs'), equals(parameters('kind'), 'LinuxSyslogCollection')), equals(parameters('kind'), 'LinuxPerformanceCollection'))), parameters('state'), null())]", + "syslogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxSyslog')), parameters('syslogName'), null())]", + "syslogSeverities": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'LinuxSyslog'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('syslogSeverities'), null())]", + "performanceCounters": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxPerformanceObject')), parameters('performanceCounters'), null())]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed data source." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataSources', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the data source is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed data source." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_tables": { + "copy": { + "name": "logAnalyticsWorkspace_tables", + "count": "[length(coalesce(parameters('tables'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-Table-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "workspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" + }, + "plan": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'plan')]" + }, + "schema": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'schema')]" + }, + "retentionInDays": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'retentionInDays')]" + }, + "totalRetentionInDays": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'totalRetentionInDays')]" + }, + "restoredLogs": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'restoredLogs')]" + }, + "searchResults": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'searchResults')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('tables'), 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.36.1.42791", + "templateHash": "315390662258960765" + }, + "name": "Log Analytics Workspace Tables", + "description": "This module deploys a Log Analytics Workspace Table." + }, + "definitions": { + "restoredLogsType": { + "type": "object", + "properties": { + "sourceTable": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table to restore data from." + } + }, + "startRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the restore from (UTC)." + } + }, + "endRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the restore by (UTC)." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the restore operation that initiated the table." + } + }, + "schemaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The table name." + } + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/columnType" }, - "outputs": { - "resourceId": { - "type": "string", - "value": "[reference('vm').outputs.resourceId.value]" - }, - "name": { - "type": "string", - "value": "[reference('vm').outputs.name.value]" - }, - "location": { - "type": "string", - "value": "[reference('vm').outputs.location.value]" - }, - "subnetId": { - "type": "string", - "value": "[reference('subnetResource').outputs.resourceId.value]" - }, - "subnetName": { - "type": "string", - "value": "[reference('subnetResource').outputs.name.value]" - }, - "nsgId": { - "type": "string", - "value": "[reference('nsg').outputs.resourceId.value]" - }, - "nsgName": { - "type": "string", - "value": "[reference('nsg').outputs.name.value]" - } + "metadata": { + "description": "Required. A list of table custom columns." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table display name." } } }, - "dependsOn": [ - "virtualNetwork" - ] - } - }, - "outputs": { - "vnetName": { - "type": "string", - "value": "[reference('virtualNetwork').outputs.name.value]" + "metadata": { + "__bicep_export!": true, + "description": "The table schema." + } }, - "vnetResourceId": { - "type": "string", - "value": "[reference('virtualNetwork').outputs.resourceId.value]" + "columnType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The column name." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "boolean", + "dateTime", + "dynamic", + "guid", + "int", + "long", + "real", + "string" + ], + "metadata": { + "description": "Required. The column type." + } + }, + "dataTypeHint": { + "type": "string", + "allowedValues": [ + "armPath", + "guid", + "ip", + "uri" + ], + "nullable": true, + "metadata": { + "description": "Optional. The column data type logical hint." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The column description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Column display name." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the table column." + } }, - "subnets": { - "type": "array", - "items": { - "$ref": "#/definitions/subnetOutputType" + "searchResultsType": { + "type": "object", + "properties": { + "query": { + "type": "string", + "metadata": { + "description": "Required. The search job query." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The search description." + } + }, + "limit": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Limit the search job to return up to specified number of rows." + } + }, + "startSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the search from (UTC)." + } + }, + "endSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the search by (UTC)." + } + } }, - "value": "[reference('virtualNetwork').outputs.subnets.value]" + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the search job that initiated the table." + } }, - "bastionSubnetId": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { "type": "string", - "value": "[reference('bastionHost').outputs.subnetId.value]" + "metadata": { + "description": "Required. The name of the table." + } }, - "bastionSubnetName": { + "workspaceName": { "type": "string", - "value": "[reference('bastionHost').outputs.subnetName.value]" + "metadata": { + "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + } }, - "bastionHostId": { + "plan": { "type": "string", - "value": "[reference('bastionHost').outputs.resourceId.value]" + "defaultValue": "Analytics", + "allowedValues": [ + "Basic", + "Analytics" + ], + "metadata": { + "description": "Optional. Instruct the system how to handle and charge the logs ingested to this table." + } }, - "bastionHostName": { - "type": "string", - "value": "[reference('bastionHost').outputs.name.value]" + "restoredLogs": { + "$ref": "#/definitions/restoredLogsType", + "nullable": true, + "metadata": { + "description": "Optional. Restore parameters." + } }, - "jumpboxSubnetName": { - "type": "string", - "value": "[reference('jumpbox').outputs.subnetName.value]" + "retentionInDays": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "maxValue": 730, + "metadata": { + "description": "Optional. The table retention in days, between 4 and 730. Setting this property to -1 will default to the workspace retention." + } }, - "jumpboxSubnetId": { - "type": "string", - "value": "[reference('jumpbox').outputs.subnetId.value]" + "schema": { + "$ref": "#/definitions/schemaType", + "nullable": true, + "metadata": { + "description": "Optional. Table's schema." + } }, - "jumpboxName": { - "type": "string", - "value": "[reference('jumpbox').outputs.name.value]" + "searchResults": { + "$ref": "#/definitions/searchResultsType", + "nullable": true, + "metadata": { + "description": "Optional. Parameters of the search job that initiated this table." + } }, - "jumpboxResourceId": { - "type": "string", - "value": "[reference('jumpbox').outputs.resourceId.value]" + "totalRetentionInDays": { + "type": "int", + "defaultValue": -1, + "minValue": -1, + "maxValue": 2555, + "metadata": { + "description": "Optional. The table total retention in days, between 4 and 2555. Setting this property to -1 will default to table retention." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } } - } - } - } - } - ], - "outputs": { - "vnetName": { - "type": "string", - "metadata": { - "description": "Name of the Virtual Network resource." - }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.vnetName.value]" - }, - "vnetResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Virtual Network." - }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.vnetResourceId.value]" - }, - "subnetWebResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the \"web\" subnet." - }, - "value": "[coalesce(tryGet(first(filter(reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.subnets.value, lambda('s', equals(lambdaVariables('s').name, 'web')))), 'resourceId'), '')]" - }, - "subnetPrivateEndpointsResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the \"peps\" subnet for Private Endpoints." - }, - "value": "[coalesce(tryGet(first(filter(reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.subnets.value, lambda('s', equals(lambdaVariables('s').name, 'peps')))), 'resourceId'), '')]" - }, - "bastionResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Bastion Host." - }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.bastionHostId.value]" - }, - "jumpboxResourceId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Jumpbox VM." - }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.jumpboxResourceId.value]" - } - } - } - }, - "dependsOn": [ - "logAnalyticsWorkspace" - ] - }, - "avmPrivateDnsZones": { - "copy": { - "name": "avmPrivateDnsZones", - "count": "[length(variables('privateDnsZones'))]", - "mode": "serial", - "batchSize": 5 - }, - "condition": "[and(parameters('enablePrivateNetworking'), or(empty(parameters('existingFoundryProjectResourceId')), not(contains(variables('aiRelatedDnsZoneIndices'), copyIndex()))))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('avm.res.network.private-dns-zone.{0}', if(contains(variables('privateDnsZones')[copyIndex()], 'azurecontainerapps.io'), 'containerappenv', split(variables('privateDnsZones')[copyIndex()], '.')[1]))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[variables('privateDnsZones')[copyIndex()]]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - }, - "virtualNetworkLinks": { - "value": [ - { - "name": "[take(format('vnetlink-{0}-{1}', reference('network').outputs.vnetName.value, split(variables('privateDnsZones')[copyIndex()], '.')[1]), 80)]", - "virtualNetworkResourceId": "[reference('network').outputs.subnetPrivateEndpointsResourceId.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.34.44.8038", - "templateHash": "4533956061065498344" - }, - "name": "Private DNS Zones", - "description": "This module deploys a Private DNS zone." - }, - "definitions": { - "aType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "aRecords": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipv4Address": { - "type": "string", - "metadata": { - "description": "Required. The IPv4 address of this A record." - } + "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')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" } }, - "nullable": true, - "metadata": { - "description": "Optional. The list of A records in the record set." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the A record." - } - }, - "aaaaType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "aaaaRecords": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipv6Address": { - "type": "string", - "metadata": { - "description": "Required. The IPv6 address of this AAAA record." - } + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "name": "[parameters('workspaceName')]" + }, + "table": { + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2025-02-01", + "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "properties": { + "plan": "[parameters('plan')]", + "restoredLogs": "[parameters('restoredLogs')]", + "retentionInDays": "[parameters('retentionInDays')]", + "schema": "[parameters('schema')]", + "searchResults": "[parameters('searchResults')]", + "totalRetentionInDays": "[parameters('totalRetentionInDays')]" } + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}/tables/{1}', parameters('workspaceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), 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": [ + "table" + ] } }, - "nullable": true, - "metadata": { - "description": "Optional. The list of AAAA records in the record set." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the AAAA record." - } - }, - "cnameType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "cnameRecord": { - "type": "object", - "properties": { - "cname": { + "outputs": { + "name": { "type": "string", "metadata": { - "description": "Required. The canonical name of the CNAME record." - } + "description": "The name of the table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the table." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the table was created in." + }, + "value": "[resourceGroup().name]" } - }, - "nullable": true, - "metadata": { - "description": "Optional. The CNAME record in the record set." } } }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the CNAME record." - } + "dependsOn": [ + "logAnalyticsWorkspace" + ] }, - "mxType": { - "type": "object", + "logAnalyticsWorkspace_solutions": { + "copy": { + "name": "logAnalyticsWorkspace_solutions", + "count": "[length(coalesce(parameters('gallerySolutions'), createArray()))]" + }, + "condition": "[not(empty(parameters('gallerySolutions')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-LAW-Solution-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } + "expressionEvaluationOptions": { + "scope": "inner" }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].name]" }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "mxRecords": { - "type": "array", - "items": { - "type": "object", - "properties": { - "exchange": { - "type": "string", - "metadata": { - "description": "Required. The domain name of the mail host for this MX record." - } - }, - "preference": { - "type": "int", - "metadata": { - "description": "Required. The preference value for this MX record." - } - } - } + "location": { + "value": "[parameters('location')]" }, - "nullable": true, - "metadata": { - "description": "Optional. The list of MX records in the record set." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the MX record." - } - }, - "ptrType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "plan": { + "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].plan]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" } }, - "ttl": { - "type": "int", - "nullable": true, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" + "_generator": { + "name": "bicep", + "version": "0.32.4.45862", + "templateHash": "10255889523646649592" + }, + "name": "Operations Management Solutions", + "description": "This module deploys an Operations Management Solution.", + "owner": "Azure/module-maintainers" }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "ptrRecords": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ptrdname": { - "type": "string", - "metadata": { - "description": "Required. The PTR target domain name for this PTR record." + "definitions": { + "solutionPlanType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + } + }, + "product": { + "type": "string", + "metadata": { + "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + } } + }, + "metadata": { + "__bicep_export!": true } } }, - "nullable": true, - "metadata": { - "description": "Optional. The list of PTR records in the record set." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the PTR record." - } - }, - "soaType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "soaRecord": { - "type": "object", - "properties": { - "email": { + "parameters": { + "name": { "type": "string", "metadata": { - "description": "Required. The email contact for this SOA record." + "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." } }, - "expireTime": { - "type": "int", + "plan": { + "$ref": "#/definitions/solutionPlanType", "metadata": { - "description": "Required. The expire time for this SOA record." + "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." } }, - "host": { + "logAnalyticsWorkspaceName": { "type": "string", "metadata": { - "description": "Required. The domain name of the authoritative name server for this SOA record." + "description": "Required. Name of the Log Analytics workspace where the solution will be deployed/enabled." } }, - "minimumTtl": { - "type": "int", + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "Required. The minimum value for this SOA record. By convention this is used to determine the negative caching duration." + "description": "Optional. Location for all resources." } }, - "refreshTime": { - "type": "int", + "enableTelemetry": { + "type": "bool", + "defaultValue": true, "metadata": { - "description": "Required. The refresh value for this SOA record." + "description": "Optional. Enable/Disable usage telemetry for module." } - }, - "retryTime": { - "type": "int", - "metadata": { - "description": "Required. The retry time for this SOA record." + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.operationsmanagement-solution.{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" + } + } + } } }, - "serialNumber": { - "type": "int", - "metadata": { - "description": "Required. The serial number for this SOA record." + "logAnalyticsWorkspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-06-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "solution": { + "type": "Microsoft.OperationsManagement/solutions", + "apiVersion": "2015-11-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "properties": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]" + }, + "plan": { + "name": "[coalesce(tryGet(parameters('plan'), 'name'), parameters('name'))]", + "promotionCode": "", + "product": "[parameters('plan').product]", + "publisher": "[coalesce(tryGet(parameters('plan'), 'publisher'), 'Microsoft')]" } } }, - "nullable": true, - "metadata": { - "description": "Optional. The SOA record in the record set." + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed solution." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed solution." + }, + "value": "[resourceId('Microsoft.OperationsManagement/solutions', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the solution is deployed." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('solution', '2015-11-01-preview', 'full').location]" + } } } }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed log analytics workspace." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", "metadata": { - "__bicep_export!": true, - "description": "The type for the SOA record." - } + "description": "The resource group of the deployed log analytics workspace." + }, + "value": "[resourceGroup().name]" }, - "srvType": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed log analytics workspace." + }, + "value": "[parameters('name')]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "The ID associated with the workspace." + }, + "value": "[reference('logAnalyticsWorkspace').customerId]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('logAnalyticsWorkspace', '2025-02-01', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('logAnalyticsWorkspace', '2025-02-01', 'full'), 'identity'), 'principalId')]" + }, + "primarySharedKey": { + "type": "securestring", + "metadata": { + "description": "The primary shared key of the log analytics workspace." + }, + "value": "[listKeys('logAnalyticsWorkspace', '2025-02-01').primarySharedKey]" + }, + "secondarySharedKey": { + "type": "securestring", + "metadata": { + "description": "The secondary shared key of the log analytics workspace." + }, + "value": "[listKeys('logAnalyticsWorkspace', '2025-02-01').secondarySharedKey]" + } + } + } + } + }, + "applicationInsights": { + "condition": "[parameters('enableMonitoring')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.insights.component.{0}', variables('applicationInsightsResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('applicationInsightsResourceName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "retentionInDays": { + "value": 365 + }, + "kind": { + "value": "web" + }, + "disableIpMasking": { + "value": false + }, + "flowType": { + "value": "Bluefield" + }, + "workspaceResourceId": "[if(parameters('enableMonitoring'), if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value)), createObject('value', ''))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]" + }, + "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.33.93.31351", + "templateHash": "5735496719243704506" + }, + "name": "Application Insights", + "description": "This component deploys an Application Insights instance." + }, + "definitions": { + "diagnosticSettingFullType": { "type": "object", "properties": { "name": { "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, "nullable": true, "metadata": { - "description": "Optional. Array of role assignments to create." + "description": "Optional. The name of the diagnostic setting." } }, - "srvRecords": { + "logCategoriesAndGroups": { "type": "array", "items": { "type": "object", "properties": { - "priority": { - "type": "int", - "metadata": { - "description": "Required. The priority value for this SRV record." - } - }, - "weight": { - "type": "int", + "category": { + "type": "string", + "nullable": true, "metadata": { - "description": "Required. The weight value for this SRV record." + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." } }, - "port": { - "type": "int", + "categoryGroup": { + "type": "string", + "nullable": true, "metadata": { - "description": "Required. The port value for this SRV record." + "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." } }, - "target": { - "type": "string", + "enabled": { + "type": "bool", + "nullable": true, "metadata": { - "description": "Required. The target domain name for this SRV record." + "description": "Optional. Enable or disable the category explicitly. Default is `true`." } } } }, "nullable": true, "metadata": { - "description": "Optional. The list of SRV records in the record set." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the SRV record." - } - }, - "txtType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata of the record." - } - }, - "ttl": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. The TTL of the record." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." } }, - "txtRecords": { + "metricCategories": { "type": "array", "items": { "type": "object", "properties": { - "value": { - "type": "array", - "items": { - "type": "string" - }, + "category": { + "type": "string", "metadata": { - "description": "Required. The text value of this TXT record." + "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 list of TXT records in the record set." + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the TXT record." - } - }, - "virtualNetworkLinkType": { - "type": "object", - "properties": { - "name": { + }, + "logAnalyticsDestinationType": { "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], "nullable": true, - "minLength": 1, - "maxLength": 80, "metadata": { - "description": "Optional. The resource name." + "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." } }, - "virtualNetworkResourceId": { + "workspaceResourceId": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The resource ID of the virtual network to link." + "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." } }, - "location": { + "storageAccountResourceId": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. The Azure Region where the resource lives." + "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." } }, - "registrationEnabled": { - "type": "bool", + "eventHubAuthorizationRuleResourceId": { + "type": "string", "nullable": true, "metadata": { - "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?." + "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." } }, - "tags": { - "type": "object", + "eventHubName": { + "type": "string", "nullable": true, "metadata": { - "description": "Optional. Resource tags." + "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." } }, - "resolutionPolicy": { + "marketplacePartnerResourceId": { "type": "string", - "allowedValues": [ - "Default", - "NxDomainRedirect" - ], "nullable": true, "metadata": { - "description": "Optional. The resolution type of the private-dns-zone fallback machanism." + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." } } }, "metadata": { - "__bicep_export!": true, - "description": "The type for the virtual network link." + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.3.0" + } } }, "lockType": { @@ -17328,7 +3835,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.3.0" } } } @@ -17337,104 +3844,136 @@ "name": { "type": "string", "metadata": { - "description": "Required. Private DNS zone name." + "description": "Required. Name of the Application Insights." } }, - "a": { - "type": "array", - "items": { - "$ref": "#/definitions/aType" - }, - "nullable": true, + "applicationType": { + "type": "string", + "defaultValue": "web", + "allowedValues": [ + "web", + "other" + ], "metadata": { - "description": "Optional. Array of A records." + "description": "Optional. Application type." } }, - "aaaa": { - "type": "array", - "items": { - "$ref": "#/definitions/aaaaType" - }, - "nullable": true, + "workspaceResourceId": { + "type": "string", "metadata": { - "description": "Optional. Array of AAAA records." + "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." } }, - "cname": { - "type": "array", - "items": { - "$ref": "#/definitions/cnameType" - }, - "nullable": true, + "disableIpMasking": { + "type": "bool", + "defaultValue": true, "metadata": { - "description": "Optional. Array of CNAME records." + "description": "Optional. Disable IP masking. Default value is set to true." } }, - "mx": { - "type": "array", - "items": { - "$ref": "#/definitions/mxType" - }, - "nullable": true, + "disableLocalAuth": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Optional. Array of MX records." + "description": "Optional. Disable Non-AAD based Auth. Default value is set to false." } }, - "ptr": { - "type": "array", - "items": { - "$ref": "#/definitions/ptrType" - }, - "nullable": true, + "forceCustomerStorageForProfiler": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Optional. Array of PTR records." + "description": "Optional. Force users to create their own storage account for profiler and debugger." } }, - "soa": { - "type": "array", - "items": { - "$ref": "#/definitions/soaType" - }, + "linkedStorageAccountResourceId": { + "type": "string", "nullable": true, "metadata": { - "description": "Optional. Array of SOA records." + "description": "Optional. Linked storage account resource ID." } }, - "srv": { - "type": "array", - "items": { - "$ref": "#/definitions/srvType" - }, - "nullable": true, + "publicNetworkAccessForIngestion": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], "metadata": { - "description": "Optional. Array of SRV records." + "description": "Optional. The network access type for accessing Application Insights ingestion. - Enabled or Disabled." } }, - "txt": { - "type": "array", - "items": { - "$ref": "#/definitions/txtType" - }, + "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." + } + }, + "flowType": { + "type": "string", "nullable": true, "metadata": { - "description": "Optional. Array of TXT records." + "description": "Optional. Used by the Application Insights system to determine what kind of flow this component was created by. This is to be set to 'Bluefield' when creating/updating a component via the REST API." } }, - "virtualNetworkLinks": { - "type": "array", - "items": { - "$ref": "#/definitions/virtualNetworkLinkType" - }, + "requestSource": { + "type": "string", "nullable": true, "metadata": { - "description": "Optional. Array of custom objects describing vNet links of the DNS zone. Each object should contain properties 'virtualNetworkResourceId' and 'registrationEnabled'. The 'vnetResourceId' is a resource ID of a vNet to link, 'registrationEnabled' (bool) enables automatic DNS registration in the zone for the linked vNet." + "description": "Optional. Describes what tool created this Application Insights component. Customers using this API should set this to the default 'rest'." + } + }, + "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": "global", + "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "Optional. The location of the PrivateDNSZone. Should be global." + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." } }, "roleAssignments": { @@ -17454,19 +3993,22 @@ "description": "Optional. Tags of the resource." } }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, "enableTelemetry": { "type": "bool", "defaultValue": true, "metadata": { "description": "Optional. Enable/Disable usage telemetry for module." } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } } }, "variables": { @@ -17479,11 +4021,14 @@ ], "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')]", - "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": "[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')]", + "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": { @@ -17491,7 +4036,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.network-privatednszone.{0}.{1}', replace('0.7.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.insights-component.{0}.{1}', replace('0.6.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -17507,36 +4052,36 @@ } } }, - "privateDnsZone": { - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", + "appInsights": { + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", "name": "[parameters('name')]", "location": "[parameters('location')]", - "tags": "[parameters('tags')]" - }, - "privateDnsZone_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/privateDnsZones/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", "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": [ - "privateDnsZone" - ] + "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')]", + "Flow_Type": "[parameters('flowType')]", + "Request_Source": "[parameters('requestSource')]" + } }, - "privateDnsZone_roleAssignments": { + "appInsights_roleAssignments": { "copy": { - "name": "privateDnsZone_roleAssignments", + "name": "appInsights_roleAssignments", "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "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]", @@ -17547,509 +4092,137 @@ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ - "privateDnsZone" + "appInsights" ] }, - "privateDnsZone_A": { + "appInsights_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.Insights/components/{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": [ + "appInsights" + ] + }, + "appInsights_diagnosticSettings": { "copy": { - "name": "privateDnsZone_A", - "count": "[length(coalesce(parameters('a'), createArray()))]" + "name": "appInsights_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-ARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "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": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(parameters('a'), createArray())[copyIndex()].name]" - }, - "aRecords": { - "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'aRecords')]" - }, - "metadata": { - "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'metadata')]" - }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'ttl'), 3600)]" - }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('a'), 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.34.44.8038", - "templateHash": "18243374258187942664" - }, - "name": "Private DNS Zone A record", - "description": "This module deploys a Private DNS Zone A record." - }, - "definitions": { - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "privateDnsZoneName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the A record." - } - }, - "aRecords": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The list of A records in the record set." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata attached to the record set." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, - "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "A": { - "type": "Microsoft.Network/privateDnsZones/A", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "properties": { - "aRecords": "[parameters('aRecords')]", - "metadata": "[parameters('metadata')]", - "ttl": "[parameters('ttl')]" - } - }, - "A_roleAssignments": { - "copy": { - "name": "A_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/A/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), 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": [ - "A" - ] + "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 } }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed A record." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed A record." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed A record." - }, - "value": "[resourceGroup().name]" + { + "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": [ - "privateDnsZone" + "appInsights" ] }, - "privateDnsZone_AAAA": { - "copy": { - "name": "privateDnsZone_AAAA", - "count": "[length(coalesce(parameters('aaaa'), createArray()))]" - }, + "linkedStorageAccount": { + "condition": "[not(empty(parameters('linkedStorageAccountResourceId')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-AAAARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-appInsights-linkedStorageAccount', uniqueString(deployment().name, parameters('location')))]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "privateDnsZoneName": { + "appInsightsName": { "value": "[parameters('name')]" }, - "name": { - "value": "[coalesce(parameters('aaaa'), createArray())[copyIndex()].name]" - }, - "aaaaRecords": { - "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'aaaaRecords')]" - }, - "metadata": { - "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'metadata')]" - }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'ttl'), 3600)]" - }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'roleAssignments')]" + "storageAccountResourceId": { + "value": "[coalesce(parameters('linkedStorageAccountResourceId'), '')]" } }, "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.34.44.8038", - "templateHash": "7322684246075092047" + "version": "0.33.93.31351", + "templateHash": "10861379689695100897" }, - "name": "Private DNS Zone AAAA record", - "description": "This module deploys a Private DNS Zone AAAA record." - }, - "definitions": { - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } + "name": "Application Insights Linked Storage Account", + "description": "This component deploys an Application Insights Linked Storage Account." }, "parameters": { - "privateDnsZoneName": { + "appInsightsName": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + "description": "Conditional. The name of the parent Application Insights instance. Required if the template is used in a standalone deployment." } }, - "name": { + "storageAccountResourceId": { "type": "string", "metadata": { - "description": "Required. The name of the AAAA record." - } - }, - "aaaaRecords": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The list of AAAA records in the record set." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata attached to the record set." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, - "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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)))))]" + "description": "Required. Linked storage account resource ID." } - ], - "builtInRoleNames": { - "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "AAAA": { - "type": "Microsoft.Network/privateDnsZones/AAAA", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "resources": [ + { + "type": "microsoft.insights/components/linkedStorageAccounts", + "apiVersion": "2020-03-01-preview", + "name": "[format('{0}/{1}', parameters('appInsightsName'), 'ServiceProfiler')]", "properties": { - "aaaaRecords": "[parameters('aaaaRecords')]", - "metadata": "[parameters('metadata')]", - "ttl": "[parameters('ttl')]" + "linkedStorageAccount": "[parameters('storageAccountResourceId')]" } - }, - "AAAA_roleAssignments": { - "copy": { - "name": "AAAA_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/AAAA/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), 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": [ - "AAAA" - ] } - }, + ], "outputs": { "name": { "type": "string", "metadata": { - "description": "The name of the deployed AAAA record." + "description": "The name of the Linked Storage Account." }, - "value": "[parameters('name')]" + "value": "ServiceProfiler" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the deployed AAAA record." + "description": "The resource ID of the Linked Storage Account." }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), parameters('name'))]" + "value": "[resourceId('microsoft.insights/components/linkedStorageAccounts', parameters('appInsightsName'), 'ServiceProfiler')]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The resource group of the deployed AAAA record." + "description": "The resource group the agent pool was deployed into." }, "value": "[resourceGroup().name]" } @@ -18057,254 +4230,491 @@ } }, "dependsOn": [ - "privateDnsZone" + "appInsights" ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the application insights component." + }, + "value": "[parameters('name')]" }, - "privateDnsZone_CNAME": { + "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]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "userAssignedIdentity": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('userAssignedIdentityResourceName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('userAssignedIdentityResourceName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.34.44.8038", + "templateHash": "16707109626832623586" + }, + "name": "User Assigned Identities", + "description": "This module deploys a User Assigned Identity." + }, + "definitions": { + "federatedIdentityCredentialType": { + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the federated identity credential." + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/federatedIdentityCredentialType" + }, + "nullable": true, + "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", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "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": "[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.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" + } + } + } + } + }, + "userAssignedIdentity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2024-11-30", + "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": "privateDnsZone_CNAME", - "count": "[length(coalesce(parameters('cname'), createArray()))]" + "name": "userAssignedIdentity_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ManagedIdentity/userAssignedIdentities/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 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": [ + "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}-PrivateDnsZone-CNAMERecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-UserMSI-FederatedIdentityCred-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, "name": { - "value": "[coalesce(parameters('cname'), createArray())[copyIndex()].name]" + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].name]" }, - "cnameRecord": { - "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'cnameRecord')]" + "userAssignedIdentityName": { + "value": "[parameters('name')]" }, - "metadata": { - "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'metadata')]" + "audiences": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].audiences]" }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'ttl'), 3600)]" + "issuer": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].issuer]" }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'roleAssignments')]" + "subject": { + "value": "[coalesce(parameters('federatedIdentityCredentials'), createArray())[copyIndex()].subject]" } }, "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.34.44.8038", - "templateHash": "5264706240021075859" + "templateHash": "13656021764446440473" }, - "name": "Private DNS Zone CNAME record", - "description": "This module deploys a Private DNS Zone CNAME record." - }, - "definitions": { - "roleAssignmentType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } + "name": "User Assigned Identity Federated Identity Credential", + "description": "This module deploys a User Assigned Identity Federated Identity Credential." }, "parameters": { - "privateDnsZoneName": { + "userAssignedIdentityName": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + "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 CNAME record." - } - }, - "cnameRecord": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. A CNAME record." + "description": "Required. The name of the secret." } }, - "metadata": { - "type": "object", - "nullable": true, + "audiences": { + "type": "array", "metadata": { - "description": "Optional. The metadata attached to the record set." + "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." } }, - "ttl": { - "type": "int", - "defaultValue": 3600, + "issuer": { + "type": "string", "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." + "description": "Required. The URL of the issuer to be trusted. Must match the issuer claim of the external token being exchanged." } }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, + "subject": { + "type": "string", "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)))))]" + "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." } - ], - "builtInRoleNames": { - "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "CNAME": { - "type": "Microsoft.Network/privateDnsZones/CNAME", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials", + "apiVersion": "2024-11-30", + "name": "[format('{0}/{1}', parameters('userAssignedIdentityName'), parameters('name'))]", "properties": { - "cnameRecord": "[parameters('cnameRecord')]", - "metadata": "[parameters('metadata')]", - "ttl": "[parameters('ttl')]" + "audiences": "[parameters('audiences')]", + "issuer": "[parameters('issuer')]", + "subject": "[parameters('subject')]" } - }, - "CNAME_roleAssignments": { - "copy": { - "name": "CNAME_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/CNAME/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), 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": [ - "CNAME" - ] } - }, + ], "outputs": { "name": { "type": "string", "metadata": { - "description": "The name of the deployed CNAME record." + "description": "The name of the federated identity credential." }, "value": "[parameters('name')]" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the deployed CNAME record." + "description": "The resource ID of the federated identity credential." }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), parameters('name'))]" + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials', parameters('userAssignedIdentityName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The resource group of the deployed CNAME record." + "description": "The name of the resource group the federated identity credential was created in." }, "value": "[resourceGroup().name]" } @@ -18312,40 +4722,366 @@ } }, "dependsOn": [ - "privateDnsZone" + "userAssignedIdentity" ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the user assigned identity." + }, + "value": "[parameters('name')]" }, - "privateDnsZone_MX": { - "copy": { - "name": "privateDnsZone_MX", - "count": "[length(coalesce(parameters('mx'), createArray()))]" + "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', '2024-11-30', 'full').location]" + } + } + } + } + }, + "network": { + "condition": "[parameters('enablePrivateNetworking')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('network-{0}-deployment', variables('resourcesName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "resourcesName": { + "value": "[variables('resourcesName')]" + }, + "logAnalyticsWorkSpaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value))]", + "vmAdminUsername": { + "value": "[coalesce(parameters('vmAdminUsername'), 'JumpboxAdminUser')]" + }, + "vmAdminPassword": { + "value": "[coalesce(parameters('vmAdminPassword'), 'JumpboxAdminP@ssw0rd1234!')]" + }, + "vmSize": { + "value": "[coalesce(parameters('vmSize'), 'Standard_DS2_v2')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[variables('allTags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "10866545553438202136" + } + }, + "parameters": { + "resourcesName": { + "type": "string", + "metadata": { + "description": "Required. Named used for all resource naming." + } + }, + "logAnalyticsWorkSpaceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the Log Analytics Workspace for monitoring and diagnostics." + } + }, + "location": { + "type": "string", + "minLength": 3, + "metadata": { + "description": "Required. Azure region for all services." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "vmAdminUsername": { + "type": "securestring", + "metadata": { + "description": "Required. Admin username for the VM." + } + }, + "vmAdminPassword": { + "type": "securestring", + "metadata": { + "description": "Required. Admin password for the VM." + } + }, + "vmSize": { + "type": "string", + "metadata": { + "description": "Required. VM size for the Jumpbox VM." + } + } + }, + "resources": [ + { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-MXRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[take(format('network-{0}-create', parameters('resourcesName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" + "resourcesName": { + "value": "[parameters('resourcesName')]" }, - "name": { - "value": "[coalesce(parameters('mx'), createArray())[copyIndex()].name]" + "location": { + "value": "[parameters('location')]" }, - "metadata": { - "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'metadata')]" + "logAnalyticsWorkSpaceResourceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" }, - "mxRecords": { - "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'mxRecords')]" + "tags": { + "value": "[parameters('tags')]" }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'ttl'), 3600)]" + "addressPrefixes": { + "value": [ + "10.0.0.0/20" + ] }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'roleAssignments')]" + "subnets": { + "value": [ + { + "name": "web", + "addressPrefixes": [ + "10.0.0.0/23" + ], + "networkSecurityGroup": { + "name": "nsg-web", + "securityRules": [ + { + "name": "AllowHttpsInbound", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 100, + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefixes": [ + "0.0.0.0/0" + ], + "destinationAddressPrefixes": [ + "10.0.0.0/23" + ] + } + }, + { + "name": "AllowIntraSubnetTraffic", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 200, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefixes": [ + "10.0.0.0/23" + ], + "destinationAddressPrefixes": [ + "10.0.0.0/23" + ] + } + }, + { + "name": "AllowAzureLoadBalancer", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 300, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "AzureLoadBalancer", + "destinationAddressPrefix": "10.0.0.0/23" + } + } + ] + }, + "delegation": "Microsoft.Web/serverFarms" + }, + { + "name": "peps", + "addressPrefixes": [ + "10.0.2.0/23" + ], + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Disabled", + "networkSecurityGroup": { + "name": "nsg-peps", + "securityRules": [] + } + } + ] + }, + "bastionConfiguration": { + "value": { + "name": "[format('bas-{0}', parameters('resourcesName'))]", + "subnet": { + "name": "AzureBastionSubnet", + "addressPrefixes": [ + "10.0.10.0/26" + ], + "networkSecurityGroup": { + "name": "nsg-AzureBastionSubnet", + "securityRules": [ + { + "name": "AllowGatewayManager", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 2702, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefix": "GatewayManager", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowHttpsInBound", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 2703, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefix": "Internet", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowSshRdpOutbound", + "properties": { + "access": "Allow", + "direction": "Outbound", + "priority": 100, + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRanges": [ + "22", + "3389" + ], + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowAzureCloudOutbound", + "properties": { + "access": "Allow", + "direction": "Outbound", + "priority": 110, + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "443", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "AzureCloud" + } + } + ] + } + } + } + }, + "jumpboxConfiguration": { + "value": { + "name": "[format('vm-jumpbox-{0}', parameters('resourcesName'))]", + "size": "[parameters('vmSize')]", + "username": "[parameters('vmAdminUsername')]", + "password": "[parameters('vmAdminPassword')]", + "subnet": { + "name": "jumpbox", + "addressPrefixes": [ + "10.0.12.0/23" + ], + "networkSecurityGroup": { + "name": "nsg-jumbox", + "securityRules": [ + { + "name": "AllowRdpFromBastion", + "properties": { + "access": "Allow", + "direction": "Inbound", + "priority": 100, + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "3389", + "sourceAddressPrefixes": [ + "10.0.10.0/26" + ], + "destinationAddressPrefixes": [ + "10.0.12.0/23" + ] + } + } + ] + } + } + } + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" } }, "template": { @@ -18355,1439 +5091,15438 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "13758189936483275969" - }, - "name": "Private DNS Zone MX record", - "description": "This module deploys a Private DNS Zone MX record." + "version": "0.37.4.10188", + "templateHash": "10645092392603021381" + } }, "definitions": { - "roleAssignmentType": { + "_1.networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "description": "Custom type definition for network security group configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + }, + "bastionHostConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Bastion Host resource." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "metadata": { + "description": "Custom type definition for establishing Bastion Host for remote connection.", + "__bicep_imported_from!": { + "sourceTemplate": "bastionHost.bicep" + } + } + }, + "jumpBoxConfigurationType": { "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." + "description": "The name of the Virtual Machine." } }, - "roleDefinitionIdOrName": { + "size": { "type": "string", + "nullable": true, "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'." + "description": "The size of the VM." } }, - "principalId": { + "username": { "type": "string", "metadata": { - "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + "description": "Username to access VM." } }, - "principalType": { - "type": "string", - "allowedValues": [ - "Device", - "ForeignGroup", - "Group", - "ServicePrincipal", - "User" - ], - "nullable": true, + "password": { + "type": "securestring", "metadata": { - "description": "Optional. The principal type of the assigned principal ID." + "description": "Password to access VM." } }, - "description": { - "type": "string", + "subnet": { + "$ref": "#/definitions/subnetType", "nullable": true, "metadata": { - "description": "Optional. The description of the role assignment." + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "metadata": { + "description": "Custom type definition for establishing Jumpbox Virtual Machine and its associated resources.", + "__bicep_imported_from!": { + "sourceTemplate": "jumpbox.bicep" + } + } + }, + "subnetOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the subnet." } }, - "condition": { + "resourceId": { "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\"." + "description": "The resource ID of the subnet." } }, - "conditionVersion": { + "nsgName": { "type": "string", - "allowedValues": [ - "2.0" - ], "nullable": true, "metadata": { - "description": "Optional. Version of the condition." + "description": "The name of the associated network security group, if any." } }, - "delegatedManagedIdentityResourceId": { + "nsgResourceId": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." + "description": "The resource ID of the associated network security group, if any." } } }, "metadata": { - "description": "An AVM-aligned type for a role assignment.", + "description": "Custom type definition for subnet resource information as output", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "virtualNetwork.bicep" } } - } - }, - "parameters": { - "privateDnsZoneName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the MX record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata attached to the record set." - } - }, - "mxRecords": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The list of MX records in the record set." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, - "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "MX": { - "type": "Microsoft.Network/privateDnsZones/MX", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "properties": { - "metadata": "[parameters('metadata')]", - "mxRecords": "[parameters('mxRecords')]", - "ttl": "[parameters('ttl')]" - } - }, - "MX_roleAssignments": { - "copy": { - "name": "MX_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/MX/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), 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": [ - "MX" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed MX record." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed MX record." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), parameters('name'))]" }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed MX record." - }, - "value": "[resourceGroup().name]" - } - } - } - }, - "dependsOn": [ - "privateDnsZone" - ] - }, - "privateDnsZone_PTR": { - "copy": { - "name": "privateDnsZone_PTR", - "count": "[length(coalesce(parameters('ptr'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-PTRRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(parameters('ptr'), createArray())[copyIndex()].name]" - }, - "metadata": { - "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'metadata')]" - }, - "ptrRecords": { - "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ptrRecords')]" - }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ttl'), 3600)]" - }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('ptr'), 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.34.44.8038", - "templateHash": "11955164584650609753" - }, - "name": "Private DNS Zone PTR record", - "description": "This module deploys a Private DNS Zone PTR record." - }, - "definitions": { - "roleAssignmentType": { + "subnetType": { "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." + "description": "Required. The Name of the subnet resource." } }, - "roleDefinitionIdOrName": { - "type": "string", + "addressPrefixes": { + "type": "array", + "items": { + "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'." + "description": "Required. Prefixes for the subnet." } }, - "principalId": { + "delegation": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + "description": "Optional. The delegation to enable on the subnet." } }, - "principalType": { + "privateEndpointNetworkPolicies": { "type": "string", "allowedValues": [ - "Device", - "ForeignGroup", - "Group", - "ServicePrincipal", - "User" + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" ], "nullable": true, "metadata": { - "description": "Optional. The principal type of the assigned principal ID." + "description": "Optional. enable or disable apply network policies on private endpoint in the subnet." } }, - "description": { + "privateLinkServiceNetworkPolicies": { "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], "nullable": true, "metadata": { - "description": "Optional. The description of the role assignment." + "description": "Optional. Enable or disable apply network policies on private link service in the subnet." } }, - "condition": { - "type": "string", + "networkSecurityGroup": { + "$ref": "#/definitions/_1.networkSecurityGroupType", "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\"." + "description": "Optional. Network Security Group configuration for the subnet." } }, - "conditionVersion": { + "routeTableResourceId": { "type": "string", - "allowedValues": [ - "2.0" - ], "nullable": true, "metadata": { - "description": "Optional. Version of the condition." + "description": "Optional. The resource ID of the route table to assign to the subnet." } }, - "delegatedManagedIdentityResourceId": { - "type": "string", + "serviceEndpointPolicies": { + "type": "array", + "items": { + "type": "object" + }, "nullable": true, "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." + "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." } } }, "metadata": { - "description": "An AVM-aligned type for a role assignment.", + "description": "Custom type definition for subnet configuration", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "virtualNetwork.bicep" } } } }, "parameters": { - "privateDnsZoneName": { + "resourcesName": { "type": "string", + "minLength": 6, + "maxLength": 25, "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + "description": "Name used for naming all network resources." } }, - "name": { + "location": { "type": "string", + "minLength": 3, "metadata": { - "description": "Required. The name of the PTR record." + "description": "Azure region for all services." } }, - "metadata": { - "type": "object", - "nullable": true, + "logAnalyticsWorkSpaceResourceId": { + "type": "string", "metadata": { - "description": "Optional. The metadata attached to the record set." + "description": "Resource ID of the Log Analytics Workspace for monitoring and diagnostics." } }, - "ptrRecords": { + "addressPrefixes": { "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The list of PTR records in the record set." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." + "description": "Networking address prefix for the VNET." } }, - "roleAssignments": { + "subnets": { "type": "array", "items": { - "$ref": "#/definitions/roleAssignmentType" + "$ref": "#/definitions/subnetType" }, - "nullable": true, "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')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "PTR": { - "type": "Microsoft.Network/privateDnsZones/PTR", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "properties": { - "metadata": "[parameters('metadata')]", - "ptrRecords": "[parameters('ptrRecords')]", - "ttl": "[parameters('ttl')]" + "description": "Array of subnets to be created within the VNET." } }, - "PTR_roleAssignments": { - "copy": { - "name": "PTR_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/PTR/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), 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": [ - "PTR" - ] - } - }, - "outputs": { - "name": { - "type": "string", + "jumpboxConfiguration": { + "$ref": "#/definitions/jumpBoxConfigurationType", + "nullable": true, "metadata": { - "description": "The name of the deployed PTR record." - }, - "value": "[parameters('name')]" + "description": "Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation." + } }, - "resourceId": { - "type": "string", + "bastionConfiguration": { + "$ref": "#/definitions/bastionHostConfigurationType", + "nullable": true, "metadata": { - "description": "The resource ID of the deployed PTR record." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), parameters('name'))]" + "description": "Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation." + } }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed PTR record." - }, - "value": "[resourceGroup().name]" - } - } - } - }, - "dependsOn": [ - "privateDnsZone" - ] - }, - "privateDnsZone_SOA": { - "copy": { - "name": "privateDnsZone_SOA", - "count": "[length(coalesce(parameters('soa'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-SOARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(parameters('soa'), createArray())[copyIndex()].name]" - }, - "metadata": { - "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'metadata')]" - }, - "soaRecord": { - "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'soaRecord')]" - }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'ttl'), 3600)]" - }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('soa'), 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.34.44.8038", - "templateHash": "14626715835033259725" + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } }, - "name": "Private DNS Zone SOA record", - "description": "This module deploys a Private DNS Zone SOA record." + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } }, - "definitions": { - "roleAssignmentType": { - "type": "object", + "resources": { + "virtualNetwork": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-virtualNetwork', parameters('resourcesName'))]", "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\"." - } + "expressionEvaluationOptions": { + "scope": "inner" }, - "conditionVersion": { - "type": "string", - "allowedValues": [ - "2.0" - ], - "nullable": true, - "metadata": { - "description": "Optional. Version of the condition." + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('vnet-{0}', parameters('resourcesName'))]" + }, + "addressPrefixes": { + "value": "[parameters('addressPrefixes')]" + }, + "subnets": { + "value": "[parameters('subnets')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" } }, - "delegatedManagedIdentityResourceId": { - "type": "string", - "nullable": true, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "1936468723755149871" + } + }, + "definitions": { + "subnetOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the subnet." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the subnet." + } + }, + "nsgName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The name of the associated network security group, if any." + } + }, + "nsgResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The resource ID of the associated network security group, if any." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for subnet resource information as output" + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for subnet configuration" + } + }, + "networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for network security group configuration" + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the virtual network." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region to deploy resources." + } + }, + "addressPrefixes": { + "type": "array", + "metadata": { + "description": "Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`." + } + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetType" + }, + "metadata": { + "description": "An array of subnets to be created within the virtual network. Each subnet can have its own configuration and associated Network Security Group (NSG)." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to be applied to the resources." + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "nsgs": { + "copy": { + "name": "nsgs", + "count": "[length(parameters('subnets'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-{1}-networksecuritygroup', parameters('name'), tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), parameters('name'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "securityRules": { + "value": "[tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'securityRules')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.33.93.31351", + "templateHash": "2305747478751645177" + }, + "name": "Network Security Groups", + "description": "This module deploys a Network security Group (NSG)." + }, + "definitions": { + "securityRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security rule." + } + }, + "properties": { + "type": "object", + "properties": { + "access": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Whether network traffic is allowed or denied." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the security rule." + } + }, + "destinationAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." + } + }, + "destinationAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." + } + }, + "destinationApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as destination." + } + }, + "destinationPortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "destinationPortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination port ranges." + } + }, + "direction": { + "type": "string", + "allowedValues": [ + "Inbound", + "Outbound" + ], + "metadata": { + "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 4096, + "metadata": { + "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "Ah", + "Esp", + "Icmp", + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Network protocol this rule applies to." + } + }, + "sourceAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." + } + }, + "sourceAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP ranges." + } + }, + "sourceApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as source." + } + }, + "sourcePortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "sourcePortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The source port ranges." + } + } + }, + "metadata": { + "description": "Required. The properties of the security rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a security rule." + } + }, + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Network Security Group." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "securityRules": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." + } + }, + "flushConnection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" + } + } + } + } + }, + "networkSecurityGroup": { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "securityRules", + "count": "[length(coalesce(parameters('securityRules'), createArray()))]", + "input": { + "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", + "properties": { + "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", + "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", + "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", + "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", + "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", + "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", + "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", + "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", + "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", + "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", + "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", + "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", + "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", + "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", + "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" + } + } + } + ], + "flushConnection": "[parameters('flushConnection')]" + } + }, + "networkSecurityGroup_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/networkSecurityGroups/{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": [ + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ + "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": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-virtualNetwork', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "addressPrefixes": { + "value": "[parameters('addressPrefixes')]" + }, + "subnets": { + "copy": [ + { + "name": "value", + "count": "[length(parameters('subnets'))]", + "input": "[createObject('name', parameters('subnets')[copyIndex('value')].name, 'addressPrefixes', tryGet(parameters('subnets')[copyIndex('value')], 'addressPrefixes'), 'networkSecurityGroupResourceId', if(not(empty(tryGet(parameters('subnets')[copyIndex('value')], 'networkSecurityGroup'))), reference(format('nsgs[{0}]', copyIndex('value'))).outputs.resourceId.value, null()), 'privateEndpointNetworkPolicies', tryGet(parameters('subnets')[copyIndex('value')], 'privateEndpointNetworkPolicies'), 'privateLinkServiceNetworkPolicies', tryGet(parameters('subnets')[copyIndex('value')], 'privateLinkServiceNetworkPolicies'), 'delegation', tryGet(parameters('subnets')[copyIndex('value')], 'delegation'))]" + } + ] + }, + "diagnosticSettings": { + "value": [ + { + "name": "vnetDiagnostics", + "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metricCategories": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + } + ] + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.35.1.17967", + "templateHash": "16195883788906927531" + }, + "name": "Virtual Networks", + "description": "This module deploys a Virtual Network (vNet)." + }, + "definitions": { + "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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "prefixItems": [ + { + "type": "object", + "properties": { + "pool": { + "type": "object", + "properties": { + "id": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the IPAM pool." + } + } + }, + "metadata": { + "description": "Required. The Resource ID of the IPAM pool." + } + }, + "numberOfIpAddresses": { + "type": "string", + "metadata": { + "description": "Required. Number of IP addresses allocated from the pool." + } + } + } + } + ], + "items": false, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` is empty and the VNet address space configured to use IPAM Pool." + } + }, + "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", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "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 OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`." + } + }, + "ipamPoolNumberOfIpAddresses": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Number of IP addresses allocated from the pool. To be used only when the addressPrefix param is defined with a resource ID of an IPAM pool." + } + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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.7.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-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "addressSpace": "[if(contains(parameters('addressPrefixes')[0], '/Microsoft.Network/networkManagers/'), createObject('ipamPoolPrefixAllocations', createArray(createObject('pool', createObject('id', parameters('addressPrefixes')[0]), 'numberOfIpAddresses', parameters('ipamPoolNumberOfIpAddresses')))), createObject('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')]" + }, + "ipamPoolPrefixAllocations": { + "value": "[tryGet(coalesce(parameters('subnets'), createArray())[copyIndex()], 'ipamPoolPrefixAllocations')]" + }, + "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')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "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.35.1.17967", + "templateHash": "9728353654559466189" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. 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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", + "nullable": true, + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "nullable": true, + "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 the 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 the 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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-05-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')]", + "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", + "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": "[parameters('privateEndpointNetworkPolicies')]", + "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + } + }, + "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())]" + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "metadata": { + "description": "The IPAM pool prefix allocations for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), 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.35.1.17967", + "templateHash": "11179987886456111827" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering." + }, + "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_subnets" + ] + }, + "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.35.1.17967", + "templateHash": "11179987886456111827" + }, + "name": "Virtual Network Peerings", + "description": "This module deploys a Virtual Network Peering." + }, + "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_subnets" + ] + } + }, + "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-05-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "nsgs" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "value": "[reference('virtualNetwork').outputs.name.value]" + }, + "resourceId": { + "type": "string", + "value": "[reference('virtualNetwork').outputs.resourceId.value]" + }, + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOutputType" + }, + "copy": { + "count": "[length(parameters('subnets'))]", + "input": { + "name": "[parameters('subnets')[copyIndex()].name]", + "resourceId": "[reference('virtualNetwork').outputs.subnetResourceIds.value[copyIndex()]]", + "nsgName": "[if(not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup'))), tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), null())]", + "nsgResourceId": "[if(not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup'))), reference(format('nsgs[{0}]', copyIndex())).outputs.resourceId.value, null())]" + } + } + } } } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } } - } - }, - "parameters": { - "privateDnsZoneName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the SOA record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata attached to the record set." - } - }, - "soaRecord": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. A SOA record." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, - "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "SOA": { - "type": "Microsoft.Network/privateDnsZones/SOA", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "properties": { - "metadata": "[parameters('metadata')]", - "soaRecord": "[parameters('soaRecord')]", - "ttl": "[parameters('ttl')]" - } - }, - "SOA_roleAssignments": { - "copy": { - "name": "SOA_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SOA/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), 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": [ - "SOA" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed SOA record." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed SOA record." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed SOA record." - }, - "value": "[resourceGroup().name]" - } - } - } - }, - "dependsOn": [ - "privateDnsZone" - ] - }, - "privateDnsZone_SRV": { - "copy": { - "name": "privateDnsZone_SRV", - "count": "[length(coalesce(parameters('srv'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-SRVRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(parameters('srv'), createArray())[copyIndex()].name]" - }, - "metadata": { - "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'metadata')]" - }, - "srvRecords": { - "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'srvRecords')]" - }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'ttl'), 3600)]" - }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('srv'), 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.34.44.8038", - "templateHash": "6510442308165042737" }, - "name": "Private DNS Zone SRV record", - "description": "This module deploys a Private DNS Zone SRV record." - }, - "definitions": { - "roleAssignmentType": { - "type": "object", + "bastionHost": { + "condition": "[not(empty(parameters('bastionConfiguration')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-bastionHost', parameters('resourcesName'))]", "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\"." - } + "expressionEvaluationOptions": { + "scope": "inner" }, - "conditionVersion": { - "type": "string", - "allowedValues": [ - "2.0" - ], - "nullable": true, - "metadata": { - "description": "Optional. Version of the condition." + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(parameters('bastionConfiguration'), 'name'), format('bas-{0}', parameters('resourcesName')))]" + }, + "vnetId": { + "value": "[reference('virtualNetwork').outputs.resourceId.value]" + }, + "vnetName": { + "value": "[reference('virtualNetwork').outputs.name.value]" + }, + "location": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "subnet": { + "value": "[tryGet(parameters('bastionConfiguration'), 'subnet')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" } }, - "delegatedManagedIdentityResourceId": { - "type": "string", - "nullable": true, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "privateDnsZoneName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the SRV record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata attached to the record set." - } - }, - "srvRecords": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The list of SRV records in the record set." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, - "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "SRV": { - "type": "Microsoft.Network/privateDnsZones/SRV", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "properties": { - "metadata": "[parameters('metadata')]", - "srvRecords": "[parameters('srvRecords')]", - "ttl": "[parameters('ttl')]" - } - }, - "SRV_roleAssignments": { - "copy": { - "name": "SRV_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SRV/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), 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')]" + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "3082168335446205769" + } + }, + "definitions": { + "bastionHostConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Bastion Host resource." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for establishing Bastion Host for remote connection." + } + }, + "_1.networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "description": "Custom type definition for network security group configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/_1.networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "description": "Custom type definition for subnet configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Azure Bastion Host resource." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region to deploy resources." + } + }, + "vnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Virtual Network where the Azure Bastion Host will be deployed." + } + }, + "vnetName": { + "type": "string", + "metadata": { + "description": "Name of the Virtual Network where the Azure Bastion Host will be deployed." + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Log Analytics Workspace for monitoring and diagnostics." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to the resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "resources": { + "nsg": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}', parameters('vnetName'), tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'), parameters('vnetName'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "securityRules": { + "value": "[tryGet(parameters('subnet'), 'networkSecurityGroup', 'securityRules')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.33.93.31351", + "templateHash": "2305747478751645177" + }, + "name": "Network Security Groups", + "description": "This module deploys a Network security Group (NSG)." + }, + "definitions": { + "securityRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security rule." + } + }, + "properties": { + "type": "object", + "properties": { + "access": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Whether network traffic is allowed or denied." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the security rule." + } + }, + "destinationAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." + } + }, + "destinationAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." + } + }, + "destinationApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as destination." + } + }, + "destinationPortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "destinationPortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination port ranges." + } + }, + "direction": { + "type": "string", + "allowedValues": [ + "Inbound", + "Outbound" + ], + "metadata": { + "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 4096, + "metadata": { + "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "Ah", + "Esp", + "Icmp", + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Network protocol this rule applies to." + } + }, + "sourceAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." + } + }, + "sourceAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP ranges." + } + }, + "sourceApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as source." + } + }, + "sourcePortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "sourcePortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The source port ranges." + } + } + }, + "metadata": { + "description": "Required. The properties of the security rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a security rule." + } + }, + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Network Security Group." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "securityRules": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." + } + }, + "flushConnection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" + } + } + } + } + }, + "networkSecurityGroup": { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "securityRules", + "count": "[length(coalesce(parameters('securityRules'), createArray()))]", + "input": { + "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", + "properties": { + "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", + "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", + "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", + "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", + "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", + "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", + "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", + "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", + "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", + "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", + "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", + "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", + "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", + "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", + "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" + } + } + } + ], + "flushConnection": "[parameters('flushConnection')]" + } + }, + "networkSecurityGroup_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/networkSecurityGroups/{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": [ + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ + "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]" + } + } + } + } + }, + "bastionSubnet": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('bastionSubnet-{0}', parameters('vnetName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('vnetName')]" + }, + "name": { + "value": "AzureBastionSubnet" + }, + "addressPrefixes": { + "value": "[tryGet(parameters('subnet'), 'addressPrefixes')]" + }, + "networkSecurityGroupResourceId": { + "value": "[reference('nsg').outputs.resourceId.value]" + }, + "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.35.1.17967", + "templateHash": "9728353654559466189" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. 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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", + "nullable": true, + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "nullable": true, + "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 the 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 the 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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-05-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')]", + "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", + "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": "[parameters('privateEndpointNetworkPolicies')]", + "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + } + }, + "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())]" + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "metadata": { + "description": "The IPAM pool prefix allocations for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]" + } + } + } + }, + "dependsOn": [ + "nsg" + ] + }, + "bastionHost": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('bastionHost-{0}-{1}', parameters('vnetName'), parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "skuName": { + "value": "Standard" + }, + "location": { + "value": "[parameters('location')]" + }, + "virtualNetworkResourceId": { + "value": "[parameters('vnetId')]" + }, + "diagnosticSettings": { + "value": [ + { + "name": "bastionDiagnostics", + "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ] + } + ] + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "publicIPAddressObject": { + "value": { + "name": "[format('pip-{0}', parameters('name'))]", + "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.33.93.31351", + "templateHash": "2586599138991803385" + }, + "name": "Bastion Hosts", + "description": "This module deploys a Bastion Host." + }, + "definitions": { + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "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. This parameter is ignored when enablePrivateOnlyBastion is true." + } + }, + "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. This parameter is ignored when enablePrivateOnlyBastion is true." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Developer", + "Premium", + "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. For Basic and Developer SKU Copy/Paste is always enabled." + } + }, + "enableFileCopy": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Choose to disable or enable File Copy. Not supported for Basic and Developer SKU." + } + }, + "enableIpConnect": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable IP Connect. Not supported for Basic and Developer SKU." + } + }, + "enableKerberos": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Kerberos authentication. Not supported for Developer SKU." + } + }, + "enableShareableLink": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Shareable Link. Not supported for Basic and Developer SKU." + } + }, + "enableSessionRecording": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Session Recording feature. The Premium SKU is required for this feature. If Session Recording is enabled, the Native client support will be disabled." + } + }, + "enablePrivateOnlyBastion": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Choose to disable or enable Private-only Bastion deployment. The Premium SKU is required for this feature." + } + }, + "scaleUnits": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Optional. The scale units for the Bastion Host resource. The Basic and Developer SKU only support 2 scale units." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + }, + "zones": { + "type": "array", + "items": { + "type": "int" + }, + "defaultValue": [], + "allowedValues": [ + 1, + 2, + 3 + ], + "metadata": { + "description": "Optional. A list of availability zones denoting where the Bastion Host resource needs to come from. This is not supported for the Developer SKU." + } + } + }, + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "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.6.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" + } + } + } + } + }, + "azureBastion": { + "type": "Microsoft.Network/bastionHosts", + "apiVersion": "2024-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[coalesce(parameters('tags'), createObject())]", + "sku": { + "name": "[parameters('skuName')]" + }, + "zones": "[if(equals(parameters('skuName'), 'Developer'), createArray(), map(parameters('zones'), lambda('zone', string(lambdaVariables('zone')))))]", + "properties": "[union(createObject('scaleUnits', if(or(equals(parameters('skuName'), 'Basic'), equals(parameters('skuName'), 'Developer')), 2, parameters('scaleUnits')), 'ipConfigurations', if(equals(parameters('skuName'), 'Developer'), createArray(), createArray(createObject('name', 'IpConfAzureBastionSubnet', 'properties', union(createObject('subnet', createObject('id', format('{0}/subnets/AzureBastionSubnet', parameters('virtualNetworkResourceId')))), if(not(parameters('enablePrivateOnlyBastion')), createObject('publicIPAddress', createObject('id', if(not(empty(parameters('bastionSubnetPublicIpResourceId'))), parameters('bastionSubnetPublicIpResourceId'), reference('publicIPAddress').outputs.resourceId.value))), createObject())))))), if(equals(parameters('skuName'), 'Developer'), createObject('virtualNetwork', createObject('id', parameters('virtualNetworkResourceId'))), createObject()), if(or(or(equals(parameters('skuName'), 'Basic'), equals(parameters('skuName'), 'Standard')), equals(parameters('skuName'), 'Premium')), createObject('enableKerberos', parameters('enableKerberos')), createObject()), if(or(equals(parameters('skuName'), 'Standard'), equals(parameters('skuName'), 'Premium')), createObject('enableTunneling', if(equals(parameters('skuName'), 'Standard'), true(), if(parameters('enableSessionRecording'), false(), true())), 'disableCopyPaste', parameters('disableCopyPaste'), 'enableFileCopy', parameters('enableFileCopy'), 'enableIpConnect', parameters('enableIpConnect'), 'enableShareableLink', parameters('enableShareableLink')), createObject()), if(equals(parameters('skuName'), 'Premium'), createObject('enableSessionRecording', parameters('enableSessionRecording'), 'enablePrivateOnlyBastion', parameters('enablePrivateOnlyBastion')), 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": "[and(and(empty(parameters('bastionSubnetPublicIpResourceId')), not(equals(parameters('skuName'), 'Developer'))), not(parameters('enablePrivateOnlyBastion')))]", + "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": "[variables('enableReferencedModulesTelemetry')]" + }, + "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": "[coalesce(tryGet(parameters('publicIPAddressObject'), 'zones'), if(greater(length(parameters('zones')), 0), parameters('zones'), null()))]" + } + }, + "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.33.93.31351", + "templateHash": "5168739580767459761" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address." + }, + "definitions": { + "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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipTagType": { + "type": "object", + "properties": { + "ipTagType": { + "type": "string", + "metadata": { + "description": "Required. The IP tag type." + } + }, + "tag": { + "type": "string", + "metadata": { + "description": "Required. The IP tag." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "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." + } + }, + "ipTags": { + "type": "array", + "items": { + "$ref": "#/definitions/ipTagType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of tags associated with the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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": "[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.8.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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2024-05-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": "[parameters('ipTags')]" + } + }, + "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', '2024-05-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', '2024-05-01', 'full').location]" + }, + "ipConfAzureBastionSubnet": { + "type": "object", + "metadata": { + "description": "The Public IPconfiguration object for the AzureBastionSubnet." + }, + "value": "[if(equals(parameters('skuName'), 'Developer'), createObject(), reference('azureBastion').ipConfigurations[0])]" + } + } + } + }, + "dependsOn": [ + "bastionSubnet" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "value": "[reference('bastionHost').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "value": "[reference('bastionHost').outputs.name.value]" + }, + "subnetId": { + "type": "string", + "value": "[reference('bastionSubnet').outputs.resourceId.value]" + }, + "subnetName": { + "type": "string", + "value": "[reference('bastionSubnet').outputs.name.value]" + } + } + } }, "dependsOn": [ - "SRV" + "virtualNetwork" ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed SRV record." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed SRV record." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed SRV record." - }, - "value": "[resourceGroup().name]" - } - } - } - }, - "dependsOn": [ - "privateDnsZone" - ] - }, - "privateDnsZone_TXT": { - "copy": { - "name": "privateDnsZone_TXT", - "count": "[length(coalesce(parameters('txt'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-TXTRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(parameters('txt'), createArray())[copyIndex()].name]" - }, - "metadata": { - "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'metadata')]" - }, - "txtRecords": { - "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'txtRecords')]" - }, - "ttl": { - "value": "[coalesce(tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'ttl'), 3600)]" - }, - "roleAssignments": { - "value": "[tryGet(coalesce(parameters('txt'), 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.34.44.8038", - "templateHash": "170623042781622569" }, - "name": "Private DNS Zone TXT record", - "description": "This module deploys a Private DNS Zone TXT record." - }, - "definitions": { - "roleAssignmentType": { - "type": "object", + "jumpbox": { + "condition": "[not(empty(parameters('jumpboxConfiguration')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-jumpbox', parameters('resourcesName'))]", "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\"." - } + "expressionEvaluationOptions": { + "scope": "inner" }, - "conditionVersion": { - "type": "string", - "allowedValues": [ - "2.0" - ], - "nullable": true, - "metadata": { - "description": "Optional. Version of the condition." + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'name'), format('vm-jumpbox-{0}', parameters('resourcesName')))]" + }, + "vnetName": { + "value": "[reference('virtualNetwork').outputs.name.value]" + }, + "size": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'size'), 'Standard_D2s_v3')]" + }, + "logAnalyticsWorkspaceId": { + "value": "[parameters('logAnalyticsWorkSpaceResourceId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "subnet": { + "value": "[tryGet(parameters('jumpboxConfiguration'), 'subnet')]" + }, + "username": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'username'), '')]" + }, + "password": { + "value": "[coalesce(tryGet(parameters('jumpboxConfiguration'), 'password'), '')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "tags": { + "value": "[parameters('tags')]" } }, - "delegatedManagedIdentityResourceId": { - "type": "string", - "nullable": true, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "40464970328764907" + } + }, + "definitions": { + "jumpBoxConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Virtual Machine." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The size of the VM." + } + }, + "username": { + "type": "string", + "metadata": { + "description": "Username to access VM." + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "Password to access VM." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Custom type definition for establishing Jumpbox Virtual Machine and its associated resources." + } + }, + "_1.networkSecurityGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network security group." + } + }, + "securityRules": { + "type": "array", + "items": { + "type": "object" + }, + "metadata": { + "description": "Required. The security rules for the network security group." + } + } + }, + "metadata": { + "description": "Custom type definition for network security group configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + }, + "subnetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The Name of the subnet resource." + } + }, + "addressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. Prefixes for the subnet." + } + }, + "delegation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The delegation to enable on the subnet." + } + }, + "privateEndpointNetworkPolicies": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "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." + } + }, + "networkSecurityGroup": { + "$ref": "#/definitions/_1.networkSecurityGroupType", + "nullable": true, + "metadata": { + "description": "Optional. Network Security Group configuration for the subnet." + } + }, + "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." + } + } + }, + "metadata": { + "description": "Custom type definition for subnet configuration", + "__bicep_imported_from!": { + "sourceTemplate": "virtualNetwork.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Jumpbox Virtual Machine." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure region to deploy resources." + } + }, + "vnetName": { + "type": "string", + "metadata": { + "description": "Name of the Virtual Network where the Jumpbox VM will be deployed." + } + }, + "size": { + "type": "string", + "metadata": { + "description": "Size of the Jumpbox Virtual Machine." + } + }, + "subnet": { + "$ref": "#/definitions/subnetType", + "nullable": true, + "metadata": { + "description": "Optional. Subnet configuration for the Jumpbox VM." + } + }, + "username": { + "type": "string", + "metadata": { + "description": "Username to access the Jumpbox VM." + } + }, + "password": { + "type": "securestring", + "metadata": { + "description": "Password to access the Jumpbox VM." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to the resources." + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Log Analytics Workspace Resource ID for VM diagnostics." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "vmName": "[take(parameters('name'), 15)]" + }, + "resources": { + "nsg": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-{1}', parameters('vnetName'), tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('{0}-{1}', tryGet(parameters('subnet'), 'networkSecurityGroup', 'name'), parameters('vnetName'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "securityRules": { + "value": "[tryGet(parameters('subnet'), 'networkSecurityGroup', 'securityRules')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "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.33.93.31351", + "templateHash": "2305747478751645177" + }, + "name": "Network Security Groups", + "description": "This module deploys a Network security Group (NSG)." + }, + "definitions": { + "securityRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the security rule." + } + }, + "properties": { + "type": "object", + "properties": { + "access": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Required. Whether network traffic is allowed or denied." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the security rule." + } + }, + "destinationAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Optional. The destination address prefix. CIDR or destination IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used." + } + }, + "destinationAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination address prefixes. CIDR or destination IP ranges." + } + }, + "destinationApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as destination." + } + }, + "destinationPortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The destination port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "destinationPortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination port ranges." + } + }, + "direction": { + "type": "string", + "allowedValues": [ + "Inbound", + "Outbound" + ], + "metadata": { + "description": "Required. The direction of the rule. The direction specifies if rule will be evaluated on incoming or outgoing traffic." + } + }, + "priority": { + "type": "int", + "minValue": 100, + "maxValue": 4096, + "metadata": { + "description": "Required. Required. The priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "*", + "Ah", + "Esp", + "Icmp", + "Tcp", + "Udp" + ], + "metadata": { + "description": "Required. Network protocol this rule applies to." + } + }, + "sourceAddressPrefix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP range. Asterisk \"*\" can also be used to match all source IPs. Default tags such as \"VirtualNetwork\", \"AzureLoadBalancer\" and \"Internet\" can also be used. If this is an ingress rule, specifies where network traffic originates from." + } + }, + "sourceAddressPrefixes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The CIDR or source IP ranges." + } + }, + "sourceApplicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource IDs of the application security groups specified as source." + } + }, + "sourcePortRange": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The source port or range. Integer or range between 0 and 65535. Asterisk \"*\" can also be used to match all ports." + } + }, + "sourcePortRanges": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The source port ranges." + } + } + }, + "metadata": { + "description": "Required. The properties of the security rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a security rule." + } + }, + "diagnosticSettingLogsOnlyType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only logs are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Network Security Group." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "securityRules": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Security Rules to deploy to the Network Security Group. When not provided, an NSG including only the built-in roles will be deployed." + } + }, + "flushConnection": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. When enabled, flows created from Network Security Group connections will be re-evaluated when rules are updates. Initial enablement will trigger re-evaluation. Network Security Group connection flushing is not available in all regions." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingLogsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the NSG 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": "[format('46d3xbcp.res.network-networksecuritygroup.{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" + } + } + } + } + }, + "networkSecurityGroup": { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "securityRules", + "count": "[length(coalesce(parameters('securityRules'), createArray()))]", + "input": { + "name": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].name]", + "properties": { + "access": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.access]", + "description": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'description'), '')]", + "destinationAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefix'), '')]", + "destinationAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationAddressPrefixes'), createArray())]", + "destinationApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationApplicationSecurityGroupResourceIds'), createArray()), lambda('destinationApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('destinationApplicationSecurityGroupResourceId'))))]", + "destinationPortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRange'), '')]", + "destinationPortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'destinationPortRanges'), createArray())]", + "direction": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.direction]", + "priority": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.priority]", + "protocol": "[coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties.protocol]", + "sourceAddressPrefix": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefix'), '')]", + "sourceAddressPrefixes": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceAddressPrefixes'), createArray())]", + "sourceApplicationSecurityGroups": "[map(coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourceApplicationSecurityGroupResourceIds'), createArray()), lambda('sourceApplicationSecurityGroupResourceId', createObject('id', lambdaVariables('sourceApplicationSecurityGroupResourceId'))))]", + "sourcePortRange": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRange'), '')]", + "sourcePortRanges": "[coalesce(tryGet(coalesce(parameters('securityRules'), createArray())[copyIndex('securityRules')].properties, 'sourcePortRanges'), createArray())]" + } + } + } + ], + "flushConnection": "[parameters('flushConnection')]" + } + }, + "networkSecurityGroup_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/networkSecurityGroups/{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": [ + "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(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkSecurityGroups/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkSecurityGroups', 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": [ + "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]" + } + } + } + } + }, + "subnetResource": { + "condition": "[not(empty(parameters('subnet')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[coalesce(tryGet(parameters('subnet'), 'name'), format('{0}-jumpbox-subnet', parameters('vnetName')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualNetworkName": { + "value": "[parameters('vnetName')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('subnet'), 'name'), '')]" + }, + "addressPrefixes": { + "value": "[tryGet(parameters('subnet'), 'addressPrefixes')]" + }, + "networkSecurityGroupResourceId": { + "value": "[reference('nsg').outputs.resourceId.value]" + }, + "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.35.1.17967", + "templateHash": "9728353654559466189" + }, + "name": "Virtual Network Subnets", + "description": "This module deploys a Virtual Network Subnet." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. 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." + } + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "description": "Conditional. The address space for the subnet, deployed from IPAM Pool. Required if `addressPrefixes` and `addressPrefix` 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", + "nullable": true, + "allowedValues": [ + "Disabled", + "Enabled", + "NetworkSecurityGroupEnabled", + "RouteTableEnabled" + ], + "metadata": { + "description": "Optional. Enable or disable apply network policies on private endpoint in the subnet." + } + }, + "privateLinkServiceNetworkPolicies": { + "type": "string", + "nullable": true, + "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 the 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 the 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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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)))))]" + } + ], + "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-virtualnetworksubnet.{0}.{1}', replace('0.1.2', '.', '-'), substring(uniqueString(deployment().name), 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": { + "existing": true, + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2024-01-01", + "name": "[parameters('virtualNetworkName')]" + }, + "subnet": { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2024-05-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')]", + "ipamPoolPrefixAllocations": "[parameters('ipamPoolPrefixAllocations')]", + "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": "[parameters('privateEndpointNetworkPolicies')]", + "privateLinkServiceNetworkPolicies": "[parameters('privateLinkServiceNetworkPolicies')]", + "applicationGatewayIPConfigurations": "[parameters('applicationGatewayIPConfigurations')]", + "serviceEndpointPolicies": "[parameters('serviceEndpointPolicies')]", + "defaultOutboundAccess": "[parameters('defaultOutboundAccess')]", + "sharingScope": "[parameters('sharingScope')]" + } + }, + "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())]" + }, + "ipamPoolPrefixAllocations": { + "type": "array", + "metadata": { + "description": "The IPAM pool prefix allocations for the subnet." + }, + "value": "[coalesce(tryGet(reference('subnet'), 'ipamPoolPrefixAllocations'), createArray())]" + } + } + } + }, + "dependsOn": [ + "nsg" + ] + }, + "vm": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[take(format('{0}-jumpbox', variables('vmName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('vmName')]" + }, + "vmSize": { + "value": "[parameters('size')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "adminUsername": { + "value": "[parameters('username')]" + }, + "adminPassword": { + "value": "[parameters('password')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "zone": { + "value": 0 + }, + "imageReference": { + "value": { + "offer": "WindowsServer", + "publisher": "MicrosoftWindowsServer", + "sku": "2019-datacenter", + "version": "latest" + } + }, + "osType": { + "value": "Windows" + }, + "osDisk": { + "value": { + "name": "[format('osdisk-{0}', variables('vmName'))]", + "managedDisk": { + "storageAccountType": "Standard_LRS" + } + } + }, + "encryptionAtHost": { + "value": false + }, + "nicConfigurations": { + "value": [ + { + "name": "[format('nic-{0}', variables('vmName'))]", + "ipConfigurations": [ + { + "name": "ipconfig1", + "subnetResourceId": "[reference('subnetResource').outputs.resourceId.value]" + } + ], + "networkSecurityGroupResourceId": "[reference('nsg').outputs.resourceId.value]", + "diagnosticSettings": [ + { + "name": "jumpboxDiagnostics", + "workspaceResourceId": "[parameters('logAnalyticsWorkspaceId')]", + "logCategoriesAndGroups": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metricCategories": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + } + ] + } + ] + }, + "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.34.44.8038", + "templateHash": "1057634180502804806" + }, + "name": "Virtual Machines", + "description": "This module deploys a Virtual Machine with one or multiple NICs and optionally one or multiple public IPs." + }, + "definitions": { + "osDiskType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The disk name." + } + }, + "diskSizeGB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the size of an empty data disk in gigabytes." + } + }, + "createOption": { + "type": "string", + "allowedValues": [ + "Attach", + "Empty", + "FromImage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies how the virtual machine should be created." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion." + } + }, + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the caching requirements." + } + }, + "diffDiskSettings": { + "type": "object", + "properties": { + "placement": { + "type": "string", + "allowedValues": [ + "CacheDisk", + "NvmeDisk", + "ResourceDisk" + ], + "metadata": { + "description": "Required. Specifies the ephemeral disk placement for the operating system disk." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the ephemeral Disk Settings for the operating system disk." + } + }, + "managedDisk": { + "type": "object", + "properties": { + "storageAccountType": { + "type": "string", + "allowedValues": [ + "PremiumV2_LRS", + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS", + "UltraSSD_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the storage account type for the managed disk." + } + }, + "diskEncryptionSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." + } + } + }, + "metadata": { + "description": "Required. The managed disk parameters." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing an OS disk." + } + }, + "dataDiskType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The disk name. When attaching a pre-existing disk, this name is ignored and the name of the existing disk is used." + } + }, + "lun": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the logical unit number of the data disk." + } + }, + "diskSizeGB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the size of an empty data disk in gigabytes. This property is ignored when attaching a pre-existing disk." + } + }, + "createOption": { + "type": "string", + "allowedValues": [ + "Attach", + "Empty", + "FromImage" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies how the virtual machine should be created. This property is automatically set to 'Attach' when attaching a pre-existing disk." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data disk should be deleted or detached upon VM deletion. This property is automatically set to 'Detach' when attaching a pre-existing disk." + } + }, + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the caching requirements. This property is automatically set to 'None' when attaching a pre-existing disk." + } + }, + "diskIOPSReadWrite": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The number of IOPS allowed for this disk; only settable for UltraSSD disks. One operation can transfer between 4k and 256k bytes. Ignored when attaching a pre-existing disk." + } + }, + "diskMBpsReadWrite": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The bandwidth allowed for this disk; only settable for UltraSSD disks. MBps means millions of bytes per second - MB here uses the ISO notation, of powers of 10. Ignored when attaching a pre-existing disk." + } + }, + "managedDisk": { + "type": "object", + "properties": { + "storageAccountType": { + "type": "string", + "allowedValues": [ + "PremiumV2_LRS", + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS", + "UltraSSD_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the storage account type for the managed disk. Ignored when attaching a pre-existing disk." + } + }, + "diskEncryptionSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the customer managed disk encryption set resource id for the managed disk." + } + }, + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the resource id of a pre-existing managed disk. If the disk should be created, this property should be empty." + } + } + }, + "metadata": { + "description": "Required. The managed disk parameters." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address. Valid only when creating a new managed disk." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing a data disk." + } + }, + "publicKeyType": { + "type": "object", + "properties": { + "keyData": { + "type": "string", + "metadata": { + "description": "Required. Specifies the SSH public key data used to authenticate through ssh." + } + }, + "path": { + "type": "string", + "metadata": { + "description": "Required. Specifies the full path on the created VM where ssh public key is stored. If the file already exists, the specified key is appended to the file." + } + } + } + }, + "nicConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the NIC configuration." + } + }, + "nicSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The suffix to append to the NIC name." + } + }, + "enableIPForwarding": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether IP forwarding is enabled on this network interface." + } + }, + "enableAcceleratedNetworking": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If the network interface is accelerated networking enabled." + } + }, + "deleteOption": { + "type": "string", + "allowedValues": [ + "Delete", + "Detach" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify what happens to the network interface when the VM is deleted." + } + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of DNS servers IP addresses. Use 'AzureProvidedDNS' to switch to azure provided DNS resolution. 'AzureProvidedDNS' value cannot be combined with other IPs, it must be the only value in dnsServers collection." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The network security group (NSG) to attach to the network interface." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "metadata": { + "description": "Required. The IP configurations of the network interface." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the IP configuration." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the NIC configuration." + } + }, + "imageReferenceType": { + "type": "object", + "properties": { + "communityGalleryImageId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specified the community gallery image unique id for vm deployment. This can be fetched from community gallery image GET call." + } + }, + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource Id of the image reference." + } + }, + "offer": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the offer of the platform image or marketplace image used to create the virtual machine." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The image publisher." + } + }, + "sku": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The SKU of the image." + } + }, + "version": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the version of the platform image or marketplace image used to create the virtual machine. The allowed formats are Major.Minor.Build or 'latest'. Even if you use 'latest', the VM image will not automatically update after deploy time even if a new version becomes available." + } + }, + "sharedGalleryImageId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specified the shared gallery image unique id for vm deployment. This can be fetched from shared gallery image GET call." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the image reference." + } + }, + "planType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the plan." + } + }, + "product": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the product of the image from the marketplace." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher ID." + } + }, + "promotionCode": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The promotion code." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Specifies information about the marketplace image used to create the virtual machine." + } + }, + "autoShutDownConfigType": { + "type": "object", + "properties": { + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the auto shutdown configuration." + } + }, + "timeZone": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time zone ID (e.g. China Standard Time, Greenland Standard Time, Pacific Standard time, etc.)." + } + }, + "dailyRecurrenceTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The time of day the schedule will occur." + } + }, + "notificationSettings": { + "type": "object", + "properties": { + "status": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the notification settings." + } + }, + "emailRecipient": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The email address to send notifications to (can be a list of semi-colon separated email addresses)." + } + }, + "notificationLocale": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The locale to use when sending a notification (fallback for unsupported languages is EN)." + } + }, + "webhookUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The webhook URL to which the notification will be sent." + } + }, + "timeInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The time in minutes before shutdown to send notifications." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the schedule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the configuration profile." + } + }, + "vaultSecretGroupType": { + "type": "object", + "properties": { + "sourceVault": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The relative URL of the Key Vault containing all of the certificates in VaultCertificates." + } + }, + "vaultCertificates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "certificateStore": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. For Windows VMs, specifies the certificate store on the Virtual Machine to which the certificate should be added. The specified certificate store is implicitly in the LocalMachine account. For Linux VMs, the certificate file is placed under the /var/lib/waagent directory, with the file name .crt for the X509 certificate file and .prv for private key. Both of these files are .pem formatted." + } + }, + "certificateUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. This is the URL of a certificate that has been uploaded to Key Vault as a secret." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of key vault references in SourceVault which contain certificates." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the set of certificates that should be installed onto the virtual machine." + } + }, + "vmGalleryApplicationType": { + "type": "object", + "properties": { + "packageReferenceId": { + "type": "string", + "metadata": { + "description": "Required. Specifies the GalleryApplicationVersion resource id on the form of /subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Compute/galleries/{galleryName}/applications/{application}/versions/{version}." + } + }, + "configurationReference": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the uri to an azure blob that will replace the default configuration for the package if provided." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If set to true, when a new Gallery Application version is available in PIR/SIG, it will be automatically updated for the VM/VMSS." + } + }, + "order": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the order in which the packages have to be installed." + } + }, + "tags": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies a passthrough value for more generic context." + } + }, + "treatFailureAsDeploymentFailure": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If true, any failure for any operation in the VmApplication will fail the deployment." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the gallery application that should be made available to the VM/VMSS." + } + }, + "additionalUnattendContentType": { + "type": "object", + "properties": { + "settingName": { + "type": "string", + "allowedValues": [ + "AutoLogon", + "FirstLogonCommands" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the name of the setting to which the content applies." + } + }, + "content": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the XML formatted content that is added to the unattend.xml file for the specified path and component. The XML must be less than 4KB and must include the root element for the setting or feature that is being inserted." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing additional base-64 encoded XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup." + } + }, + "winRMListenerType": { + "type": "object", + "properties": { + "certificateUrl": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The URL of a certificate that has been uploaded to Key Vault as a secret." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "Http", + "Https" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the protocol of WinRM listener." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing a Windows Remote Management listener." + } + }, + "nicConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the NIC configuration." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" + }, + "metadata": { + "description": "Required. List of IP configurations of the NIC configuration." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type describing the network interface configuration output." + } + }, + "_1.applicationGatewayBackendAddressPoolsType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddresses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. IP address of the backend address." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN of the backend address." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Backend addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application gateway backend address pool." + } + } + }, + "metadata": { + "description": "The type for the application gateway backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.applicationSecurityGroupType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application security group." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the application security group." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application security group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the application security group." + } + } + }, + "metadata": { + "description": "The type for the application security group.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.backendAddressPoolType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the backend address pool." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The properties of the backend address pool." + } + } + }, + "metadata": { + "description": "The type for a backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.inboundNatRuleType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the inbound NAT rule." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddressPool": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to backendAddressPool resource." + } + }, + "backendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." + } + }, + "enableFloatingIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." + } + }, + "enableTcpReset": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." + } + }, + "frontendIPConfiguration": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to frontend IP addresses." + } + }, + "frontendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeStart": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeEnd": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "All", + "Tcp", + "Udp" + ], + "nullable": true, + "metadata": { + "description": "Optional. The reference to the transport protocol used by the load balancing rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the inbound NAT rule." + } + } + }, + "metadata": { + "description": "The type for the inbound NAT rule.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_1.virtualNetworkTapType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the virtual network tap." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the virtual network tap." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the virtual network tap." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the virtual network tap." + } + } + }, + "metadata": { + "description": "The type for the virtual network tap.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "_2.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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "_2.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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "_3.publicIPConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Public IP Address." + } + }, + "publicIPAddressResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the public IP address." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Diagnostic settings for the public IP address." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout in minutes." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the public IP address." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "ddosSettings": { + "$ref": "#/definitions/_2.ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "dnsSettings": { + "$ref": "#/definitions/_2.dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "publicIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address version." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "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." + } + }, + "publicIpNameSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name suffix of the public IP address resource." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "skuName": { + "type": "string", + "allowedValues": [ + "Basic", + "Standard" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU name of the public IP address." + } + }, + "skuTier": { + "type": "string", + "allowedValues": [ + "Global", + "Regional" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU tier of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "zones": { + "type": "array", + "allowedValues": [ + 1, + 2, + 3 + ], + "nullable": true, + "metadata": { + "description": "Optional. The zones of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "description": "The type for the public IP address configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "modules/nic-configuration.bicep" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "ipConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the IP configuration." + } + }, + "privateIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address allocation method." + } + }, + "privateIPAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The private IP address." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "loadBalancerBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.backendAddressPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer backend address pools." + } + }, + "applicationSecurityGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.applicationSecurityGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application security groups." + } + }, + "applicationGatewayBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.applicationGatewayBackendAddressPoolsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application gateway backend address pools." + } + }, + "gatewayLoadBalancer": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The gateway load balancer settings." + } + }, + "loadBalancerInboundNatRules": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.inboundNatRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer inbound NAT rules." + } + }, + "privateIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address version." + } + }, + "virtualNetworkTaps": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.virtualNetworkTapType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The virtual network taps." + } + }, + "pipConfiguration": { + "$ref": "#/definitions/_3.publicIPConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. The public IP address configuration." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the IP configuration." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "description": "The type for the IP configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "modules/nic-configuration.bicep" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "networkInterfaceIPConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the IP configuration." + } + }, + "privateIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The private IP address." + } + }, + "publicIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The public IP address." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "subResourceType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the sub resource." + } + } + }, + "metadata": { + "description": "The type for the sub resource.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine to be created. You should use a unique prefix to reduce name collisions in Active Directory." + } + }, + "computerName": { + "type": "string", + "defaultValue": "[parameters('name')]", + "metadata": { + "description": "Optional. Can be used if the computer name needs to be different from the Azure VM resource name. If not used, the resource name will be used as computer name." + } + }, + "vmSize": { + "type": "string", + "metadata": { + "description": "Required. Specifies the size for the VMs." + } + }, + "encryptionAtHost": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine. This will enable the encryption for all the disks including Resource/Temp disk at host itself. For security reasons, it is recommended to set encryptionAtHost to True. Restrictions: Cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." + } + }, + "securityType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "", + "ConfidentialVM", + "TrustedLaunch" + ], + "metadata": { + "description": "Optional. Specifies the SecurityType of the virtual machine. It has to be set to any specified value to enable UefiSettings. The default behavior is: UefiSettings will not be enabled unless this property is set." + } + }, + "secureBootEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether secure boot should be enabled on the virtual machine. This parameter is part of the UefiSettings. SecurityType should be set to TrustedLaunch to enable UefiSettings." + } + }, + "vTpmEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether vTPM should be enabled on the virtual machine. This parameter is part of the UefiSettings. SecurityType should be set to TrustedLaunch to enable UefiSettings." + } + }, + "imageReference": { + "$ref": "#/definitions/imageReferenceType", + "metadata": { + "description": "Required. OS image reference. In case of marketplace images, it's the combination of the publisher, offer, sku, version attributes. In case of custom images it's the resource ID of the custom image." + } + }, + "plan": { + "$ref": "#/definitions/planType", + "nullable": true, + "metadata": { + "description": "Optional. Specifies information about the marketplace image used to create the virtual machine. This element is only used for marketplace images. Before you can use a marketplace image from an API, you must enable the image for programmatic use." + } + }, + "osDisk": { + "$ref": "#/definitions/osDiskType", + "metadata": { + "description": "Required. Specifies the OS disk. For security reasons, it is recommended to specify DiskEncryptionSet into the osDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." + } + }, + "dataDisks": { + "type": "array", + "items": { + "$ref": "#/definitions/dataDiskType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the data disks. For security reasons, it is recommended to specify DiskEncryptionSet into the dataDisk object. Restrictions: DiskEncryptionSet cannot be enabled if Azure Disk Encryption (guest-VM encryption using bitlocker/DM-Crypt) is enabled on your VMs." + } + }, + "ultraSSDEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The flag that enables or disables a capability to have one or more managed data disks with UltraSSD_LRS storage account type on the VM or VMSS. Managed disks with storage account type UltraSSD_LRS can be added to a virtual machine or virtual machine scale set only if this property is enabled." + } + }, + "hibernationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The flag that enables or disables hibernation capability on the VM." + } + }, + "adminUsername": { + "type": "securestring", + "metadata": { + "description": "Required. Administrator username." + } + }, + "adminPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. When specifying a Windows Virtual Machine, this value should be passed." + } + }, + "userData": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. UserData for the VM, which must be base-64 encoded. Customer should not pass any secrets in here." + } + }, + "customData": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom data associated to the VM, this value will be automatically converted into base64 to account for the expected VM format." + } + }, + "certificatesToBeInstalled": { + "type": "array", + "items": { + "$ref": "#/definitions/vaultSecretGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies set of certificates that should be installed onto the virtual machine." + } + }, + "priority": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Regular", + "Low", + "Spot" + ], + "metadata": { + "description": "Optional. Specifies the priority for the virtual machine." + } + }, + "evictionPolicy": { + "type": "string", + "defaultValue": "Deallocate", + "allowedValues": [ + "Deallocate", + "Delete" + ], + "metadata": { + "description": "Optional. Specifies the eviction policy for the low priority virtual machine." + } + }, + "maxPriceForLowPriorityVm": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the maximum price you are willing to pay for a low priority VM/VMSS. This price is in US Dollars." + } + }, + "dedicatedHostId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies resource ID about the dedicated host that the virtual machine resides in." + } + }, + "licenseType": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "RHEL_BYOS", + "SLES_BYOS", + "Windows_Client", + "Windows_Server", + "" + ], + "metadata": { + "description": "Optional. Specifies that the image or disk that is being used was licensed on-premises." + } + }, + "publicKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/publicKeyType" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. The list of SSH public keys used to authenticate with linux based VMs." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource. The system-assigned managed identity will automatically be enabled if extensionAadJoinConfig.enabled = \"True\"." + } + }, + "bootDiagnostics": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether boot diagnostics should be enabled on the Virtual Machine. Boot diagnostics will be enabled with a managed storage account if no bootDiagnosticsStorageAccountName value is provided. If bootDiagnostics and bootDiagnosticsStorageAccountName values are not provided, boot diagnostics will be disabled." + } + }, + "bootDiagnosticStorageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom storage account used to store boot diagnostic information. Boot diagnostics will be enabled with a custom storage account if a value is provided." + } + }, + "bootDiagnosticStorageAccountUri": { + "type": "string", + "defaultValue": "[format('.blob.{0}/', environment().suffixes.storage)]", + "metadata": { + "description": "Optional. Storage account boot diagnostic base URI." + } + }, + "proximityPlacementGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of a proximity placement group." + } + }, + "virtualMachineScaleSetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of a virtual machine scale set, where the VM should be added." + } + }, + "availabilitySetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of an availability set. Cannot be used in combination with availability zone nor scale set." + } + }, + "galleryApplications": { + "type": "array", + "items": { + "$ref": "#/definitions/vmGalleryApplicationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the gallery applications that should be made available to the VM/VMSS." + } + }, + "zone": { + "type": "int", + "allowedValues": [ + 0, + 1, + 2, + 3 + ], + "metadata": { + "description": "Required. If set to 1, 2 or 3, the availability zone for all VMs is hardcoded to that value. If zero, then availability zones is not used. Cannot be used in combination with availability set nor scale set." + } + }, + "nicConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/nicConfigurationType" + }, + "metadata": { + "description": "Required. Configures NICs and PIPs." + } + }, + "backupVaultName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Recovery service vault name to add VMs to backup." + } + }, + "backupVaultResourceGroup": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Optional. Resource group of the backup recovery service vault. If not provided the current resource group name is considered by default." + } + }, + "backupPolicyName": { + "type": "string", + "defaultValue": "DefaultPolicy", + "metadata": { + "description": "Optional. Backup policy the VMs should be using for backup. If not provided, it will use the DefaultPolicy from the backup recovery service vault." + } + }, + "autoShutdownConfig": { + "$ref": "#/definitions/autoShutDownConfigType", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for auto-shutdown." + } + }, + "maintenanceConfigurationResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The resource Id of a maintenance configuration for this VM." + } + }, + "allowExtensionOperations": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies whether extension operations should be allowed on the virtual machine. This may only be set to False when no extensions are present on the virtual machine." + } + }, + "extensionDomainJoinPassword": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Required if name is specified. Password of the user specified in user parameter." + } + }, + "extensionDomainJoinConfig": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. The configuration for the [Domain Join] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionAadJoinConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [AAD Join] extension. Must at least contain the [\"enabled\": true] property to be executed. To enroll in Intune, add the setting mdmId: \"0000000a-0000-0000-c000-000000000000\"." + } + }, + "extensionAntiMalwareConfig": { + "type": "object", + "defaultValue": "[if(equals(parameters('osType'), 'Windows'), createObject('enabled', true()), createObject('enabled', false()))]", + "metadata": { + "description": "Optional. The configuration for the [Anti Malware] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionMonitoringAgentConfig": { + "type": "object", + "defaultValue": { + "enabled": false, + "dataCollectionRuleAssociations": [] + }, + "metadata": { + "description": "Optional. The configuration for the [Monitoring Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionDependencyAgentConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Dependency Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionNetworkWatcherAgentConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Network Watcher Agent] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionAzureDiskEncryptionConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Azure Disk Encryption] extension. Must at least contain the [\"enabled\": true] property to be executed. Restrictions: Cannot be enabled on disks that have encryption at host enabled. Managed disks encrypted using Azure Disk Encryption cannot be encrypted using customer-managed keys." + } + }, + "extensionDSCConfig": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Desired State Configuration] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionCustomScriptConfig": { + "type": "object", + "defaultValue": { + "enabled": false, + "fileData": [] + }, + "metadata": { + "description": "Optional. The configuration for the [Custom Script] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionNvidiaGpuDriverWindows": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Nvidia Gpu Driver Windows] extension. Must at least contain the [\"enabled\": true] property to be executed." + } + }, + "extensionHostPoolRegistration": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Host Pool Registration] extension. Must at least contain the [\"enabled\": true] property to be executed. Needs a managed identy." + } + }, + "extensionGuestConfigurationExtension": { + "type": "object", + "defaultValue": { + "enabled": false + }, + "metadata": { + "description": "Optional. The configuration for the [Guest Configuration] extension. Must at least contain the [\"enabled\": true] property to be executed. Needs a managed identy." + } + }, + "guestConfiguration": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The guest configuration for the virtual machine. Needs the Guest Configuration extension to be enabled." + } + }, + "extensionCustomScriptProtectedSetting": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. An object that contains the extension specific protected settings." + } + }, + "extensionGuestConfigurationExtensionProtectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. An object that contains the extension specific protected settings." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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." + } + }, + "baseTime": { + "type": "string", + "defaultValue": "[utcNow('u')]", + "metadata": { + "description": "Generated. Do not provide a value! This date value is used to generate a registration token." + } + }, + "sasTokenValidityLength": { + "type": "string", + "defaultValue": "PT8H", + "metadata": { + "description": "Optional. SAS token validity length to use to download files from storage accounts. Usage: 'PT8H' - valid for 8 hours; 'P5D' - valid for 5 days; 'P1Y' - valid for 1 year. When not provided, the SAS token will be valid for 8 hours." + } + }, + "osType": { + "type": "string", + "allowedValues": [ + "Windows", + "Linux" + ], + "metadata": { + "description": "Required. The chosen OS type." + } + }, + "disablePasswordAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Specifies whether password authentication should be disabled." + } + }, + "provisionVMAgent": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether virtual machine agent should be provisioned on the virtual machine. When this property is not specified in the request body, default behavior is to set it to true. This will ensure that VM Agent is installed on the VM so that extensions can be added to the VM later." + } + }, + "enableAutomaticUpdates": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether Automatic Updates is enabled for the Windows virtual machine. Default value is true. When patchMode is set to Manual, this parameter must be set to false. For virtual machine scale sets, this property can be updated and updates will take effect on OS reprovisioning." + } + }, + "patchMode": { + "type": "string", + "defaultValue": "", + "allowedValues": [ + "AutomaticByPlatform", + "AutomaticByOS", + "Manual", + "ImageDefault", + "" + ], + "metadata": { + "description": "Optional. VM guest patching orchestration mode. 'AutomaticByOS' & 'Manual' are for Windows only, 'ImageDefault' for Linux only. Refer to 'https://learn.microsoft.com/en-us/azure/virtual-machines/automatic-vm-guest-patching'." + } + }, + "bypassPlatformSafetyChecksOnUserSchedule": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enables customer to schedule patching without accidental upgrades." + } + }, + "rebootSetting": { + "type": "string", + "defaultValue": "IfRequired", + "allowedValues": [ + "Always", + "IfRequired", + "Never", + "Unknown" + ], + "metadata": { + "description": "Optional. Specifies the reboot setting for all AutomaticByPlatform patch installation operations." + } + }, + "patchAssessmentMode": { + "type": "string", + "defaultValue": "ImageDefault", + "allowedValues": [ + "AutomaticByPlatform", + "ImageDefault" + ], + "metadata": { + "description": "Optional. VM guest patching assessment mode. Set it to 'AutomaticByPlatform' to enable automatically check for updates every 24 hours." + } + }, + "enableHotpatching": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables customers to patch their Azure VMs without requiring a reboot. For enableHotpatching, the 'provisionVMAgent' must be set to true and 'patchMode' must be set to 'AutomaticByPlatform'." + } + }, + "timeZone": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Specifies the time zone of the virtual machine. e.g. 'Pacific Standard Time'. Possible values can be `TimeZoneInfo.id` value from time zones returned by `TimeZoneInfo.GetSystemTimeZones`." + } + }, + "additionalUnattendContent": { + "type": "array", + "items": { + "$ref": "#/definitions/additionalUnattendContentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies additional XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup. Contents are defined by setting name, component name, and the pass in which the content is applied." + } + }, + "winRMListeners": { + "type": "array", + "items": { + "$ref": "#/definitions/winRMListenerType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Specifies the Windows Remote Management listeners. This enables remote Windows PowerShell." + } + }, + "configurationProfile": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The configuration profile of automanage. Either '/providers/Microsoft.Automanage/bestPractices/AzureBestPracticesProduction', 'providers/Microsoft.Automanage/bestPractices/AzureBestPracticesDevTest' or the resource Id of custom profile." + } + } + }, + "variables": { + "copy": [ + { + "name": "publicKeysFormatted", + "count": "[length(parameters('publicKeys'))]", + "input": { + "path": "[parameters('publicKeys')[copyIndex('publicKeysFormatted')].path]", + "keyData": "[parameters('publicKeys')[copyIndex('publicKeysFormatted')].keyData]" + } + }, + { + "name": "additionalUnattendContentFormatted", + "count": "[length(coalesce(parameters('additionalUnattendContent'), createArray()))]", + "input": { + "settingName": "[coalesce(parameters('additionalUnattendContent'), createArray())[copyIndex('additionalUnattendContentFormatted')].settingName]", + "content": "[coalesce(parameters('additionalUnattendContent'), createArray())[copyIndex('additionalUnattendContentFormatted')].content]", + "componentName": "Microsoft-Windows-Shell-Setup", + "passName": "OobeSystem" + } + }, + { + "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)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "linuxConfiguration": { + "disablePasswordAuthentication": "[parameters('disablePasswordAuthentication')]", + "ssh": { + "publicKeys": "[variables('publicKeysFormatted')]" + }, + "provisionVMAgent": "[parameters('provisionVMAgent')]", + "patchSettings": "[if(and(parameters('provisionVMAgent'), or(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), equals(toLower(parameters('patchMode')), toLower('ImageDefault')))), createObject('patchMode', parameters('patchMode'), 'assessmentMode', parameters('patchAssessmentMode'), 'automaticByPlatformSettings', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), createObject('bypassPlatformSafetyChecksOnUserSchedule', parameters('bypassPlatformSafetyChecksOnUserSchedule'), 'rebootSetting', parameters('rebootSetting')), null())), null())]" + }, + "windowsConfiguration": { + "provisionVMAgent": "[parameters('provisionVMAgent')]", + "enableAutomaticUpdates": "[parameters('enableAutomaticUpdates')]", + "patchSettings": "[if(and(parameters('provisionVMAgent'), or(or(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), equals(toLower(parameters('patchMode')), toLower('AutomaticByOS'))), equals(toLower(parameters('patchMode')), toLower('Manual')))), createObject('patchMode', parameters('patchMode'), 'assessmentMode', parameters('patchAssessmentMode'), 'enableHotpatching', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), parameters('enableHotpatching'), false()), 'automaticByPlatformSettings', if(equals(toLower(parameters('patchMode')), toLower('AutomaticByPlatform')), createObject('bypassPlatformSafetyChecksOnUserSchedule', parameters('bypassPlatformSafetyChecksOnUserSchedule'), 'rebootSetting', parameters('rebootSetting')), null())), null())]", + "timeZone": "[if(empty(parameters('timeZone')), null(), parameters('timeZone'))]", + "additionalUnattendContent": "[if(empty(parameters('additionalUnattendContent')), null(), variables('additionalUnattendContentFormatted'))]", + "winRM": "[if(not(empty(parameters('winRMListeners'))), createObject('listeners', parameters('winRMListeners')), null())]" + }, + "accountSasProperties": { + "signedServices": "b", + "signedPermission": "r", + "signedExpiry": "[dateTimeAdd(parameters('baseTime'), parameters('sasTokenValidityLength'))]", + "signedResourceTypes": "o", + "signedProtocol": "https" + }, + "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(if(parameters('extensionAadJoinConfig').enabled, true(), 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', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Data Operator for Managed Disks": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '959f8984-c045-4866-89c7-12bf9737be2e')]", + "Desktop Virtualization Power On Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '489581de-a3bd-480d-9518-53dea7416b33')]", + "Desktop Virtualization Power On Off Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '40c5ff49-9181-41f8-ae61-143b0e78555e')]", + "Desktop Virtualization Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c')]", + "DevTest Labs User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76283e04-6283-4c54-8f91-bcf1374a3c64')]", + "Disk Backup Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3e5e47e6-65f7-47ef-90b5-e5dd4d455f24')]", + "Disk Pool Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '60fc6e62-5479-42d4-8bf4-67625fcc2840')]", + "Disk Restore Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b50d9833-a0cb-478e-945f-707fcc997c13')]", + "Disk Snapshot Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7efff54f-a5b4-42b5-a1c5-5411624893ce')]", + "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')]", + "Virtual Machine Administrator Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1c0163c0-47e6-4577-8991-ea5c82e286e4')]", + "Virtual Machine Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9980e02c-c2be-4d73-94e8-173b1dc7cf3c')]", + "Virtual Machine User Login": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb879df8-f326-4884-b1cf-06f3ad86be52')]", + "VM Scanner Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd24ecba3-c1f4-40fa-a7bb-4588a071e8fd')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.compute-virtualmachine.{0}.{1}', replace('0.15.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" + } + } + } + } + }, + "managedDataDisks": { + "copy": { + "name": "managedDataDisks", + "count": "[length(coalesce(parameters('dataDisks'), createArray()))]" + }, + "condition": "[empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'id'))]", + "type": "Microsoft.Compute/disks", + "apiVersion": "2024-03-02", + "name": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex(), 1), 2, '0')))]", + "location": "[parameters('location')]", + "sku": { + "name": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'storageAccountType')]" + }, + "properties": { + "diskSizeGB": "[coalesce(parameters('dataDisks'), createArray())[copyIndex()].diskSizeGB]", + "creationData": { + "createOption": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'createoption'), 'Empty')]" + }, + "diskIOPSReadWrite": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'diskIOPSReadWrite')]", + "diskMBpsReadWrite": "[tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'diskMBpsReadWrite')]" + }, + "zones": "[if(and(not(equals(parameters('zone'), 0)), not(contains(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()].managedDisk, 'storageAccountType'), 'ZRS'))), array(string(parameters('zone'))), null())]", + "tags": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "vm": { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2024-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "zones": "[if(not(equals(parameters('zone'), 0)), array(string(parameters('zone'))), null())]", + "plan": "[parameters('plan')]", + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "securityProfile": { + "encryptionAtHost": "[if(parameters('encryptionAtHost'), parameters('encryptionAtHost'), null())]", + "securityType": "[parameters('securityType')]", + "uefiSettings": "[if(equals(parameters('securityType'), 'TrustedLaunch'), createObject('secureBootEnabled', parameters('secureBootEnabled'), 'vTpmEnabled', parameters('vTpmEnabled')), null())]" + }, + "storageProfile": { + "copy": [ + { + "name": "dataDisks", + "count": "[length(coalesce(parameters('dataDisks'), createArray()))]", + "input": { + "lun": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'lun'), copyIndex('dataDisks'))]", + "name": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), last(split(coalesce(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.id, ''), '/')), coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0'))))]", + "createOption": "[if(or(not(equals(resourceId('Microsoft.Compute/disks', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))), null())), not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id')))), 'Attach', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'createoption'), 'Empty'))]", + "deleteOption": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), 'Detach', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'deleteOption'), 'Delete'))]", + "caching": "[if(not(empty(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'))), 'None', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'caching'), 'ReadOnly'))]", + "managedDisk": { + "id": "[coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'id'), resourceId('Microsoft.Compute/disks', coalesce(tryGet(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')], 'name'), format('{0}-disk-data-{1}', parameters('name'), padLeft(add(copyIndex('dataDisks'), 1), 2, '0')))))]", + "diskEncryptionSet": "[if(contains(coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk, 'diskEncryptionSet'), createObject('id', coalesce(parameters('dataDisks'), createArray())[copyIndex('dataDisks')].managedDisk.diskEncryptionSet.id), null())]" + } + } + } + ], + "imageReference": "[parameters('imageReference')]", + "osDisk": { + "name": "[coalesce(tryGet(parameters('osDisk'), 'name'), format('{0}-disk-os-01', parameters('name')))]", + "createOption": "[coalesce(tryGet(parameters('osDisk'), 'createOption'), 'FromImage')]", + "deleteOption": "[coalesce(tryGet(parameters('osDisk'), 'deleteOption'), 'Delete')]", + "diffDiskSettings": "[if(empty(coalesce(tryGet(parameters('osDisk'), 'diffDiskSettings'), createObject())), null(), createObject('option', 'Local', 'placement', parameters('osDisk').diffDiskSettings.placement))]", + "diskSizeGB": "[tryGet(parameters('osDisk'), 'diskSizeGB')]", + "caching": "[coalesce(tryGet(parameters('osDisk'), 'caching'), 'ReadOnly')]", + "managedDisk": { + "storageAccountType": "[tryGet(parameters('osDisk').managedDisk, 'storageAccountType')]", + "diskEncryptionSet": { + "id": "[tryGet(parameters('osDisk').managedDisk, 'diskEncryptionSetResourceId')]" + } + } + } + }, + "additionalCapabilities": { + "ultraSSDEnabled": "[parameters('ultraSSDEnabled')]", + "hibernationEnabled": "[parameters('hibernationEnabled')]" + }, + "osProfile": { + "computerName": "[parameters('computerName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]", + "customData": "[if(not(empty(parameters('customData'))), base64(parameters('customData')), null())]", + "windowsConfiguration": "[if(equals(parameters('osType'), 'Windows'), variables('windowsConfiguration'), null())]", + "linuxConfiguration": "[if(equals(parameters('osType'), 'Linux'), variables('linuxConfiguration'), null())]", + "secrets": "[parameters('certificatesToBeInstalled')]", + "allowExtensionOperations": "[parameters('allowExtensionOperations')]" + }, + "networkProfile": { + "copy": [ + { + "name": "networkInterfaces", + "count": "[length(parameters('nicConfigurations'))]", + "input": { + "properties": { + "deleteOption": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'deleteOption'), 'Delete')]", + "primary": "[if(equals(copyIndex('networkInterfaces'), 0), true(), false())]" + }, + "id": "[resourceId('Microsoft.Network/networkInterfaces', coalesce(tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'name'), format('{0}{1}', parameters('name'), tryGet(parameters('nicConfigurations')[copyIndex('networkInterfaces')], 'nicSuffix'))))]" + } + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": "[if(not(empty(parameters('bootDiagnosticStorageAccountName'))), true(), parameters('bootDiagnostics'))]", + "storageUri": "[if(not(empty(parameters('bootDiagnosticStorageAccountName'))), format('https://{0}{1}', parameters('bootDiagnosticStorageAccountName'), parameters('bootDiagnosticStorageAccountUri')), null())]" + } + }, + "applicationProfile": "[if(not(empty(parameters('galleryApplications'))), createObject('galleryApplications', parameters('galleryApplications')), null())]", + "availabilitySet": "[if(not(empty(parameters('availabilitySetResourceId'))), createObject('id', parameters('availabilitySetResourceId')), null())]", + "proximityPlacementGroup": "[if(not(empty(parameters('proximityPlacementGroupResourceId'))), createObject('id', parameters('proximityPlacementGroupResourceId')), null())]", + "virtualMachineScaleSet": "[if(not(empty(parameters('virtualMachineScaleSetResourceId'))), createObject('id', parameters('virtualMachineScaleSetResourceId')), null())]", + "priority": "[parameters('priority')]", + "evictionPolicy": "[if(and(not(empty(parameters('priority'))), not(equals(parameters('priority'), 'Regular'))), parameters('evictionPolicy'), null())]", + "billingProfile": "[if(and(not(empty(parameters('priority'))), not(empty(parameters('maxPriceForLowPriorityVm')))), createObject('maxPrice', json(parameters('maxPriceForLowPriorityVm'))), null())]", + "host": "[if(not(empty(parameters('dedicatedHostId'))), createObject('id', parameters('dedicatedHostId')), null())]", + "licenseType": "[if(not(empty(parameters('licenseType'))), parameters('licenseType'), null())]", + "userData": "[if(not(empty(parameters('userData'))), base64(parameters('userData')), null())]" + }, + "dependsOn": [ + "managedDataDisks", + "vm_nic" + ] + }, + "vm_configurationAssignment": { + "condition": "[not(empty(parameters('maintenanceConfigurationResourceId')))]", + "type": "Microsoft.Maintenance/configurationAssignments", + "apiVersion": "2023-04-01", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[format('{0}assignment', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "maintenanceConfigurationId": "[parameters('maintenanceConfigurationResourceId')]", + "resourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" + }, + "dependsOn": [ + "vm" + ] + }, + "vm_configurationProfileAssignment": { + "condition": "[not(empty(parameters('configurationProfile')))]", + "type": "Microsoft.Automanage/configurationProfileAssignments", + "apiVersion": "2022-05-04", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "default", + "properties": { + "configurationProfile": "[parameters('configurationProfile')]" + }, + "dependsOn": [ + "vm" + ] + }, + "vm_autoShutdownConfiguration": { + "condition": "[not(empty(parameters('autoShutdownConfig')))]", + "type": "Microsoft.DevTestLab/schedules", + "apiVersion": "2018-09-15", + "name": "[format('shutdown-computevm-{0}', parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "status": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'status'), 'Disabled')]", + "targetResourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]", + "taskType": "ComputeVmShutdownTask", + "dailyRecurrence": { + "time": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'dailyRecurrenceTime'), '19:00')]" + }, + "timeZoneId": "[coalesce(tryGet(parameters('autoShutdownConfig'), 'timeZone'), 'UTC')]", + "notificationSettings": "[if(contains(parameters('autoShutdownConfig'), 'notificationSettings'), createObject('status', coalesce(tryGet(parameters('autoShutdownConfig'), 'status'), 'Disabled'), 'emailRecipient', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'emailRecipient'), ''), 'notificationLocale', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'notificationLocale'), 'en'), 'webhookUrl', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'webhookUrl'), ''), 'timeInMinutes', coalesce(tryGet(tryGet(parameters('autoShutdownConfig'), 'notificationSettings'), 'timeInMinutes'), 30)), null())]" + }, + "dependsOn": [ + "vm" + ] + }, + "vm_dataCollectionRuleAssociations": { + "copy": { + "name": "vm_dataCollectionRuleAssociations", + "count": "[length(parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations)]" + }, + "condition": "[parameters('extensionMonitoringAgentConfig').enabled]", + "type": "Microsoft.Insights/dataCollectionRuleAssociations", + "apiVersion": "2023-03-11", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations[copyIndex()].name]", + "properties": { + "dataCollectionRuleId": "[parameters('extensionMonitoringAgentConfig').dataCollectionRuleAssociations[copyIndex()].dataCollectionRuleResourceId]" + }, + "dependsOn": [ + "vm", + "vm_azureMonitorAgentExtension" + ] + }, + "AzureWindowsBaseline": { + "condition": "[not(empty(parameters('guestConfiguration')))]", + "type": "Microsoft.GuestConfiguration/guestConfigurationAssignments", + "apiVersion": "2020-06-25", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('guestConfiguration'), 'name'), 'AzureWindowsBaseline')]", + "location": "[parameters('location')]", + "properties": { + "guestConfiguration": "[parameters('guestConfiguration')]" + }, + "dependsOn": [ + "vm", + "vm_azureGuestConfigurationExtension" + ] + }, + "vm_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/virtualMachines/{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": [ + "vm" + ] + }, + "vm_roleAssignments": { + "copy": { + "name": "vm_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Compute/virtualMachines', 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": [ + "vm" + ] + }, + "vm_nic": { + "copy": { + "name": "vm_nic", + "count": "[length(parameters('nicConfigurations'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-Nic-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "networkInterfaceName": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'name'), format('{0}{1}', parameters('name'), tryGet(parameters('nicConfigurations')[copyIndex()], 'nicSuffix')))]" + }, + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "enableIPForwarding": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'enableIPForwarding'), false())]" + }, + "enableAcceleratedNetworking": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'enableAcceleratedNetworking'), true())]" + }, + "dnsServers": "[if(contains(parameters('nicConfigurations')[copyIndex()], 'dnsServers'), if(not(empty(tryGet(parameters('nicConfigurations')[copyIndex()], 'dnsServers'))), createObject('value', tryGet(parameters('nicConfigurations')[copyIndex()], 'dnsServers')), createObject('value', createArray())), createObject('value', createArray()))]", + "networkSecurityGroupResourceId": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'networkSecurityGroupResourceId'), '')]" + }, + "ipConfigurations": { + "value": "[parameters('nicConfigurations')[copyIndex()].ipConfigurations]" + }, + "lock": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'lock'), parameters('lock'))]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('nicConfigurations')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('nicConfigurations')[copyIndex()], 'diagnosticSettings')]" + }, + "roleAssignments": { + "value": "[tryGet(parameters('nicConfigurations')[copyIndex()], 'roleAssignments')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "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.34.44.8038", + "templateHash": "3333482934245501039" + } + }, + "definitions": { + "publicIPConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Public IP Address." + } + }, + "publicIPAddressResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the public IP address." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Diagnostic settings for the public IP address." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout in minutes." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the public IP address." + } + }, + "idleTimeoutInMinutes": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The idle timeout of the public IP address." + } + }, + "ddosSettings": { + "$ref": "#/definitions/ddosSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DDoS protection plan configuration associated with the public IP address." + } + }, + "dnsSettings": { + "$ref": "#/definitions/dnsSettingsType", + "nullable": true, + "metadata": { + "description": "Optional. The DNS settings of the public IP address." + } + }, + "publicIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address version." + } + }, + "publicIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The public IP address allocation method." + } + }, + "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." + } + }, + "publicIpNameSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name suffix of the public IP address resource." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "skuName": { + "type": "string", + "allowedValues": [ + "Basic", + "Standard" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU name of the public IP address." + } + }, + "skuTier": { + "type": "string", + "allowedValues": [ + "Global", + "Regional" + ], + "nullable": true, + "metadata": { + "description": "Optional. The SKU tier of the public IP address." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "zones": { + "type": "array", + "allowedValues": [ + 1, + 2, + 3 + ], + "nullable": true, + "metadata": { + "description": "Optional. The zones of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the public IP address configuration." + } + }, + "ipConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the IP configuration." + } + }, + "privateIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address allocation method." + } + }, + "privateIPAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The private IP address." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "loadBalancerBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/backendAddressPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer backend address pools." + } + }, + "applicationSecurityGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationSecurityGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application security groups." + } + }, + "applicationGatewayBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationGatewayBackendAddressPoolsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The application gateway backend address pools." + } + }, + "gatewayLoadBalancer": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The gateway load balancer settings." + } + }, + "loadBalancerInboundNatRules": { + "type": "array", + "items": { + "$ref": "#/definitions/inboundNatRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The load balancer inbound NAT rules." + } + }, + "privateIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address version." + } + }, + "virtualNetworkTaps": { + "type": "array", + "items": { + "$ref": "#/definitions/virtualNetworkTapType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The virtual network taps." + } + }, + "pipConfiguration": { + "$ref": "#/definitions/publicIPConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. The public IP address configuration." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the IP configuration." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The tags of the public IP address." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for the module." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the IP configuration." + } + }, + "applicationGatewayBackendAddressPoolsType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddresses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. IP address of the backend address." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN of the backend address." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Backend addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application gateway backend address pool." + } + } + }, + "metadata": { + "description": "The type for the application gateway backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "applicationSecurityGroupType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application security group." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the application security group." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application security group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the application security group." + } + } + }, + "metadata": { + "description": "The type for the application security group.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "backendAddressPoolType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the backend address pool." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The properties of the backend address pool." + } + } + }, + "metadata": { + "description": "The type for a backend address pool.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "inboundNatRuleType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the inbound NAT rule." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddressPool": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to backendAddressPool resource." + } + }, + "backendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." + } + }, + "enableFloatingIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." + } + }, + "enableTcpReset": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." + } + }, + "frontendIPConfiguration": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to frontend IP addresses." + } + }, + "frontendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeStart": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeEnd": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "All", + "Tcp", + "Udp" + ], + "nullable": true, + "metadata": { + "description": "Optional. The reference to the transport protocol used by the load balancing rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the inbound NAT rule." + } + } + }, + "metadata": { + "description": "The type for the inbound NAT rule.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "ipTagType": { + "type": "object", + "properties": { + "ipTagType": { + "type": "string", + "metadata": { + "description": "Required. The IP tag type." + } + }, + "tag": { + "type": "string", + "metadata": { + "description": "Required. The IP tag." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/public-ip-address:0.8.0" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "networkInterfaceIPConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the IP configuration." + } + }, + "privateIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The private IP address." + } + }, + "publicIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The public IP address." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "subResourceType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the sub resource." + } + } + }, + "metadata": { + "description": "The type for the sub resource.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + }, + "virtualNetworkTapType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the virtual network tap." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the virtual network tap." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the virtual network tap." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the virtual network tap." + } + } + }, + "metadata": { + "description": "The type for the virtual network tap.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/network/network-interface:0.5.1" + } + } + } + }, + "parameters": { + "networkInterfaceName": { + "type": "string" + }, + "virtualMachineName": { + "type": "string" + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableIPForwarding": { + "type": "bool", + "defaultValue": false + }, + "enableAcceleratedNetworking": { + "type": "bool", + "defaultValue": false + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [] + }, + "enableTelemetry": { + "type": "bool", + "metadata": { + "description": "Required. Enable telemetry via a Globally Unique Identifier (GUID)." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The network security group (NSG) to attach to the network interface." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "resources": { + "networkInterface_publicIPAddresses": { + "copy": { + "name": "networkInterface_publicIPAddresses", + "count": "[length(parameters('ipConfigurations'))]" + }, + "condition": "[and(not(empty(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'))), empty(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAddressResourceId')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-publicIP-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'name'), format('{0}{1}', parameters('virtualMachineName'), tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIpNameSuffix')))]" + }, + "diagnosticSettings": { + "value": "[coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'diagnosticSettings'), tryGet(parameters('ipConfigurations')[copyIndex()], 'diagnosticSettings'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "idleTimeoutInMinutes": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'idleTimeoutInMinutes')]" + }, + "ddosSettings": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'ddosSettings')]" + }, + "dnsSettings": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'dnsSettings')]" + }, + "publicIPAddressVersion": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAddressVersion')]" + }, + "publicIPAllocationMethod": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIPAllocationMethod')]" + }, + "publicIpPrefixResourceId": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'publicIpPrefixResourceId')]" + }, + "roleAssignments": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'roleAssignments')]" + }, + "skuName": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'skuName')]" + }, + "skuTier": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'skuTier')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'tags'), parameters('tags'))]" + }, + "zones": { + "value": "[tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'zones')]" + }, + "enableTelemetry": { + "value": "[coalesce(coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex()], 'pipConfiguration'), 'enableTelemetry'), tryGet(parameters('ipConfigurations')[copyIndex()], '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.33.93.31351", + "templateHash": "5168739580767459761" + }, + "name": "Public IP Addresses", + "description": "This module deploys a Public IP Address." + }, + "definitions": { + "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" + ], + "nullable": true, + "metadata": { + "description": "Optional. 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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipTagType": { + "type": "object", + "properties": { + "ipTagType": { + "type": "string", + "metadata": { + "description": "Required. The IP tag type." + } + }, + "tag": { + "type": "string", + "metadata": { + "description": "Required. The IP tag." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" + } + } + } + }, + "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." + } + }, + "ipTags": { + "type": "array", + "items": { + "$ref": "#/definitions/ipTagType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of tags associated with the public IP address." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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": "[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.8.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" + } + } + } + } + }, + "publicIpAddress": { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2024-05-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": "[parameters('ipTags')]" + } + }, + "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', '2024-05-01', 'full').location]" + } + } + } + } + }, + "networkInterface": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-NetworkInterface', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('networkInterfaceName')]" + }, + "ipConfigurations": { + "copy": [ + { + "name": "value", + "count": "[length(parameters('ipConfigurations'))]", + "input": "[createObject('name', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'name'), 'privateIPAllocationMethod', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAllocationMethod'), 'privateIPAddress', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAddress'), 'publicIPAddressResourceId', if(not(empty(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'))), if(not(contains(coalesce(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), createObject()), 'publicIPAddressResourceId')), resourceId('Microsoft.Network/publicIPAddresses', coalesce(tryGet(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), 'name'), format('{0}{1}', parameters('virtualMachineName'), tryGet(tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration'), 'publicIpNameSuffix')))), tryGet(parameters('ipConfigurations')[copyIndex('value')], 'pipConfiguration', 'publicIPAddressResourceId')), null()), 'subnetResourceId', parameters('ipConfigurations')[copyIndex('value')].subnetResourceId, 'loadBalancerBackendAddressPools', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'loadBalancerBackendAddressPools'), 'applicationSecurityGroups', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'applicationSecurityGroups'), 'applicationGatewayBackendAddressPools', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'applicationGatewayBackendAddressPools'), 'gatewayLoadBalancer', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'gatewayLoadBalancer'), 'loadBalancerInboundNatRules', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'loadBalancerInboundNatRules'), 'privateIPAddressVersion', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'privateIPAddressVersion'), 'virtualNetworkTaps', tryGet(parameters('ipConfigurations')[copyIndex('value')], 'virtualNetworkTaps'))]" + } + ] + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "diagnosticSettings": { + "value": "[parameters('diagnosticSettings')]" + }, + "dnsServers": { + "value": "[parameters('dnsServers')]" + }, + "enableAcceleratedNetworking": { + "value": "[parameters('enableAcceleratedNetworking')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "enableIPForwarding": { + "value": "[parameters('enableIPForwarding')]" + }, + "lock": { + "value": "[parameters('lock')]" + }, + "networkSecurityGroupResourceId": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('value', parameters('networkSecurityGroupResourceId')), createObject('value', ''))]", + "roleAssignments": { + "value": "[parameters('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.34.44.8038", + "templateHash": "8196054567469390015" + }, + "name": "Network Interface", + "description": "This module deploys a Network Interface." + }, + "definitions": { + "networkInterfaceIPConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the IP configuration." + } + }, + "privateIPAllocationMethod": { + "type": "string", + "allowedValues": [ + "Dynamic", + "Static" + ], + "nullable": true, + "metadata": { + "description": "Optional. The private IP address allocation method." + } + }, + "privateIPAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The private IP address." + } + }, + "publicIPAddressResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the public IP address." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the subnet." + } + }, + "loadBalancerBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/backendAddressPoolType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of load balancer backend address pools." + } + }, + "loadBalancerInboundNatRules": { + "type": "array", + "items": { + "$ref": "#/definitions/inboundNatRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of references of LoadBalancerInboundNatRules." + } + }, + "applicationSecurityGroups": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationSecurityGroupType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the IP configuration is included." + } + }, + "applicationGatewayBackendAddressPools": { + "type": "array", + "items": { + "$ref": "#/definitions/applicationGatewayBackendAddressPoolsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The reference to Application Gateway Backend Address Pools." + } + }, + "gatewayLoadBalancer": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. The reference to gateway load balancer frontend IP." + } + }, + "privateIPAddressVersion": { + "type": "string", + "allowedValues": [ + "IPv4", + "IPv6" + ], + "nullable": true, + "metadata": { + "description": "Optional. Whether the specific IP configuration is IPv4 or IPv6." + } + }, + "virtualNetworkTaps": { + "type": "array", + "items": { + "$ref": "#/definitions/virtualNetworkTapType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The reference to Virtual Network Taps." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The resource ID of the deployed resource." + } + }, + "backendAddressPoolType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the backend address pool." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The properties of the backend address pool." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a backend address pool." + } + }, + "applicationSecurityGroupType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the application security group." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the application security group." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application security group." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the application security group." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the application security group." + } + }, + "applicationGatewayBackendAddressPoolsType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the backend address pool." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the backend address pool that is unique within an Application Gateway." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddresses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. IP address of the backend address." + } + }, + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN of the backend address." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Backend addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the application gateway backend address pool." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the application gateway backend address pool." + } + }, + "subResourceType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the sub resource." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the sub resource." + } + }, + "inboundNatRuleType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the inbound NAT rule." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the resource that is unique within the set of inbound NAT rules used by the load balancer. This name can be used to access the resource." + } + }, + "properties": { + "type": "object", + "properties": { + "backendAddressPool": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to backendAddressPool resource." + } + }, + "backendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port used for the internal endpoint. Acceptable values range from 1 to 65535." + } + }, + "enableFloatingIP": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn Availability Groups in SQL server. This setting can't be changed after you create the endpoint." + } + }, + "enableTcpReset": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination. This element is only used when the protocol is set to TCP." + } + }, + "frontendIPConfiguration": { + "$ref": "#/definitions/subResourceType", + "nullable": true, + "metadata": { + "description": "Optional. A reference to frontend IP addresses." + } + }, + "frontendPort": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port for the external endpoint. Port numbers for each rule must be unique within the Load Balancer. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeStart": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range start for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeEnd. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "frontendPortRangeEnd": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The port range end for the external endpoint. This property is used together with BackendAddressPool and FrontendPortRangeStart. Individual inbound NAT rule port mappings will be created for each backend address from BackendAddressPool. Acceptable values range from 1 to 65534." + } + }, + "protocol": { + "type": "string", + "allowedValues": [ + "All", + "Tcp", + "Udp" + ], + "nullable": true, + "metadata": { + "description": "Optional. The reference to the transport protocol used by the load balancing rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Properties of the inbound NAT rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the inbound NAT rule." + } + }, + "virtualNetworkTapType": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the virtual network tap." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location of the virtual network tap." + } + }, + "properties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Properties of the virtual network tap." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the virtual network tap." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the virtual network tap." + } + }, + "networkInterfaceIPConfigurationOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the IP configuration." + } + }, + "privateIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The private IP address." + } + }, + "publicIP": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The public IP address." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the 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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the network interface." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Resource tags." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "enableIPForwarding": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether IP forwarding is enabled on this network interface." + } + }, + "enableAcceleratedNetworking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If the network interface is accelerated networking enabled." + } + }, + "dnsServers": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. List of DNS servers IP addresses. Use 'AzureProvidedDNS' to switch to azure provided DNS resolution. 'AzureProvidedDNS' value cannot be combined with other IPs, it must be the only value in dnsServers collection." + } + }, + "networkSecurityGroupResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The network security group (NSG) to attach to the network interface." + } + }, + "auxiliaryMode": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "Floating", + "MaxConnections", + "None" + ], + "metadata": { + "description": "Optional. Auxiliary mode of Network Interface resource. Not all regions are enabled for Auxiliary Mode Nic." + } + }, + "auxiliarySku": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "A1", + "A2", + "A4", + "A8", + "None" + ], + "metadata": { + "description": "Optional. Auxiliary sku of Network Interface resource. Not all regions are enabled for Auxiliary Mode Nic." + } + }, + "disableTcpStateTracking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to disable tcp state tracking. Subscription must be registered for the Microsoft.Network/AllowDisableTcpStateTracking feature before this property can be set to true." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationType" + }, + "metadata": { + "description": "Required. A list of IPConfigurations of the network interface." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "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')]", + "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": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "publicIp": { + "copy": { + "name": "publicIp", + "count": "[length(parameters('ipConfigurations'))]" + }, + "condition": "[and(contains(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), not(equals(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), null())))]", + "existing": true, + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2024-05-01", + "resourceGroup": "[split(coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), ''), '/')[4]]", + "name": "[last(split(coalesce(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), ''), '/'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-networkinterface.{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" + } + } + } + } + }, + "networkInterface": { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2024-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "ipConfigurations", + "count": "[length(parameters('ipConfigurations'))]", + "input": { + "name": "[coalesce(tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'name'), format('ipconfig0{0}', add(copyIndex('ipConfigurations'), 1)))]", + "properties": { + "primary": "[if(equals(copyIndex('ipConfigurations'), 0), true(), false())]", + "privateIPAllocationMethod": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAllocationMethod')]", + "privateIPAddress": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAddress')]", + "publicIPAddress": "[if(contains(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId'), if(not(equals(tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId'), null())), createObject('id', tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'publicIPAddressResourceId')), null()), null())]", + "subnet": { + "id": "[parameters('ipConfigurations')[copyIndex('ipConfigurations')].subnetResourceId]" + }, + "loadBalancerBackendAddressPools": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'loadBalancerBackendAddressPools')]", + "applicationSecurityGroups": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'applicationSecurityGroups')]", + "applicationGatewayBackendAddressPools": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'applicationGatewayBackendAddressPools')]", + "gatewayLoadBalancer": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'gatewayLoadBalancer')]", + "loadBalancerInboundNatRules": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'loadBalancerInboundNatRules')]", + "privateIPAddressVersion": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'privateIPAddressVersion')]", + "virtualNetworkTaps": "[tryGet(parameters('ipConfigurations')[copyIndex('ipConfigurations')], 'virtualNetworkTaps')]" + } + } + } + ], + "auxiliaryMode": "[parameters('auxiliaryMode')]", + "auxiliarySku": "[parameters('auxiliarySku')]", + "disableTcpStateTracking": "[parameters('disableTcpStateTracking')]", + "dnsSettings": "[if(not(empty(parameters('dnsServers'))), createObject('dnsServers', parameters('dnsServers')), null())]", + "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", + "enableIPForwarding": "[parameters('enableIPForwarding')]", + "networkSecurityGroup": "[if(not(empty(parameters('networkSecurityGroupResourceId'))), createObject('id', parameters('networkSecurityGroupResourceId')), null())]" + } + }, + "networkInterface_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/networkInterfaces/{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": [ + "networkInterface" + ] + }, + "networkInterface_diagnosticSettings": { + "copy": { + "name": "networkInterface_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Network/networkInterfaces/{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": [ + "networkInterface" + ] + }, + "networkInterface_roleAssignments": { + "copy": { + "name": "networkInterface_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/networkInterfaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/networkInterfaces', 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": [ + "networkInterface" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed resource." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed resource." + }, + "value": "[resourceId('Microsoft.Network/networkInterfaces', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed resource." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('networkInterface', '2024-05-01', 'full').location]" + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" + }, + "metadata": { + "description": "The list of IP configurations of the network interface." + }, + "copy": { + "count": "[length(parameters('ipConfigurations'))]", + "input": { + "name": "[reference('networkInterface').ipConfigurations[copyIndex()].name]", + "privateIP": "[coalesce(tryGet(reference('networkInterface').ipConfigurations[copyIndex()].properties, 'privateIPAddress'), '')]", + "publicIP": "[if(and(contains(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), not(equals(tryGet(parameters('ipConfigurations')[copyIndex()], 'publicIPAddressResourceId'), null()))), coalesce(reference(format('publicIp[{0}]', copyIndex())).ipAddress, ''), '')]" + } + } + } + } + } + }, + "dependsOn": [ + "networkInterface_publicIPAddresses" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the network interface." + }, + "value": "[reference('networkInterface').outputs.name.value]" + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/networkInterfaceIPConfigurationOutputType" + }, + "metadata": { + "description": "The list of IP configurations of the network interface." + }, + "value": "[reference('networkInterface').outputs.ipConfigurations.value]" + } + } + } + } + }, + "vm_domainJoinExtension": { + "condition": "[and(contains(parameters('extensionDomainJoinConfig'), 'enabled'), parameters('extensionDomainJoinConfig').enabled)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-DomainJoin', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'name'), 'DomainJoin')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Compute" + }, + "type": { + "value": "JsonADDomainExtension" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'typeHandlerVersion'), '1.3')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[parameters('extensionDomainJoinConfig').settings]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionDomainJoinConfig'), 'tags'), parameters('tags'))]" + }, + "protectedSettings": { + "value": { + "Password": "[parameters('extensionDomainJoinPassword')]" + } + } + }, + "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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm" + ] + }, + "vm_aadJoinExtension": { + "condition": "[parameters('extensionAadJoinConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-AADLogin', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'name'), 'AADLogin')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.ActiveDirectory" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AADLoginForWindows'), createObject('value', 'AADSSHLoginforLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '2.0', '1.0'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionAadJoinConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_domainJoinExtension" + ] + }, + "vm_microsoftAntiMalwareExtension": { + "condition": "[parameters('extensionAntiMalwareConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-MicrosoftAntiMalware', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'name'), 'MicrosoftAntiMalware')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Security" + }, + "type": { + "value": "IaaSAntimalware" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'typeHandlerVersion'), '1.3')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'settings'), createObject('AntimalwareEnabled', 'true', 'Exclusions', createObject(), 'RealtimeProtectionEnabled', 'true', 'ScheduledScanSettings', createObject('day', '7', 'isEnabled', 'true', 'scanType', 'Quick', 'time', '120')))]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionAntiMalwareConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_aadJoinExtension" + ] + }, + "vm_azureMonitorAgentExtension": { + "condition": "[parameters('extensionMonitoringAgentConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-AzureMonitorAgent', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'name'), 'AzureMonitorAgent')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Monitor" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AzureMonitorWindowsAgent'), createObject('value', 'AzureMonitorLinuxAgent'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.22', '1.29'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionMonitoringAgentConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_microsoftAntiMalwareExtension" + ] + }, + "vm_dependencyAgentExtension": { + "condition": "[parameters('extensionDependencyAgentConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-DependencyAgent', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'name'), 'DependencyAgent')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Monitoring.DependencyAgent" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'DependencyAgentWindows'), createObject('value', 'DependencyAgentLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'typeHandlerVersion'), '9.10')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'enableAutomaticUpgrade'), true())]" + }, + "settings": { + "value": { + "enableAMA": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'enableAMA'), true())]" + } + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionDependencyAgentConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_azureMonitorAgentExtension" + ] + }, + "vm_networkWatcherAgentExtension": { + "condition": "[parameters('extensionNetworkWatcherAgentConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-NetworkWatcherAgent', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'name'), 'NetworkWatcherAgent')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.NetworkWatcher" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'NetworkWatcherAgentWindows'), createObject('value', 'NetworkWatcherAgentLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'typeHandlerVersion'), '1.4')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionNetworkWatcherAgentConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_dependencyAgentExtension" + ] + }, + "vm_desiredStateConfigurationExtension": { + "condition": "[parameters('extensionDSCConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-DesiredStateConfiguration', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'name'), 'DesiredStateConfiguration')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Powershell" + }, + "type": { + "value": "DSC" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'typeHandlerVersion'), '2.77')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'tags'), parameters('tags'))]" + }, + "protectedSettings": { + "value": "[coalesce(tryGet(parameters('extensionDSCConfig'), 'protectedSettings'), 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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_networkWatcherAgentExtension" + ] + }, + "vm_customScriptExtension": { + "condition": "[parameters('extensionCustomScriptConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-CustomScriptExtension', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'name'), 'CustomScriptExtension')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'Microsoft.Compute'), createObject('value', 'Microsoft.Azure.Extensions'))]", + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'CustomScriptExtension'), createObject('value', 'CustomScript'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.10', '2.1'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": { + "copy": [ + { + "name": "fileUris", + "count": "[length(parameters('extensionCustomScriptConfig').fileData)]", + "input": "[if(contains(parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')], 'storageAccountId'), format('{0}?{1}', parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].uri, listAccountSas(parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].storageAccountId, '2019-04-01', variables('accountSasProperties')).accountSasToken), parameters('extensionCustomScriptConfig').fileData[copyIndex('fileUris')].uri)]" + } + ] + } + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionCustomScriptConfig'), 'tags'), parameters('tags'))]" + }, + "protectedSettings": { + "value": "[parameters('extensionCustomScriptProtectedSetting')]" + } + }, + "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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_desiredStateConfigurationExtension" + ] + }, + "vm_azureDiskEncryptionExtension": { + "condition": "[parameters('extensionAzureDiskEncryptionConfig').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-AzureDiskEncryption', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'name'), 'AzureDiskEncryption')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.Azure.Security" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'AzureDiskEncryption'), createObject('value', 'AzureDiskEncryptionForLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '2.2', '1.1'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'enableAutomaticUpgrade'), false())]" + }, + "forceUpdateTag": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'forceUpdateTag'), '1.0')]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionAzureDiskEncryptionConfig'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_customScriptExtension" + ] + }, + "vm_nvidiaGpuDriverWindowsExtension": { + "condition": "[parameters('extensionNvidiaGpuDriverWindows').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-NvidiaGpuDriverWindows', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'name'), 'NvidiaGpuDriverWindows')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.HpcCompute" + }, + "type": { + "value": "NvidiaGpuDriverWindows" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'typeHandlerVersion'), '1.4')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'enableAutomaticUpgrade'), false())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), 'supressFailures'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionNvidiaGpuDriverWindows'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_azureDiskEncryptionExtension" + ] + }, + "vm_hostPoolRegistrationExtension": { + "condition": "[parameters('extensionHostPoolRegistration').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-HostPoolRegistration', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'name'), 'HostPoolRegistration')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.PowerShell" + }, + "type": { + "value": "DSC" + }, + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'typeHandlerVersion'), '2.77')]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'enableAutomaticUpgrade'), false())]" + }, + "settings": { + "value": { + "modulesUrl": "[parameters('extensionHostPoolRegistration').modulesUrl]", + "configurationFunction": "[parameters('extensionHostPoolRegistration').configurationFunction]", + "properties": { + "hostPoolName": "[parameters('extensionHostPoolRegistration').hostPoolName]", + "registrationInfoToken": "[parameters('extensionHostPoolRegistration').registrationInfoToken]", + "aadJoin": true + }, + "supressFailures": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), 'supressFailures'), false())]" + } + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionHostPoolRegistration'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_nvidiaGpuDriverWindowsExtension" + ] + }, + "vm_azureGuestConfigurationExtension": { + "condition": "[parameters('extensionGuestConfigurationExtension').enabled]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-GuestConfiguration', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "virtualMachineName": { + "value": "[parameters('name')]" + }, + "name": "[if(coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'name'), equals(parameters('osType'), 'Windows')), createObject('value', 'AzurePolicyforWindows'), createObject('value', 'AzurePolicyforLinux'))]", + "location": { + "value": "[parameters('location')]" + }, + "publisher": { + "value": "Microsoft.GuestConfiguration" + }, + "type": "[if(equals(parameters('osType'), 'Windows'), createObject('value', 'ConfigurationforWindows'), createObject('value', 'ConfigurationForLinux'))]", + "typeHandlerVersion": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'typeHandlerVersion'), if(equals(parameters('osType'), 'Windows'), '1.0', '1.0'))]" + }, + "autoUpgradeMinorVersion": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'autoUpgradeMinorVersion'), true())]" + }, + "enableAutomaticUpgrade": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'enableAutomaticUpgrade'), true())]" + }, + "forceUpdateTag": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'forceUpdateTag'), '1.0')]" + }, + "settings": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'settings'), createObject())]" + }, + "supressFailures": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), 'supressFailures'), false())]" + }, + "protectedSettings": { + "value": "[parameters('extensionGuestConfigurationExtensionProtectedSettings')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('extensionGuestConfigurationExtension'), '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.34.44.8038", + "templateHash": "8482591295619883067" + }, + "name": "Virtual Machine Extensions", + "description": "This module deploys a Virtual Machine Extension." + }, + "parameters": { + "virtualMachineName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent virtual machine that extension is provisioned for. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the virtual machine extension." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location the extension is deployed to." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. The name of the extension handler publisher." + } + }, + "type": { + "type": "string", + "metadata": { + "description": "Required. Specifies the type of the extension; an example is \"CustomScriptExtension\"." + } + }, + "typeHandlerVersion": { + "type": "string", + "metadata": { + "description": "Required. Specifies the version of the script handler." + } + }, + "autoUpgradeMinorVersion": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should use a newer minor version if one is available at deployment time. Once deployed, however, the extension will not upgrade minor versions unless redeployed, even with this property set to true." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. How the extension handler should be forced to update even if the extension configuration has not changed." + } + }, + "settings": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific settings." + } + }, + "protectedSettings": { + "type": "secureObject", + "defaultValue": {}, + "metadata": { + "description": "Optional. Any object that contains the extension specific protected settings." + } + }, + "supressFailures": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether failures stemming from the extension will be suppressed (Operational failures such as not connecting to the VM will not be suppressed regardless of this value). The default is false." + } + }, + "enableAutomaticUpgrade": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether the extension should be automatically upgraded by the platform if there is a newer version of the extension available." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + } + }, + "resources": { + "virtualMachine": { + "existing": true, + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2022-11-01", + "name": "[parameters('virtualMachineName')]" + }, + "extension": { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2022-11-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "publisher": "[parameters('publisher')]", + "type": "[parameters('type')]", + "typeHandlerVersion": "[parameters('typeHandlerVersion')]", + "autoUpgradeMinorVersion": "[parameters('autoUpgradeMinorVersion')]", + "enableAutomaticUpgrade": "[parameters('enableAutomaticUpgrade')]", + "forceUpdateTag": "[if(not(empty(parameters('forceUpdateTag'))), parameters('forceUpdateTag'), null())]", + "settings": "[if(not(empty(parameters('settings'))), parameters('settings'), null())]", + "protectedSettings": "[if(not(empty(parameters('protectedSettings'))), parameters('protectedSettings'), null())]", + "suppressFailures": "[parameters('supressFailures')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the extension." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the extension." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the extension was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('extension', '2022-11-01', 'full').location]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_hostPoolRegistrationExtension" + ] + }, + "vm_backup": { + "condition": "[not(empty(parameters('backupVaultName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-VM-Backup', uniqueString(deployment().name, parameters('location')))]", + "resourceGroup": "[parameters('backupVaultResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[format('vm;iaasvmcontainerv2;{0};{1}', resourceGroup().name, parameters('name'))]" + }, + "location": { + "value": "[parameters('location')]" + }, + "policyId": { + "value": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', parameters('backupVaultName'), parameters('backupPolicyName'))]" + }, + "protectedItemType": { + "value": "Microsoft.Compute/virtualMachines" + }, + "protectionContainerName": { + "value": "[format('iaasvmcontainer;iaasvmcontainerv2;{0};{1}', resourceGroup().name, parameters('name'))]" + }, + "recoveryVaultName": { + "value": "[parameters('backupVaultName')]" + }, + "sourceResourceId": { + "value": "[resourceId('Microsoft.Compute/virtualMachines', 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.34.44.8038", + "templateHash": "7743264001610407207" + }, + "name": "Recovery Service Vaults Protection Container Protected Item", + "description": "This module deploys a Recovery Services Vault Protection Container Protected Item." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the resource." + } + }, + "protectionContainerName": { + "type": "string", + "metadata": { + "description": "Conditional. Name of the Azure Recovery Service Vault Protection Container. Required if the template is used in a standalone deployment." + } + }, + "recoveryVaultName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Azure Recovery Service Vault. Required if the template is used in a standalone deployment." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "protectedItemType": { + "type": "string", + "allowedValues": [ + "AzureFileShareProtectedItem", + "AzureVmWorkloadSAPAseDatabase", + "AzureVmWorkloadSAPHanaDatabase", + "AzureVmWorkloadSQLDatabase", + "DPMProtectedItem", + "GenericProtectedItem", + "MabFileFolderProtectedItem", + "Microsoft.ClassicCompute/virtualMachines", + "Microsoft.Compute/virtualMachines", + "Microsoft.Sql/servers/databases" + ], + "metadata": { + "description": "Required. The backup item type." + } + }, + "policyId": { + "type": "string", + "metadata": { + "description": "Required. ID of the backup policy with which this item is backed up." + } + }, + "sourceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource to back up." + } + } + }, + "resources": [ + { + "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems", + "apiVersion": "2023-01-01", + "name": "[format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "protectedItemType": "[parameters('protectedItemType')]", + "policyId": "[parameters('policyId')]", + "sourceResourceId": "[parameters('sourceResourceId')]" + } + } + ], + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Resource Group the protected item was created in." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the protected item." + }, + "value": "[resourceId('Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems', split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[0], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[1], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[2], split(format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name')), '/')[3])]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The Name of the protected item." + }, + "value": "[format('{0}/Azure/{1}/{2}', parameters('recoveryVaultName'), parameters('protectionContainerName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "vm", + "vm_azureGuestConfigurationExtension" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the VM." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the VM." + }, + "value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the VM was created in." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('vm', '2024-07-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('vm', '2024-07-01', 'full').location]" + }, + "nicConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/nicConfigurationOutputType" + }, + "metadata": { + "description": "The list of NIC configurations of the virtual machine." + }, + "copy": { + "count": "[length(parameters('nicConfigurations'))]", + "input": { + "name": "[reference(format('vm_nic[{0}]', copyIndex())).outputs.name.value]", + "ipConfigurations": "[reference(format('vm_nic[{0}]', copyIndex())).outputs.ipConfigurations.value]" + } + } + } + } + } + }, + "dependsOn": [ + "nsg", + "subnetResource" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "value": "[reference('vm').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "value": "[reference('vm').outputs.name.value]" + }, + "location": { + "type": "string", + "value": "[reference('vm').outputs.location.value]" + }, + "subnetId": { + "type": "string", + "value": "[reference('subnetResource').outputs.resourceId.value]" + }, + "subnetName": { + "type": "string", + "value": "[reference('subnetResource').outputs.name.value]" + }, + "nsgId": { + "type": "string", + "value": "[reference('nsg').outputs.resourceId.value]" + }, + "nsgName": { + "type": "string", + "value": "[reference('nsg').outputs.name.value]" + } } } }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - } - }, - "parameters": { - "privateDnsZoneName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the TXT record." - } - }, - "metadata": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. The metadata attached to the record set." - } - }, - "ttl": { - "type": "int", - "defaultValue": 3600, - "metadata": { - "description": "Optional. The TTL (time-to-live) of the records in the record set." - } - }, - "txtRecords": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The list of TXT records in the record set." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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')]", - "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", - "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": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" - }, - "TXT": { - "type": "Microsoft.Network/privateDnsZones/TXT", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "properties": { - "metadata": "[parameters('metadata')]", - "ttl": "[parameters('ttl')]", - "txtRecords": "[parameters('txtRecords')]" - } - }, - "TXT_roleAssignments": { - "copy": { - "name": "TXT_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Network/privateDnsZones/{0}/TXT/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), 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": [ - "TXT" + "virtualNetwork" ] } }, "outputs": { - "name": { + "vnetName": { "type": "string", - "metadata": { - "description": "The name of the deployed TXT record." - }, - "value": "[parameters('name')]" + "value": "[reference('virtualNetwork').outputs.name.value]" }, - "resourceId": { + "vnetResourceId": { "type": "string", - "metadata": { - "description": "The resource ID of the deployed TXT record." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), parameters('name'))]" + "value": "[reference('virtualNetwork').outputs.resourceId.value]" }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed TXT record." + "subnets": { + "type": "array", + "items": { + "$ref": "#/definitions/subnetOutputType" }, - "value": "[resourceGroup().name]" - } - } - } - }, - "dependsOn": [ - "privateDnsZone" - ] - }, - "privateDnsZone_virtualNetworkLinks": { - "copy": { - "name": "privateDnsZone_virtualNetworkLinks", - "count": "[length(coalesce(parameters('virtualNetworkLinks'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-PrivateDnsZone-VNetLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "privateDnsZoneName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'name'), format('{0}-vnetlink', last(split(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId, '/'))))]" - }, - "virtualNetworkResourceId": { - "value": "[coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId]" - }, - "location": { - "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'location'), 'global')]" - }, - "registrationEnabled": { - "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'registrationEnabled'), false())]" - }, - "tags": { - "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" - }, - "resolutionPolicy": { - "value": "[tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'resolutionPolicy')]" - } - }, - "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.34.44.8038", - "templateHash": "725891200086243555" - }, - "name": "Private DNS Zone Virtual Network Link", - "description": "This module deploys a Private DNS Zone Virtual Network Link." - }, - "parameters": { - "privateDnsZoneName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." - } + "value": "[reference('virtualNetwork').outputs.subnets.value]" }, - "name": { + "bastionSubnetId": { "type": "string", - "defaultValue": "[format('{0}-vnetlink', last(split(parameters('virtualNetworkResourceId'), '/')))]", - "metadata": { - "description": "Optional. The name of the virtual network link." - } + "value": "[reference('bastionHost').outputs.subnetId.value]" }, - "location": { + "bastionSubnetName": { "type": "string", - "defaultValue": "global", - "metadata": { - "description": "Optional. The location of the PrivateDNSZone. Should be global." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Tags of the resource." - } - }, - "registrationEnabled": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?." - } + "value": "[reference('bastionHost').outputs.subnetName.value]" }, - "virtualNetworkResourceId": { + "bastionHostId": { "type": "string", - "metadata": { - "description": "Required. Link to another virtual network resource ID." - } + "value": "[reference('bastionHost').outputs.resourceId.value]" }, - "resolutionPolicy": { + "bastionHostName": { "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resolution policy on the virtual network link. Only applicable for virtual network links to privatelink zones, and for A,AAAA,CNAME queries. When set to `NxDomainRedirect`, Azure DNS resolver falls back to public resolution if private dns query resolution results in non-existent domain response. `Default` is configured as the default option." - } - } - }, - "resources": { - "privateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[parameters('privateDnsZoneName')]" + "value": "[reference('bastionHost').outputs.name.value]" }, - "virtualNetworkLink": { - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "registrationEnabled": "[parameters('registrationEnabled')]", - "virtualNetwork": { - "id": "[parameters('virtualNetworkResourceId')]" - }, - "resolutionPolicy": "[parameters('resolutionPolicy')]" - } - } - }, - "outputs": { - "name": { + "jumpboxSubnetName": { "type": "string", - "metadata": { - "description": "The name of the deployed virtual network link." - }, - "value": "[parameters('name')]" + "value": "[reference('jumpbox').outputs.subnetName.value]" }, - "resourceId": { + "jumpboxSubnetId": { "type": "string", - "metadata": { - "description": "The resource ID of the deployed virtual network link." - }, - "value": "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', parameters('privateDnsZoneName'), parameters('name'))]" + "value": "[reference('jumpbox').outputs.subnetId.value]" }, - "resourceGroupName": { + "jumpboxName": { "type": "string", - "metadata": { - "description": "The resource group of the deployed virtual network link." - }, - "value": "[resourceGroup().name]" + "value": "[reference('jumpbox').outputs.name.value]" }, - "location": { + "jumpboxResourceId": { "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('virtualNetworkLink', '2024-06-01', 'full').location]" + "value": "[reference('jumpbox').outputs.resourceId.value]" } } } - }, - "dependsOn": [ - "privateDnsZone" - ] + } } - }, + ], "outputs": { - "resourceGroupName": { + "vnetName": { "type": "string", "metadata": { - "description": "The resource group the private DNS zone was deployed into." + "description": "Name of the Virtual Network resource." }, - "value": "[resourceGroup().name]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.vnetName.value]" }, - "name": { + "vnetResourceId": { "type": "string", "metadata": { - "description": "The name of the private DNS zone." + "description": "Resource ID of the Virtual Network." }, - "value": "[parameters('name')]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.vnetResourceId.value]" + }, + "subnetWebResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the \"web\" subnet." + }, + "value": "[coalesce(tryGet(first(filter(reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.subnets.value, lambda('s', equals(lambdaVariables('s').name, 'web')))), 'resourceId'), '')]" + }, + "subnetPrivateEndpointsResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the \"peps\" subnet for Private Endpoints." + }, + "value": "[coalesce(tryGet(first(filter(reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.subnets.value, lambda('s', equals(lambdaVariables('s').name, 'peps')))), 'resourceId'), '')]" }, - "resourceId": { + "bastionResourceId": { "type": "string", "metadata": { - "description": "The resource ID of the private DNS zone." + "description": "Resource ID of the Bastion Host." }, - "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('name'))]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.bastionHostId.value]" }, - "location": { + "jumpboxResourceId": { "type": "string", "metadata": { - "description": "The location the resource was deployed into." + "description": "Resource ID of the Jumpbox VM." }, - "value": "[reference('privateDnsZone', '2020-06-01', 'full').location]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('network-{0}-create', parameters('resourcesName')), 64)), '2022-09-01').outputs.jumpboxResourceId.value]" } } } }, "dependsOn": [ - "network" + "logAnalyticsWorkspace" ] }, - "logAnalyticsWorkspace": { - "condition": "[parameters('enableMonitoring')]", + "avmPrivateDnsZones": { + "copy": { + "name": "avmPrivateDnsZones", + "count": "[length(variables('privateDnsZones'))]", + "mode": "serial", + "batchSize": 5 + }, + "condition": "[and(parameters('enablePrivateNetworking'), or(empty(parameters('existingFoundryProjectResourceId')), not(contains(variables('aiRelatedDnsZoneIndices'), copyIndex()))))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[take(format('avm.res.operational-insights.workspace.{0}', variables('logAnalyticsWorkspaceResourceName')), 64)]", + "name": "[format('dns-zone-{0}', copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -19795,40 +20530,22 @@ "mode": "Incremental", "parameters": { "name": { - "value": "[variables('logAnalyticsWorkspaceResourceName')]" + "value": "[variables('privateDnsZones')[copyIndex()]]" }, "tags": { "value": "[parameters('tags')]" }, - "location": { - "value": "[variables('solutionLocation')]" - }, "enableTelemetry": { "value": "[parameters('enableTelemetry')]" }, - "skuName": { - "value": "PerGB2018" - }, - "dataRetention": { - "value": 365 - }, - "features": { - "value": { - "enableLogAccessUsingOnlyResourcePermissions": true - } - }, - "diagnosticSettings": { + "virtualNetworkLinks": { "value": [ { - "useThisWorkspace": true + "name": "[take(format('vnetlink-{0}-{1}', reference('network').outputs.vnetName.value, split(variables('privateDnsZones')[copyIndex()], '.')[1]), 80)]", + "virtualNetworkResourceId": "[reference('network').outputs.vnetResourceId.value]" } ] - }, - "dailyQuotaGb": "[if(parameters('enableRedundancy'), createObject('value', 10), createObject('value', null()))]", - "replication": "[if(parameters('enableRedundancy'), createObject('value', createObject('enabled', true(), 'location', variables('replicaLocation'))), createObject('value', null()))]", - "publicNetworkAccessForIngestion": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", - "publicNetworkAccessForQuery": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", - "dataSources": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('tags', parameters('tags'), 'eventLogName', 'Application', 'eventTypes', createArray(createObject('eventType', 'Error'), createObject('eventType', 'Warning'), createObject('eventType', 'Information')), 'kind', 'WindowsEvent', 'name', 'applicationEvent'), createObject('counterName', '% Processor Time', 'instanceName', '*', 'intervalSeconds', 60, 'kind', 'WindowsPerformanceCounter', 'name', 'windowsPerfCounter1', 'objectName', 'Processor'), createObject('kind', 'IISLogs', 'name', 'sampleIISLog1', 'state', 'OnPremiseEnabled'))), createObject('value', null()))]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -19837,678 +20554,580 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "1749032521457140145" + "version": "0.34.44.8038", + "templateHash": "4533956061065498344" }, - "name": "Log Analytics Workspaces", - "description": "This module deploys a Log Analytics Workspace." + "name": "Private DNS Zones", + "description": "This module deploys a Private DNS zone." }, "definitions": { - "diagnosticSettingType": { + "aType": { "type": "object", "properties": { "name": { "type": "string", + "metadata": { + "description": "Required. The name of the record." + } + }, + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. The name of diagnostic setting." + "description": "Optional. The metadata of the record." } }, - "logCategoriesAndGroups": { + "ttl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The TTL of the record." + } + }, + "roleAssignments": { "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`." - } - } - } + "$ref": "#/definitions/roleAssignmentType" }, "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." + "description": "Optional. Array of role assignments to create." } }, - "metricCategories": { + "aRecords": { "type": "array", "items": { "type": "object", "properties": { - "category": { + "ipv4Address": { "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`." + "description": "Required. The IPv4 address of this A record." } } } }, "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." - } - }, - "useThisWorkspace": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. If set to `true`, the `workspaceResourceId` property is ignored." - } - }, - "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." + "description": "Optional. The list of A records in the record set." } } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the A record." } }, - "gallerySolutionType": { + "aaaaType": { "type": "object", "properties": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + "description": "Required. The name of the record." } }, - "plan": { - "$ref": "#/definitions/solutionPlanType", + "metadata": { + "type": "object", + "nullable": true, "metadata": { - "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." + "description": "Optional. The metadata of the record." } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Properties of the gallery solutions to be created in the log analytics workspace." - } - }, - "storageInsightsConfigType": { - "type": "object", - "properties": { - "storageAccountResourceId": { - "type": "string", + }, + "ttl": { + "type": "int", + "nullable": true, "metadata": { - "description": "Required. Resource ID of the storage account to be linked." + "description": "Optional. The TTL of the record." } }, - "containers": { + "roleAssignments": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/roleAssignmentType" }, "nullable": true, "metadata": { - "description": "Optional. The names of the blob containers that the workspace should read." + "description": "Optional. Array of role assignments to create." } }, - "tables": { + "aaaaRecords": { "type": "array", "items": { - "type": "string" + "type": "object", + "properties": { + "ipv6Address": { + "type": "string", + "metadata": { + "description": "Required. The IPv6 address of this AAAA record." + } + } + } }, "nullable": true, "metadata": { - "description": "Optional. List of tables to be read by the workspace." + "description": "Optional. The list of AAAA records in the record set." } } }, "metadata": { "__bicep_export!": true, - "description": "Properties of the storage insights configuration." + "description": "The type for the AAAA record." } }, - "linkedServiceType": { + "cnameType": { "type": "object", "properties": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the linked service." + "description": "Required. The name of the record." } }, - "resourceId": { - "type": "string", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require read access." + "description": "Optional. The metadata of the record." } }, - "writeAccessResourceId": { - "type": "string", + "ttl": { + "type": "int", "nullable": true, "metadata": { - "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require write access." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Properties of the linked service." - } - }, - "linkedStorageAccountType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. Name of the link." + "description": "Optional. The TTL of the record." } }, - "storageAccountIds": { + "roleAssignments": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/roleAssignmentType" }, - "minLength": 1, + "nullable": true, "metadata": { - "description": "Required. Linked storage accounts resources Ids." + "description": "Optional. Array of role assignments to create." + } + }, + "cnameRecord": { + "type": "object", + "properties": { + "cname": { + "type": "string", + "metadata": { + "description": "Required. The canonical name of the CNAME record." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The CNAME record in the record set." } } }, "metadata": { "__bicep_export!": true, - "description": "Properties of the linked storage account." + "description": "The type for the CNAME record." } }, - "savedSearchType": { + "mxType": { "type": "object", "properties": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the saved search." - } - }, - "etag": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." - } - }, - "category": { - "type": "string", - "metadata": { - "description": "Required. The category of the saved search. This helps the user to find a saved search faster." - } - }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name for the search." + "description": "Required. The name of the record." } }, - "functionAlias": { - "type": "string", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. The function alias if query serves as a function." + "description": "Optional. The metadata of the record." } }, - "functionParameters": { - "type": "string", + "ttl": { + "type": "int", "nullable": true, "metadata": { - "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: 'param-name1:type1 = default_value1, param-name2:type2 = default_value2'. For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." - } - }, - "query": { - "type": "string", - "metadata": { - "description": "Required. The query expression for the saved search." + "description": "Optional. The TTL of the record." } }, - "tags": { + "roleAssignments": { "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, "nullable": true, "metadata": { - "description": "Optional. The tags attached to the saved search." + "description": "Optional. Array of role assignments to create." } }, - "version": { - "type": "int", + "mxRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "exchange": { + "type": "string", + "metadata": { + "description": "Required. The domain name of the mail host for this MX record." + } + }, + "preference": { + "type": "int", + "metadata": { + "description": "Required. The preference value for this MX record." + } + } + } + }, "nullable": true, "metadata": { - "description": "Optional. The version number of the query language. The current version is 2 and is the default." + "description": "Optional. The list of MX records in the record set." } } }, "metadata": { "__bicep_export!": true, - "description": "Properties of the saved search." + "description": "The type for the MX record." } }, - "dataExportType": { + "ptrType": { "type": "object", "properties": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the data export." + "description": "Required. The name of the record." } }, - "destination": { - "$ref": "#/definitions/destinationType", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. The destination of the data export." + "description": "Optional. The metadata of the record." } }, - "enable": { - "type": "bool", + "ttl": { + "type": "int", "nullable": true, "metadata": { - "description": "Optional. Enable or disable the data export." + "description": "Optional. The TTL of the record." } }, - "tableNames": { + "roleAssignments": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/roleAssignmentType" }, + "nullable": true, "metadata": { - "description": "Required. The list of table names to export." + "description": "Optional. Array of role assignments to create." + } + }, + "ptrRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ptrdname": { + "type": "string", + "metadata": { + "description": "Required. The PTR target domain name for this PTR record." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of PTR records in the record set." } } }, "metadata": { "__bicep_export!": true, - "description": "Properties of the data export." + "description": "The type for the PTR record." } }, - "dataSourceType": { + "soaType": { "type": "object", "properties": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the data source." - } - }, - "kind": { - "type": "string", - "metadata": { - "description": "Required. The kind of data source." - } - }, - "linkedResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource id of the resource that will be linked to the workspace." - } - }, - "eventLogName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the event log to configure when kind is WindowsEvent." - } - }, - "eventTypes": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. The event types to configure when kind is WindowsEvent." - } - }, - "objectName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + "description": "Required. The name of the record." } }, - "instanceName": { - "type": "string", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + "description": "Optional. The metadata of the record." } }, - "intervalSeconds": { + "ttl": { "type": "int", "nullable": true, "metadata": { - "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + "description": "Optional. The TTL of the record." } }, - "performanceCounters": { + "roleAssignments": { "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, "nullable": true, "metadata": { - "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." - } - }, - "counterName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." - } - }, - "state": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." - } - }, - "syslogName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. System log to configure when kind is LinuxSyslog." + "description": "Optional. Array of role assignments to create." } }, - "syslogSeverities": { - "type": "array", + "soaRecord": { + "type": "object", + "properties": { + "email": { + "type": "string", + "metadata": { + "description": "Required. The email contact for this SOA record." + } + }, + "expireTime": { + "type": "int", + "metadata": { + "description": "Required. The expire time for this SOA record." + } + }, + "host": { + "type": "string", + "metadata": { + "description": "Required. The domain name of the authoritative name server for this SOA record." + } + }, + "minimumTtl": { + "type": "int", + "metadata": { + "description": "Required. The minimum value for this SOA record. By convention this is used to determine the negative caching duration." + } + }, + "refreshTime": { + "type": "int", + "metadata": { + "description": "Required. The refresh value for this SOA record." + } + }, + "retryTime": { + "type": "int", + "metadata": { + "description": "Required. The retry time for this SOA record." + } + }, + "serialNumber": { + "type": "int", + "metadata": { + "description": "Required. The serial number for this SOA record." + } + } + }, "nullable": true, "metadata": { - "description": "Optional. Severities to configure when kind is LinuxSyslog." + "description": "Optional. The SOA record in the record set." } - }, - "tags": { - "type": "object", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" - }, - "description": "Optional. Tags to configure in the resource." - }, - "nullable": true } }, "metadata": { "__bicep_export!": true, - "description": "Properties of the data source." + "description": "The type for the SOA record." } }, - "tableType": { + "srvType": { "type": "object", "properties": { "name": { "type": "string", "metadata": { - "description": "Required. The name of the table." - } - }, - "plan": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The plan for the table." - } - }, - "restoredLogs": { - "$ref": "#/definitions/restoredLogsType", - "nullable": true, - "metadata": { - "description": "Optional. The restored logs for the table." - } - }, - "schema": { - "$ref": "#/definitions/schemaType", - "nullable": true, - "metadata": { - "description": "Optional. The schema for the table." + "description": "Required. The name of the record." } }, - "searchResults": { - "$ref": "#/definitions/searchResultsType", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. The search results for the table." + "description": "Optional. The metadata of the record." } }, - "retentionInDays": { + "ttl": { "type": "int", "nullable": true, "metadata": { - "description": "Optional. The retention in days for the table." + "description": "Optional. The TTL of the record." } }, - "totalRetentionInDays": { - "type": "int", + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, "nullable": true, "metadata": { - "description": "Optional. The total retention in days for the table." + "description": "Optional. Array of role assignments to create." } }, - "roleAssignments": { + "srvRecords": { "type": "array", "items": { - "$ref": "#/definitions/roleAssignmentType" + "type": "object", + "properties": { + "priority": { + "type": "int", + "metadata": { + "description": "Required. The priority value for this SRV record." + } + }, + "weight": { + "type": "int", + "metadata": { + "description": "Required. The weight value for this SRV record." + } + }, + "port": { + "type": "int", + "metadata": { + "description": "Required. The port value for this SRV record." + } + }, + "target": { + "type": "string", + "metadata": { + "description": "Required. The target domain name for this SRV record." + } + } + } }, "nullable": true, "metadata": { - "description": "Optional. The role assignments for the table." + "description": "Optional. The list of SRV records in the record set." } } }, "metadata": { "__bicep_export!": true, - "description": "Properties of the custom table." + "description": "The type for the SRV record." } }, - "workspaceFeaturesType": { + "txtType": { "type": "object", "properties": { - "disableLocalAuth": { - "type": "bool", - "nullable": true, + "name": { + "type": "string", "metadata": { - "description": "Optional. Disable Non-EntraID based Auth. Default is true." + "description": "Required. The name of the record." } }, - "enableDataExport": { - "type": "bool", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. Flag that indicate if data should be exported." + "description": "Optional. The metadata of the record." } }, - "enableLogAccessUsingOnlyResourcePermissions": { - "type": "bool", + "ttl": { + "type": "int", "nullable": true, "metadata": { - "description": "Optional. Enable log access using only resource permissions. Default is false." + "description": "Optional. The TTL of the record." } }, - "immediatePurgeDataOn30Days": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Flag that describes if we want to remove the data after 30 days." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "Features of the workspace." - } - }, - "workspaceReplicationType": { - "type": "object", - "properties": { - "enabled": { - "type": "bool", + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, "nullable": true, "metadata": { - "description": "Optional. Specifies whether the replication is enabled or not. When true, workspace configuration and data is replicated to the specified location." + "description": "Optional. Array of role assignments to create." } }, - "location": { - "type": "string", + "txtRecords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The text value of this TXT record." + } + } + } + }, "nullable": true, "metadata": { - "description": "Conditional. The location to which the workspace is replicated. Required if replication is enabled." + "description": "Optional. The list of TXT records in the record set." } } }, "metadata": { "__bicep_export!": true, - "description": "Replication properties of the workspace." + "description": "The type for the TXT record." } }, - "_1.columnType": { + "virtualNetworkLinkType": { "type": "object", "properties": { "name": { "type": "string", + "nullable": true, + "minLength": 1, + "maxLength": 80, "metadata": { - "description": "Required. The column name." + "description": "Optional. The resource name." } }, - "type": { + "virtualNetworkResourceId": { "type": "string", - "allowedValues": [ - "boolean", - "dateTime", - "dynamic", - "guid", - "int", - "long", - "real", - "string" - ], "metadata": { - "description": "Required. The column type." + "description": "Required. The resource ID of the virtual network to link." } }, - "dataTypeHint": { + "location": { "type": "string", - "allowedValues": [ - "armPath", - "guid", - "ip", - "uri" - ], "nullable": true, "metadata": { - "description": "Optional. The column data type logical hint." + "description": "Optional. The Azure Region where the resource lives." } }, - "description": { - "type": "string", + "registrationEnabled": { + "type": "bool", "nullable": true, "metadata": { - "description": "Optional. The column description." + "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?." } }, - "displayName": { - "type": "string", + "tags": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. Column display name." - } - } - }, - "metadata": { - "description": "The parameters of the table column.", - "__bicep_imported_from!": { - "sourceTemplate": "table/main.bicep" - } - } - }, - "destinationType": { - "type": "object", - "properties": { - "resourceId": { - "type": "string", - "metadata": { - "description": "Required. The destination resource ID." + "description": "Optional. Resource tags." } }, - "metaData": { - "type": "object", - "properties": { - "eventHubName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." - } - } - }, + "resolutionPolicy": { + "type": "string", + "allowedValues": [ + "Default", + "NxDomainRedirect" + ], "nullable": true, "metadata": { - "description": "Optional. The destination metadata." + "description": "Optional. The resolution type of the private-dns-zone fallback machanism." } } }, "metadata": { - "description": "The data export destination properties.", - "__bicep_imported_from!": { - "sourceTemplate": "data-export/main.bicep" - } + "__bicep_export!": true, + "description": "The type for the virtual network link." } }, "lockType": { @@ -20541,66 +21160,6 @@ } } }, - "managedIdentityAllType": { - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } - }, - "restoredLogsType": { - "type": "object", - "properties": { - "sourceTable": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The table to restore data from." - } - }, - "startRestoreTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to start the restore from (UTC)." - } - }, - "endRestoreTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to end the restore by (UTC)." - } - } - }, - "metadata": { - "description": "The parameters of the restore operation that initiated the table.", - "__bicep_imported_from!": { - "sourceTemplate": "table/main.bicep" - } - } - }, "roleAssignmentType": { "type": "object", "properties": { @@ -20675,332 +21234,110 @@ "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" } } - }, - "schemaType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The table name." - } - }, - "columns": { - "type": "array", - "items": { - "$ref": "#/definitions/_1.columnType" - }, - "metadata": { - "description": "Required. A list of table custom columns." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The table description." - } - }, - "displayName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The table display name." - } - } - }, - "metadata": { - "description": "The table schema.", - "__bicep_imported_from!": { - "sourceTemplate": "table/main.bicep" - } - } - }, - "searchResultsType": { - "type": "object", - "properties": { - "query": { - "type": "string", - "metadata": { - "description": "Required. The search job query." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The search description." - } - }, - "limit": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. Limit the search job to return up to specified number of rows." - } - }, - "startSearchTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to start the search from (UTC)." - } - }, - "endSearchTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to end the search by (UTC)." - } - } - }, - "metadata": { - "description": "The parameters of the search job that initiated the table.", - "__bicep_imported_from!": { - "sourceTemplate": "table/main.bicep" - } - } - }, - "solutionPlanType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." - } - }, - "product": { - "type": "string", - "metadata": { - "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." - } - }, - "publisher": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." - } - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/operations-management/solution:0.3.1" - } - } } }, "parameters": { "name": { "type": "string", "metadata": { - "description": "Required. Name of the Log Analytics workspace." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "skuName": { - "type": "string", - "defaultValue": "PerGB2018", - "allowedValues": [ - "CapacityReservation", - "Free", - "LACluster", - "PerGB2018", - "PerNode", - "Premium", - "Standalone", - "Standard" - ], - "metadata": { - "description": "Optional. The name of the SKU." - } - }, - "skuCapacityReservationLevel": { - "type": "int", - "defaultValue": 100, - "minValue": 100, - "maxValue": 5000, - "metadata": { - "description": "Optional. The capacity reservation level in GB for this workspace, when CapacityReservation sku is selected. Must be in increments of 100 between 100 and 5000." - } - }, - "storageInsightsConfigs": { - "type": "array", - "items": { - "$ref": "#/definitions/storageInsightsConfigType" - }, - "nullable": true, - "metadata": { - "description": "Optional. List of storage accounts to be read by the workspace." - } - }, - "linkedServices": { - "type": "array", - "items": { - "$ref": "#/definitions/linkedServiceType" - }, - "nullable": true, - "metadata": { - "description": "Optional. List of services to be linked." - } - }, - "linkedStorageAccounts": { - "type": "array", - "items": { - "$ref": "#/definitions/linkedStorageAccountType" - }, - "nullable": true, - "metadata": { - "description": "Conditional. List of Storage Accounts to be linked. Required if 'forceCmkForQuery' is set to 'true' and 'savedSearches' is not empty." + "description": "Required. Private DNS zone name." } }, - "savedSearches": { + "a": { "type": "array", "items": { - "$ref": "#/definitions/savedSearchType" + "$ref": "#/definitions/aType" }, "nullable": true, "metadata": { - "description": "Optional. Kusto Query Language searches to save." + "description": "Optional. Array of A records." } }, - "dataExports": { + "aaaa": { "type": "array", "items": { - "$ref": "#/definitions/dataExportType" + "$ref": "#/definitions/aaaaType" }, "nullable": true, "metadata": { - "description": "Optional. LAW data export instances to be deployed." + "description": "Optional. Array of AAAA records." } }, - "dataSources": { + "cname": { "type": "array", "items": { - "$ref": "#/definitions/dataSourceType" + "$ref": "#/definitions/cnameType" }, "nullable": true, "metadata": { - "description": "Optional. LAW data sources to configure." + "description": "Optional. Array of CNAME records." } }, - "tables": { + "mx": { "type": "array", "items": { - "$ref": "#/definitions/tableType" + "$ref": "#/definitions/mxType" }, "nullable": true, "metadata": { - "description": "Optional. LAW custom tables to be deployed." + "description": "Optional. Array of MX records." } }, - "gallerySolutions": { + "ptr": { "type": "array", "items": { - "$ref": "#/definitions/gallerySolutionType" + "$ref": "#/definitions/ptrType" }, "nullable": true, "metadata": { - "description": "Optional. List of gallerySolutions to be created in the log analytics workspace." - } - }, - "onboardWorkspaceToSentinel": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Onboard the Log Analytics Workspace to Sentinel. Requires 'SecurityInsights' solution to be in gallerySolutions." - } - }, - "dataRetention": { - "type": "int", - "defaultValue": 365, - "minValue": 0, - "maxValue": 730, - "metadata": { - "description": "Optional. Number of days data will be retained for." - } - }, - "dailyQuotaGb": { - "type": "int", - "defaultValue": -1, - "minValue": -1, - "metadata": { - "description": "Optional. The workspace daily quota for ingestion." - } - }, - "publicNetworkAccessForIngestion": { - "type": "string", - "defaultValue": "Enabled", - "allowedValues": [ - "Enabled", - "Disabled" - ], - "metadata": { - "description": "Optional. The network access type for accessing Log Analytics ingestion." - } - }, - "publicNetworkAccessForQuery": { - "type": "string", - "defaultValue": "Enabled", - "allowedValues": [ - "Enabled", - "Disabled" - ], - "metadata": { - "description": "Optional. The network access type for accessing Log Analytics query." + "description": "Optional. Array of PTR records." } }, - "managedIdentities": { - "$ref": "#/definitions/managedIdentityAllType", + "soa": { + "type": "array", + "items": { + "$ref": "#/definitions/soaType" + }, "nullable": true, "metadata": { - "description": "Optional. The managed identity definition for this resource. Only one type of identity is supported: system-assigned or user-assigned, but not both." + "description": "Optional. Array of SOA records." } }, - "features": { - "$ref": "#/definitions/workspaceFeaturesType", + "srv": { + "type": "array", + "items": { + "$ref": "#/definitions/srvType" + }, "nullable": true, "metadata": { - "description": "Optional. The workspace features." + "description": "Optional. Array of SRV records." } }, - "replication": { - "$ref": "#/definitions/workspaceReplicationType", + "txt": { + "type": "array", + "items": { + "$ref": "#/definitions/txtType" + }, "nullable": true, "metadata": { - "description": "Optional. The workspace replication properties." + "description": "Optional. Array of TXT records." } }, - "diagnosticSettings": { + "virtualNetworkLinks": { "type": "array", "items": { - "$ref": "#/definitions/diagnosticSettingType" + "$ref": "#/definitions/virtualNetworkLinkType" }, "nullable": true, "metadata": { - "description": "Optional. The diagnostic settings of the service." - } - }, - "forceCmkForQuery": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Indicates whether customer managed storage is mandatory for query management." + "description": "Optional. Array of custom objects describing vNet links of the DNS zone. Each object should contain properties 'virtualNetworkResourceId' and 'registrationEnabled'. The 'vnetResourceId' is a resource ID of a vNet to link, 'registrationEnabled' (bool) enables automatic DNS registration in the zone for the linked vNet." } }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, + "location": { + "type": "string", + "defaultValue": "global", "metadata": { - "description": "Optional. The lock settings of the service." + "description": "Optional. The location of the PrivateDNSZone. Should be global." } }, "roleAssignments": { @@ -21015,13 +21352,17 @@ }, "tags": { "type": "object", + "nullable": true, "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces@2025-02-01#properties/tags" - }, "description": "Optional. Tags of the resource." - }, - "nullable": true + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } }, "enableTelemetry": { "type": "bool", @@ -21039,21 +21380,13 @@ "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)))))]" } ], - "enableReferencedModulesTelemetry": false, - "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()), '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')]", - "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", - "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", - "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", - "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "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": "[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')]" + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" } }, "resources": { @@ -21061,7 +21394,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.12.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.network-privatednszone.{0}.{1}', replace('0.7.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -21077,418 +21410,80 @@ } } }, - "logAnalyticsWorkspace": { - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "privateDnsZone": { + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", "name": "[parameters('name')]", "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "properties": { - "features": { - "searchVersion": 1, - "enableLogAccessUsingOnlyResourcePermissions": "[coalesce(tryGet(parameters('features'), 'enableLogAccessUsingOnlyResourcePermissions'), false())]", - "disableLocalAuth": "[coalesce(tryGet(parameters('features'), 'disableLocalAuth'), true())]", - "enableDataExport": "[tryGet(parameters('features'), 'enableDataExport')]", - "immediatePurgeDataOn30Days": "[tryGet(parameters('features'), 'immediatePurgeDataOn30Days')]" - }, - "sku": { - "name": "[parameters('skuName')]", - "capacityReservationLevel": "[if(equals(parameters('skuName'), 'CapacityReservation'), parameters('skuCapacityReservationLevel'), null())]" - }, - "retentionInDays": "[parameters('dataRetention')]", - "workspaceCapping": { - "dailyQuotaGb": "[parameters('dailyQuotaGb')]" - }, - "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]", - "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]", - "forceCmkForQuery": "[parameters('forceCmkForQuery')]", - "replication": "[parameters('replication')]" - }, - "identity": "[variables('identity')]" - }, - "logAnalyticsWorkspace_diagnosticSettings": { - "copy": { - "name": "logAnalyticsWorkspace_diagnosticSettings", - "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" - }, - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.OperationalInsights/workspaces/{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": "[if(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'useThisWorkspace'), false()), resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), 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": [ - "logAnalyticsWorkspace" - ] - }, - "logAnalyticsWorkspace_sentinelOnboarding": { - "condition": "[and(not(empty(filter(coalesce(parameters('gallerySolutions'), createArray()), lambda('item', startsWith(lambdaVariables('item').name, 'SecurityInsights'))))), parameters('onboardWorkspaceToSentinel'))]", - "type": "Microsoft.SecurityInsights/onboardingStates", - "apiVersion": "2024-03-01", - "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", - "name": "default", - "properties": {}, - "dependsOn": [ - "logAnalyticsWorkspace" - ] + "tags": "[parameters('tags')]" }, - "logAnalyticsWorkspace_lock": { + "privateDnsZone_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.OperationalInsights/workspaces/{0}', parameters('name'))]", + "scope": "[format('Microsoft.Network/privateDnsZones/{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": [ - "logAnalyticsWorkspace" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_roleAssignments": { + "privateDnsZone_roleAssignments": { "copy": { - "name": "logAnalyticsWorkspace_roleAssignments", + "name": "privateDnsZone_roleAssignments", "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones', 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": [ - "logAnalyticsWorkspace" - ] - }, - "logAnalyticsWorkspace_storageInsightConfigs": { - "copy": { - "name": "logAnalyticsWorkspace_storageInsightConfigs", - "count": "[length(coalesce(parameters('storageInsightsConfigs'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-StorageInsightsConfig-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "logAnalyticsWorkspaceName": { - "value": "[parameters('name')]" - }, - "containers": { - "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'containers')]" - }, - "tables": { - "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'tables')]" - }, - "storageAccountResourceId": { - "value": "[coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()].storageAccountResourceId]" - } - }, - "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.36.1.42791", - "templateHash": "1306323182548882150" - }, - "name": "Log Analytics Workspace Storage Insight Configs", - "description": "This module deploys a Log Analytics Workspace Storage Insight Config." - }, - "parameters": { - "logAnalyticsWorkspaceName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "defaultValue": "[format('{0}-stinsconfig', last(split(parameters('storageAccountResourceId'), '/')))]", - "metadata": { - "description": "Optional. The name of the storage insights config." - } - }, - "storageAccountResourceId": { - "type": "string", - "metadata": { - "description": "Required. The Azure Resource Manager ID of the storage account resource." - } - }, - "containers": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The names of the blob containers that the workspace should read." - } - }, - "tables": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The names of the Azure tables that the workspace should read." - } - }, - "tags": { - "type": "object", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-02-01#properties/tags" - }, - "description": "Optional. Tags to configure in the resource." - }, - "nullable": true - } - }, - "resources": { - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", - "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" - }, - "workspace": { - "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('logAnalyticsWorkspaceName')]" - }, - "storageinsightconfig": { - "type": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", - "tags": "[parameters('tags')]", - "properties": { - "containers": "[parameters('containers')]", - "tables": "[parameters('tables')]", - "storageAccount": { - "id": "[parameters('storageAccountResourceId')]", - "key": "[listKeys('storageAccount', '2024-01-01').keys[0].value]" - } - } - } - }, - "outputs": { - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed storage insights configuration." - }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/storageInsightConfigs', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group where the storage insight configuration is deployed." - }, - "value": "[resourceGroup().name]" - }, - "name": { - "type": "string", - "metadata": { - "description": "The name of the storage insights configuration." - }, - "value": "[parameters('name')]" - } - } - } - }, - "dependsOn": [ - "logAnalyticsWorkspace" - ] - }, - "logAnalyticsWorkspace_linkedServices": { - "copy": { - "name": "logAnalyticsWorkspace_linkedServices", - "count": "[length(coalesce(parameters('linkedServices'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-LinkedService-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "logAnalyticsWorkspaceName": { - "value": "[parameters('name')]" - }, - "name": { - "value": "[coalesce(parameters('linkedServices'), createArray())[copyIndex()].name]" - }, - "resourceId": { - "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'resourceId')]" - }, - "writeAccessResourceId": { - "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'writeAccessResourceId')]" - } - }, - "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.36.1.42791", - "templateHash": "5230241501765697269" - }, - "name": "Log Analytics Workspace Linked Services", - "description": "This module deploys a Log Analytics Workspace Linked Service." - }, - "parameters": { - "logAnalyticsWorkspaceName": { - "type": "string", - "metadata": { - "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. Name of the link." - } - }, - "resourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require read access." - } - }, - "writeAccessResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require write access." - } - }, - "tags": { - "type": "object", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-02-01#properties/tags" - }, - "description": "Optional. Tags to configure in the resource." - }, - "nullable": true - } - }, - "resources": { - "workspace": { - "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('logAnalyticsWorkspaceName')]" - }, - "linkedService": { - "type": "Microsoft.OperationalInsights/workspaces/linkedServices", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", - "tags": "[parameters('tags')]", - "properties": { - "resourceId": "[parameters('resourceId')]", - "writeAccessResourceId": "[parameters('writeAccessResourceId')]" - } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed linked service." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed linked service." - }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedServices', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group where the linked service is deployed." - }, - "value": "[resourceGroup().name]" - } - } - } + "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": [ - "logAnalyticsWorkspace" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_linkedStorageAccounts": { + "privateDnsZone_A": { "copy": { - "name": "logAnalyticsWorkspace_linkedStorageAccounts", - "count": "[length(coalesce(parameters('linkedStorageAccounts'), createArray()))]" + "name": "privateDnsZone_A", + "count": "[length(coalesce(parameters('a'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-LinkedStorageAccount-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-PrivateDnsZone-ARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "logAnalyticsWorkspaceName": { + "privateDnsZoneName": { "value": "[parameters('name')]" }, "name": { - "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].name]" + "value": "[coalesce(parameters('a'), createArray())[copyIndex()].name]" }, - "storageAccountIds": { - "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].storageAccountIds]" + "aRecords": { + "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'aRecords')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'metadata')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('a'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -21498,77 +21493,211 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "10372135754202496594" + "version": "0.34.44.8038", + "templateHash": "18243374258187942664" }, - "name": "Log Analytics Workspace Linked Storage Accounts", - "description": "This module deploys a Log Analytics Workspace Linked Storage Account." + "name": "Private DNS Zone A record", + "description": "This module deploys a Private DNS Zone A record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } }, "parameters": { - "logAnalyticsWorkspaceName": { + "privateDnsZoneName": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, "name": { "type": "string", - "allowedValues": [ - "Query", - "Alerts", - "CustomLogs", - "AzureWatson" - ], "metadata": { - "description": "Required. Name of the link." + "description": "Required. The name of the A record." } }, - "storageAccountIds": { + "aRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of A records in the record set." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/roleAssignmentType" }, - "minLength": 1, + "nullable": true, "metadata": { - "description": "Required. Linked storage accounts resources Ids." + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { - "workspace": { + "privateDnsZone": { "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('logAnalyticsWorkspaceName')]" + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" }, - "linkedStorageAccount": { - "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "A": { + "type": "Microsoft.Network/privateDnsZones/A", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", "properties": { - "storageAccountIds": "[parameters('storageAccountIds')]" + "aRecords": "[parameters('aRecords')]", + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]" } + }, + "A_roleAssignments": { + "copy": { + "name": "A_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/A/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), 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": [ + "A" + ] } }, "outputs": { "name": { "type": "string", "metadata": { - "description": "The name of the deployed linked storage account." + "description": "The name of the deployed A record." }, "value": "[parameters('name')]" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the deployed linked storage account." + "description": "The resource ID of the deployed A record." }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedStorageAccounts', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + "value": "[resourceId('Microsoft.Network/privateDnsZones/A', parameters('privateDnsZoneName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The resource group where the linked storage account is deployed." + "description": "The resource group of the deployed A record." }, "value": "[resourceGroup().name]" } @@ -21576,52 +21705,40 @@ } }, "dependsOn": [ - "logAnalyticsWorkspace" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_savedSearches": { + "privateDnsZone_AAAA": { "copy": { - "name": "logAnalyticsWorkspace_savedSearches", - "count": "[length(coalesce(parameters('savedSearches'), createArray()))]" + "name": "privateDnsZone_AAAA", + "count": "[length(coalesce(parameters('aaaa'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-SavedSearch-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-PrivateDnsZone-AAAARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "logAnalyticsWorkspaceName": { + "privateDnsZoneName": { "value": "[parameters('name')]" }, "name": { - "value": "[format('{0}{1}', coalesce(parameters('savedSearches'), createArray())[copyIndex()].name, uniqueString(deployment().name))]" - }, - "etag": { - "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'etag')]" - }, - "displayName": { - "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].displayName]" - }, - "category": { - "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].category]" - }, - "query": { - "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].query]" + "value": "[coalesce(parameters('aaaa'), createArray())[copyIndex()].name]" }, - "functionAlias": { - "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionAlias')]" + "aaaaRecords": { + "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'aaaaRecords')]" }, - "functionParameters": { - "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionParameters')]" + "metadata": { + "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'metadata')]" }, - "tags": { - "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'tags')]" + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'ttl'), 3600)]" }, - "version": { - "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'version')]" + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('aaaa'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -21631,163 +21748,252 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "9015459905306126128" + "version": "0.34.44.8038", + "templateHash": "7322684246075092047" }, - "name": "Log Analytics Workspace Saved Searches", - "description": "This module deploys a Log Analytics Workspace Saved Search." + "name": "Private DNS Zone AAAA record", + "description": "This module deploys a Private DNS Zone AAAA record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } }, "parameters": { - "logAnalyticsWorkspaceName": { + "privateDnsZoneName": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, "name": { "type": "string", "metadata": { - "description": "Required. Name of the saved search." + "description": "Required. The name of the AAAA record." } }, - "displayName": { - "type": "string", + "aaaaRecords": { + "type": "array", + "nullable": true, "metadata": { - "description": "Required. Display name for the search." + "description": "Optional. The list of AAAA records in the record set." } }, - "category": { - "type": "string", + "metadata": { + "type": "object", + "nullable": true, "metadata": { - "description": "Required. Query category." + "description": "Optional. The metadata attached to the record set." } }, - "query": { - "type": "string", + "ttl": { + "type": "int", + "defaultValue": 3600, "metadata": { - "description": "Required. Kusto Query to be stored." + "description": "Optional. The TTL (time-to-live) of the records in the record set." } }, - "tags": { + "roleAssignments": { "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-02-01#properties/properties/properties/tags" - }, - "description": "Optional. Tags to configure in the resource." + "items": { + "$ref": "#/definitions/roleAssignmentType" }, - "nullable": true - }, - "functionAlias": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The function alias if query serves as a function." - } - }, - "functionParameters": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: \"param-name1:type1 = default_value1, param-name2:type2 = default_value2\". For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." - } - }, - "version": { - "type": "int", "nullable": true, "metadata": { - "description": "Optional. The version number of the query language." + "description": "Optional. Array of role assignments to create." } - }, - "etag": { - "type": "string", - "defaultValue": "*", - "metadata": { - "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." + } + }, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { - "workspace": { + "privateDnsZone": { "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('logAnalyticsWorkspaceName')]" + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" }, - "savedSearch": { - "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "AAAA": { + "type": "Microsoft.Network/privateDnsZones/AAAA", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", "properties": { - "etag": "[parameters('etag')]", - "tags": "[coalesce(parameters('tags'), createArray())]", - "displayName": "[parameters('displayName')]", - "category": "[parameters('category')]", - "query": "[parameters('query')]", - "functionAlias": "[parameters('functionAlias')]", - "functionParameters": "[parameters('functionParameters')]", - "version": "[parameters('version')]" + "aaaaRecords": "[parameters('aaaaRecords')]", + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]" } + }, + "AAAA_roleAssignments": { + "copy": { + "name": "AAAA_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/AAAA/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), 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": [ + "AAAA" + ] } }, "outputs": { - "resourceId": { + "name": { "type": "string", "metadata": { - "description": "The resource ID of the deployed saved search." + "description": "The name of the deployed AAAA record." }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + "value": "[parameters('name')]" }, - "resourceGroupName": { + "resourceId": { "type": "string", "metadata": { - "description": "The resource group where the saved search is deployed." + "description": "The resource ID of the deployed AAAA record." }, - "value": "[resourceGroup().name]" + "value": "[resourceId('Microsoft.Network/privateDnsZones/AAAA', parameters('privateDnsZoneName'), parameters('name'))]" }, - "name": { + "resourceGroupName": { "type": "string", "metadata": { - "description": "The name of the deployed saved search." + "description": "The resource group of the deployed AAAA record." }, - "value": "[parameters('name')]" + "value": "[resourceGroup().name]" } } } }, "dependsOn": [ - "logAnalyticsWorkspace", - "logAnalyticsWorkspace_linkedStorageAccounts" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_dataExports": { + "privateDnsZone_CNAME": { "copy": { - "name": "logAnalyticsWorkspace_dataExports", - "count": "[length(coalesce(parameters('dataExports'), createArray()))]" + "name": "privateDnsZone_CNAME", + "count": "[length(coalesce(parameters('cname'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-DataExport-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-PrivateDnsZone-CNAMERecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "workspaceName": { + "privateDnsZoneName": { "value": "[parameters('name')]" }, - "name": { - "value": "[coalesce(parameters('dataExports'), createArray())[copyIndex()].name]" + "name": { + "value": "[coalesce(parameters('cname'), createArray())[copyIndex()].name]" + }, + "cnameRecord": { + "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'cnameRecord')]" }, - "destination": { - "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'destination')]" + "metadata": { + "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'metadata')]" }, - "enable": { - "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'enable')]" + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'ttl'), 3600)]" }, - "tableNames": { - "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'tableNames')]" + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('cname'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -21797,122 +22003,211 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "8586520532175356447" + "version": "0.34.44.8038", + "templateHash": "5264706240021075859" }, - "name": "Log Analytics Workspace Data Exports", - "description": "This module deploys a Log Analytics Workspace Data Export." + "name": "Private DNS Zone CNAME record", + "description": "This module deploys a Private DNS Zone CNAME record." }, "definitions": { - "destinationType": { + "roleAssignmentType": { "type": "object", "properties": { - "resourceId": { + "name": { "type": "string", + "nullable": true, "metadata": { - "description": "Required. The destination resource ID." + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." } }, - "metaData": { - "type": "object", - "properties": { - "eventHubName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." - } - } - }, + "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 destination 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." } } }, "metadata": { - "__bicep_export!": true, - "description": "The data export destination properties." + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } } }, "parameters": { - "name": { + "privateDnsZoneName": { "type": "string", - "minLength": 4, - "maxLength": 63, "metadata": { - "description": "Required. The data export rule name." + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, - "workspaceName": { + "name": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + "description": "Required. The name of the CNAME record." } }, - "destination": { - "$ref": "#/definitions/destinationType", + "cnameRecord": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. Destination properties." + "description": "Optional. A CNAME record." } }, - "enable": { - "type": "bool", - "defaultValue": false, + "metadata": { + "type": "object", + "nullable": true, "metadata": { - "description": "Optional. Active when enabled." + "description": "Optional. The metadata attached to the record set." } }, - "tableNames": { + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/roleAssignmentType" }, - "minLength": 1, + "nullable": true, "metadata": { - "description": "Required. An array of tables to export, for example: ['Heartbeat', 'SecurityEvent']." + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { - "workspace": { + "privateDnsZone": { "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('workspaceName')]" + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" }, - "dataExport": { - "type": "Microsoft.OperationalInsights/workspaces/dataExports", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "CNAME": { + "type": "Microsoft.Network/privateDnsZones/CNAME", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", "properties": { - "destination": "[parameters('destination')]", - "enable": "[parameters('enable')]", - "tableNames": "[parameters('tableNames')]" + "cnameRecord": "[parameters('cnameRecord')]", + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]" } + }, + "CNAME_roleAssignments": { + "copy": { + "name": "CNAME_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/CNAME/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), 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": [ + "CNAME" + ] } }, "outputs": { "name": { "type": "string", "metadata": { - "description": "The name of the data export." + "description": "The name of the deployed CNAME record." }, "value": "[parameters('name')]" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the data export." + "description": "The resource ID of the deployed CNAME record." }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataExports', parameters('workspaceName'), parameters('name'))]" + "value": "[resourceId('Microsoft.Network/privateDnsZones/CNAME', parameters('privateDnsZoneName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The name of the resource group the data export was created in." + "description": "The resource group of the deployed CNAME record." }, "value": "[resourceGroup().name]" } @@ -21920,67 +22215,40 @@ } }, "dependsOn": [ - "logAnalyticsWorkspace" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_dataSources": { + "privateDnsZone_MX": { "copy": { - "name": "logAnalyticsWorkspace_dataSources", - "count": "[length(coalesce(parameters('dataSources'), createArray()))]" + "name": "privateDnsZone_MX", + "count": "[length(coalesce(parameters('mx'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-DataSource-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-PrivateDnsZone-MXRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "logAnalyticsWorkspaceName": { + "privateDnsZoneName": { "value": "[parameters('name')]" }, "name": { - "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].name]" - }, - "kind": { - "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].kind]" - }, - "linkedResourceId": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'linkedResourceId')]" - }, - "eventLogName": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventLogName')]" - }, - "eventTypes": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventTypes')]" - }, - "objectName": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'objectName')]" - }, - "instanceName": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'instanceName')]" - }, - "intervalSeconds": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'intervalSeconds')]" - }, - "counterName": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'counterName')]" - }, - "state": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'state')]" + "value": "[coalesce(parameters('mx'), createArray())[copyIndex()].name]" }, - "syslogName": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogName')]" + "metadata": { + "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'metadata')]" }, - "syslogSeverities": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogSeverities')]" + "mxRecords": { + "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'mxRecords')]" }, - "performanceCounters": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'performanceCounters')]" + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'ttl'), 3600)]" }, - "tags": { - "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'tags')]" + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('mx'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -21990,227 +22258,252 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "8336916453932906250" + "version": "0.34.44.8038", + "templateHash": "13758189936483275969" }, - "name": "Log Analytics Workspace Datasources", - "description": "This module deploys a Log Analytics Workspace Data Source." + "name": "Private DNS Zone MX record", + "description": "This module deploys a Private DNS Zone MX record." }, - "parameters": { - "logAnalyticsWorkspaceName": { - "type": "string", + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, "metadata": { - "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } - }, - "name": { + } + }, + "parameters": { + "privateDnsZoneName": { "type": "string", "metadata": { - "description": "Required. Name of the data source." + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, - "kind": { + "name": { "type": "string", - "defaultValue": "AzureActivityLog", - "allowedValues": [ - "AzureActivityLog", - "WindowsEvent", - "WindowsPerformanceCounter", - "IISLogs", - "LinuxSyslog", - "LinuxSyslogCollection", - "LinuxPerformanceObject", - "LinuxPerformanceCollection" - ], "metadata": { - "description": "Optional. The kind of the data source." + "description": "Required. The name of the MX record." } }, - "tags": { + "metadata": { "type": "object", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" - }, - "description": "Optional. Tags to configure in the resource." - }, - "nullable": true - }, - "linkedResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Resource ID of the resource to be linked." - } - }, - "eventLogName": { - "type": "string", "nullable": true, "metadata": { - "description": "Optional. Windows event log name to configure when kind is WindowsEvent." + "description": "Optional. The metadata attached to the record set." } }, - "eventTypes": { + "mxRecords": { "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Windows event types to configure when kind is WindowsEvent." - } - }, - "objectName": { - "type": "string", "nullable": true, "metadata": { - "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." - } - }, - "instanceName": { - "type": "string", - "defaultValue": "*", - "metadata": { - "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + "description": "Optional. The list of MX records in the record set." } }, - "intervalSeconds": { + "ttl": { "type": "int", - "defaultValue": 60, + "defaultValue": 3600, "metadata": { - "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + "description": "Optional. The TTL (time-to-live) of the records in the record set." } }, - "performanceCounters": { + "roleAssignments": { "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." - } - }, - "counterName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." - } - }, - "state": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." - } - }, - "syslogName": { - "type": "string", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, "nullable": true, "metadata": { - "description": "Optional. System log to configure when kind is LinuxSyslog." + "description": "Optional. Array of role assignments to create." } - }, - "syslogSeverities": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + }, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { - "workspace": { + "privateDnsZone": { "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('logAnalyticsWorkspaceName')]" + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" }, - "dataSource": { - "type": "Microsoft.OperationalInsights/workspaces/dataSources", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", - "kind": "[parameters('kind')]", - "tags": "[parameters('tags')]", + "MX": { + "type": "Microsoft.Network/privateDnsZones/MX", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", "properties": { - "linkedResourceId": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'AzureActivityLog')), parameters('linkedResourceId'), null())]", - "eventLogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventLogName'), null())]", - "eventTypes": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventTypes'), null())]", - "objectName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('objectName'), null())]", - "instanceName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('instanceName'), null())]", - "intervalSeconds": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('intervalSeconds'), null())]", - "counterName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsPerformanceCounter')), parameters('counterName'), null())]", - "state": "[if(and(not(empty(parameters('kind'))), or(or(equals(parameters('kind'), 'IISLogs'), equals(parameters('kind'), 'LinuxSyslogCollection')), equals(parameters('kind'), 'LinuxPerformanceCollection'))), parameters('state'), null())]", - "syslogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxSyslog')), parameters('syslogName'), null())]", - "syslogSeverities": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'LinuxSyslog'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('syslogSeverities'), null())]", - "performanceCounters": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxPerformanceObject')), parameters('performanceCounters'), null())]" + "metadata": "[parameters('metadata')]", + "mxRecords": "[parameters('mxRecords')]", + "ttl": "[parameters('ttl')]" } + }, + "MX_roleAssignments": { + "copy": { + "name": "MX_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/MX/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), 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": [ + "MX" + ] } }, "outputs": { - "resourceId": { + "name": { "type": "string", "metadata": { - "description": "The resource ID of the deployed data source." + "description": "The name of the deployed MX record." }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataSources', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + "value": "[parameters('name')]" }, - "resourceGroupName": { + "resourceId": { "type": "string", "metadata": { - "description": "The resource group where the data source is deployed." + "description": "The resource ID of the deployed MX record." }, - "value": "[resourceGroup().name]" + "value": "[resourceId('Microsoft.Network/privateDnsZones/MX', parameters('privateDnsZoneName'), parameters('name'))]" }, - "name": { + "resourceGroupName": { "type": "string", "metadata": { - "description": "The name of the deployed data source." + "description": "The resource group of the deployed MX record." }, - "value": "[parameters('name')]" + "value": "[resourceGroup().name]" } } } }, "dependsOn": [ - "logAnalyticsWorkspace" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_tables": { + "privateDnsZone_PTR": { "copy": { - "name": "logAnalyticsWorkspace_tables", - "count": "[length(coalesce(parameters('tables'), createArray()))]" + "name": "privateDnsZone_PTR", + "count": "[length(coalesce(parameters('ptr'), createArray()))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-Table-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-PrivateDnsZone-PTRRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "workspaceName": { + "privateDnsZoneName": { "value": "[parameters('name')]" }, "name": { - "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" - }, - "plan": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'plan')]" - }, - "schema": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'schema')]" - }, - "retentionInDays": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'retentionInDays')]" + "value": "[coalesce(parameters('ptr'), createArray())[copyIndex()].name]" }, - "totalRetentionInDays": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'totalRetentionInDays')]" + "metadata": { + "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'metadata')]" }, - "restoredLogs": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'restoredLogs')]" + "ptrRecords": { + "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ptrRecords')]" }, - "searchResults": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'searchResults')]" + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'ttl'), 3600)]" }, "roleAssignments": { - "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'roleAssignments')]" + "value": "[tryGet(coalesce(parameters('ptr'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -22220,182 +22513,268 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.1.42791", - "templateHash": "315390662258960765" + "version": "0.34.44.8038", + "templateHash": "11955164584650609753" }, - "name": "Log Analytics Workspace Tables", - "description": "This module deploys a Log Analytics Workspace Table." + "name": "Private DNS Zone PTR record", + "description": "This module deploys a Private DNS Zone PTR record." }, "definitions": { - "restoredLogsType": { + "roleAssignmentType": { "type": "object", "properties": { - "sourceTable": { + "name": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. The table to restore data from." + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." } }, - "startRestoreTime": { + "roleDefinitionIdOrName": { "type": "string", - "nullable": true, "metadata": { - "description": "Optional. The timestamp to start the restore from (UTC)." + "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'." } }, - "endRestoreTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to end the restore by (UTC)." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The parameters of the restore operation that initiated the table." - } - }, - "schemaType": { - "type": "object", - "properties": { - "name": { + "principalId": { "type": "string", "metadata": { - "description": "Required. The table name." - } - }, - "columns": { - "type": "array", - "items": { - "$ref": "#/definitions/columnType" - }, - "metadata": { - "description": "Required. A list of table custom columns." + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." } }, - "description": { + "principalType": { "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], "nullable": true, "metadata": { - "description": "Optional. The table description." + "description": "Optional. The principal type of the assigned principal ID." } }, - "displayName": { + "description": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. The table display name." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The table schema." - } - }, - "columnType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The column name." + "description": "Optional. The description of the role assignment." } }, - "type": { + "condition": { "type": "string", - "allowedValues": [ - "boolean", - "dateTime", - "dynamic", - "guid", - "int", - "long", - "real", - "string" - ], + "nullable": true, "metadata": { - "description": "Required. The column type." + "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\"." } }, - "dataTypeHint": { + "conditionVersion": { "type": "string", "allowedValues": [ - "armPath", - "guid", - "ip", - "uri" + "2.0" ], "nullable": true, "metadata": { - "description": "Optional. The column data type logical hint." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The column description." + "description": "Optional. Version of the condition." } }, - "displayName": { + "delegatedManagedIdentityResourceId": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. Column display name." + "description": "Optional. The Resource Id of the delegated managed identity resource." } } }, "metadata": { - "__bicep_export!": true, - "description": "The parameters of the table column." + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, - "searchResultsType": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the PTR record." + } + }, + "metadata": { "type": "object", - "properties": { - "query": { - "type": "string", - "metadata": { - "description": "Required. The search job query." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The search description." - } - }, - "limit": { - "type": "int", - "nullable": true, - "metadata": { - "description": "Optional. Limit the search job to return up to specified number of rows." - } - }, - "startSearchTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to start the search from (UTC)." - } - }, - "endSearchTime": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The timestamp to end the search by (UTC)." - } - } + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ptrRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of PTR records in the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" }, + "nullable": true, "metadata": { - "__bicep_export!": true, - "description": "The parameters of the search job that initiated the table." + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "PTR": { + "type": "Microsoft.Network/privateDnsZones/PTR", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "ptrRecords": "[parameters('ptrRecords')]", + "ttl": "[parameters('ttl')]" } }, + "PTR_roleAssignments": { + "copy": { + "name": "PTR_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/PTR/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), 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": [ + "PTR" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed PTR record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed PTR record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/PTR', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed PTR record." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateDnsZone" + ] + }, + "privateDnsZone_SOA": { + "copy": { + "name": "privateDnsZone_SOA", + "count": "[length(coalesce(parameters('soa'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-SOARecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('soa'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'metadata')]" + }, + "soaRecord": { + "value": "[tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'soaRecord')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('soa'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('soa'), 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.34.44.8038", + "templateHash": "14626715835033259725" + }, + "name": "Private DNS Zone SOA record", + "description": "This module deploys a Private DNS Zone SOA record." + }, + "definitions": { "roleAssignmentType": { "type": "object", "properties": { @@ -22473,66 +22852,37 @@ } }, "parameters": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the table." - } - }, - "workspaceName": { + "privateDnsZoneName": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, - "plan": { + "name": { "type": "string", - "defaultValue": "Analytics", - "allowedValues": [ - "Basic", - "Analytics" - ], - "metadata": { - "description": "Optional. Instruct the system how to handle and charge the logs ingested to this table." - } - }, - "restoredLogs": { - "$ref": "#/definitions/restoredLogsType", - "nullable": true, - "metadata": { - "description": "Optional. Restore parameters." - } - }, - "retentionInDays": { - "type": "int", - "defaultValue": -1, - "minValue": -1, - "maxValue": 730, "metadata": { - "description": "Optional. The table retention in days, between 4 and 730. Setting this property to -1 will default to the workspace retention." + "description": "Required. The name of the SOA record." } }, - "schema": { - "$ref": "#/definitions/schemaType", + "metadata": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. Table's schema." + "description": "Optional. The metadata attached to the record set." } }, - "searchResults": { - "$ref": "#/definitions/searchResultsType", + "soaRecord": { + "type": "object", "nullable": true, "metadata": { - "description": "Optional. Parameters of the search job that initiated this table." + "description": "Optional. A SOA record." } }, - "totalRetentionInDays": { + "ttl": { "type": "int", - "defaultValue": -1, - "minValue": -1, - "maxValue": 2555, + "defaultValue": 3600, "metadata": { - "description": "Optional. The table total retention in days, between 4 and 2555. Setting this property to -1 will default to table retention." + "description": "Optional. The TTL (time-to-live) of the records in the record set." } }, "roleAssignments": { @@ -22556,10 +22906,8 @@ ], "builtInRoleNames": { "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", - "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", - "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", - "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", - "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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')]", @@ -22567,34 +22915,31 @@ } }, "resources": { - "workspace": { + "privateDnsZone": { "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", - "name": "[parameters('workspaceName')]" + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" }, - "table": { - "type": "Microsoft.OperationalInsights/workspaces/tables", - "apiVersion": "2025-02-01", - "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "SOA": { + "type": "Microsoft.Network/privateDnsZones/SOA", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", "properties": { - "plan": "[parameters('plan')]", - "restoredLogs": "[parameters('restoredLogs')]", - "retentionInDays": "[parameters('retentionInDays')]", - "schema": "[parameters('schema')]", - "searchResults": "[parameters('searchResults')]", - "totalRetentionInDays": "[parameters('totalRetentionInDays')]" + "metadata": "[parameters('metadata')]", + "soaRecord": "[parameters('soaRecord')]", + "ttl": "[parameters('ttl')]" } }, - "table_roleAssignments": { + "SOA_roleAssignments": { "copy": { - "name": "table_roleAssignments", + "name": "SOA_roleAssignments", "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}/tables/{1}', parameters('workspaceName'), parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SOA/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), 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]", @@ -22605,7 +22950,7 @@ "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" }, "dependsOn": [ - "table" + "SOA" ] } }, @@ -22613,21 +22958,21 @@ "name": { "type": "string", "metadata": { - "description": "The name of the table." + "description": "The name of the deployed SOA record." }, "value": "[parameters('name')]" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the table." + "description": "The resource ID of the deployed SOA record." }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name'))]" + "value": "[resourceId('Microsoft.Network/privateDnsZones/SOA', parameters('privateDnsZoneName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The name of the resource group the table was created in." + "description": "The resource group of the deployed SOA record." }, "value": "[resourceGroup().name]" } @@ -22635,38 +22980,40 @@ } }, "dependsOn": [ - "logAnalyticsWorkspace" + "privateDnsZone" ] }, - "logAnalyticsWorkspace_solutions": { + "privateDnsZone_SRV": { "copy": { - "name": "logAnalyticsWorkspace_solutions", - "count": "[length(coalesce(parameters('gallerySolutions'), createArray()))]" + "name": "privateDnsZone_SRV", + "count": "[length(coalesce(parameters('srv'), createArray()))]" }, - "condition": "[not(empty(parameters('gallerySolutions')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('{0}-LAW-Solution-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "name": "[format('{0}-PrivateDnsZone-SRVRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, "name": { - "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].name]" + "value": "[coalesce(parameters('srv'), createArray())[copyIndex()].name]" }, - "location": { - "value": "[parameters('location')]" + "metadata": { + "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'metadata')]" }, - "logAnalyticsWorkspaceName": { - "value": "[parameters('name')]" + "srvRecords": { + "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'srvRecords')]" }, - "plan": { - "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].plan]" + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'ttl'), 3600)]" }, - "enableTelemetry": { - "value": "[variables('enableReferencedModulesTelemetry')]" + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('srv'), createArray())[copyIndex()], 'roleAssignments')]" } }, "template": { @@ -22676,948 +23023,667 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.32.4.45862", - "templateHash": "10255889523646649592" + "version": "0.34.44.8038", + "templateHash": "6510442308165042737" }, - "name": "Operations Management Solutions", - "description": "This module deploys an Operations Management Solution.", - "owner": "Azure/module-maintainers" + "name": "Private DNS Zone SRV record", + "description": "This module deploys a Private DNS Zone SRV record." }, "definitions": { - "solutionPlanType": { + "roleAssignmentType": { "type": "object", "properties": { "name": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." } }, - "product": { + "roleDefinitionIdOrName": { "type": "string", "metadata": { - "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." + "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'." } }, - "publisher": { + "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 publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + "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." } } }, "metadata": { - "__bicep_export!": true + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } } } }, "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, "name": { "type": "string", "metadata": { - "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + "description": "Required. The name of the SRV record." } }, - "plan": { - "$ref": "#/definitions/solutionPlanType", + "metadata": { + "type": "object", + "nullable": true, "metadata": { - "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." + "description": "Optional. The metadata attached to the record set." } }, - "logAnalyticsWorkspaceName": { - "type": "string", + "srvRecords": { + "type": "array", + "nullable": true, "metadata": { - "description": "Required. Name of the Log Analytics workspace where the solution will be deployed/enabled." + "description": "Optional. The list of SRV records in the record set." } }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", + "ttl": { + "type": "int", + "defaultValue": 3600, "metadata": { - "description": "Optional. Location for all resources." + "description": "Optional. The TTL (time-to-live) of the records in the record set." } }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." + "description": "Optional. Array of role assignments to create." } } }, - "resources": { - "avmTelemetry": { - "condition": "[parameters('enableTelemetry')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.operationsmanagement-solution.{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" - } - } - } + "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)))))]" } - }, - "logAnalyticsWorkspace": { + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { "existing": true, - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2021-06-01", - "name": "[parameters('logAnalyticsWorkspaceName')]" + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" }, - "solution": { - "type": "Microsoft.OperationsManagement/solutions", - "apiVersion": "2015-11-01-preview", - "name": "[parameters('name')]", - "location": "[parameters('location')]", + "SRV": { + "type": "Microsoft.Network/privateDnsZones/SRV", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", "properties": { - "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]" - }, - "plan": { - "name": "[coalesce(tryGet(parameters('plan'), 'name'), parameters('name'))]", - "promotionCode": "", - "product": "[parameters('plan').product]", - "publisher": "[coalesce(tryGet(parameters('plan'), 'publisher'), 'Microsoft')]" + "metadata": "[parameters('metadata')]", + "srvRecords": "[parameters('srvRecords')]", + "ttl": "[parameters('ttl')]" } - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed solution." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed solution." - }, - "value": "[resourceId('Microsoft.OperationsManagement/solutions', parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group where the solution is deployed." - }, - "value": "[resourceGroup().name]" }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('solution', '2015-11-01-preview', 'full').location]" - } - } - } - }, - "dependsOn": [ - "logAnalyticsWorkspace" - ] - } - }, - "outputs": { - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployed log analytics workspace." - }, - "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group of the deployed log analytics workspace." - }, - "value": "[resourceGroup().name]" - }, - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployed log analytics workspace." - }, - "value": "[parameters('name')]" - }, - "logAnalyticsWorkspaceId": { - "type": "string", - "metadata": { - "description": "The ID associated with the workspace." - }, - "value": "[reference('logAnalyticsWorkspace').customerId]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('logAnalyticsWorkspace', '2025-02-01', 'full').location]" - }, - "systemAssignedMIPrincipalId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The principal ID of the system assigned identity." - }, - "value": "[tryGet(tryGet(reference('logAnalyticsWorkspace', '2025-02-01', 'full'), 'identity'), 'principalId')]" - }, - "primarySharedKey": { - "type": "securestring", - "metadata": { - "description": "The primary shared key of the log analytics workspace." - }, - "value": "[listKeys('logAnalyticsWorkspace', '2025-02-01').primarySharedKey]" - }, - "secondarySharedKey": { - "type": "securestring", - "metadata": { - "description": "The secondary shared key of the log analytics workspace." - }, - "value": "[listKeys('logAnalyticsWorkspace', '2025-02-01').secondarySharedKey]" - } - } - } - } - }, - "applicationInsights": { - "condition": "[parameters('enableMonitoring')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[take(format('avm.res.insights.component.{0}', variables('applicationInsightsResourceName')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[variables('applicationInsightsResourceName')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "location": { - "value": "[variables('solutionLocation')]" - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - }, - "retentionInDays": { - "value": 365 - }, - "kind": { - "value": "web" - }, - "disableIpMasking": { - "value": false - }, - "flowType": { - "value": "Bluefield" - }, - "workspaceResourceId": "[if(parameters('enableMonitoring'), if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value)), createObject('value', ''))]", - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]" - }, - "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.33.93.31351", - "templateHash": "5735496719243704506" - }, - "name": "Application Insights", - "description": "This component deploys an Application Insights instance." - }, - "definitions": { - "diagnosticSettingFullType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the 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." - } + "SRV_roleAssignments": { + "copy": { + "name": "SRV_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" }, - "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." - } + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/SRV/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), 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')]" }, - "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." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.3.0" - } - } - }, - "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." + "dependsOn": [ + "SRV" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed SRV record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed SRV record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/SRV', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed SRV record." + }, + "value": "[resourceGroup().name]" + } } } }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" - } - } + "dependsOn": [ + "privateDnsZone" + ] }, - "roleAssignmentType": { - "type": "object", + "privateDnsZone_TXT": { + "copy": { + "name": "privateDnsZone_TXT", + "count": "[length(coalesce(parameters('txt'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-PrivateDnsZone-TXTRecord-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "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\"." - } + "expressionEvaluationOptions": { + "scope": "inner" }, - "conditionVersion": { - "type": "string", - "allowedValues": [ - "2.0" - ], - "nullable": true, + "mode": "Incremental", + "parameters": { + "privateDnsZoneName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('txt'), createArray())[copyIndex()].name]" + }, "metadata": { - "description": "Optional. Version of the condition." + "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'metadata')]" + }, + "txtRecords": { + "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'txtRecords')]" + }, + "ttl": { + "value": "[coalesce(tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'ttl'), 3600)]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('txt'), createArray())[copyIndex()], 'roleAssignments')]" } }, - "delegatedManagedIdentityResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.3.0" - } - } - } - }, - "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", - "nullable": true, - "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." - } - }, - "flowType": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Used by the Application Insights system to determine what kind of flow this component was created by. This is to be set to 'Bluefield' when creating/updating a component via the REST API." - } - }, - "requestSource": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Describes what tool created this Application Insights component. Customers using this API should set this to the default 'rest'." - } - }, - "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." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "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": { - "type": "array", - "items": { - "$ref": "#/definitions/diagnosticSettingFullType" - }, - "nullable": true, - "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.6.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#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", - "resources": [], + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.34.44.8038", + "templateHash": "170623042781622569" + }, + "name": "Private DNS Zone TXT record", + "description": "This module deploys a Private DNS Zone TXT record." + }, + "definitions": { + "roleAssignmentType": { + "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." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "privateDnsZoneName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the TXT record." + } + }, + "metadata": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The metadata attached to the record set." + } + }, + "ttl": { + "type": "int", + "defaultValue": 3600, + "metadata": { + "description": "Optional. The TTL (time-to-live) of the records in the record set." + } + }, + "txtRecords": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The list of TXT records in the record set." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "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')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "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": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "TXT": { + "type": "Microsoft.Network/privateDnsZones/TXT", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]", + "ttl": "[parameters('ttl')]", + "txtRecords": "[parameters('txtRecords')]" + } + }, + "TXT_roleAssignments": { + "copy": { + "name": "TXT_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateDnsZones/{0}/TXT/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), 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": [ + "TXT" + ] + } + }, "outputs": { - "telemetry": { - "type": "String", - "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed TXT record." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed TXT record." + }, + "value": "[resourceId('Microsoft.Network/privateDnsZones/TXT', parameters('privateDnsZoneName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed TXT record." + }, + "value": "[resourceGroup().name]" } } } - } - }, - "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')]", - "Flow_Type": "[parameters('flowType')]", - "Request_Source": "[parameters('requestSource')]" - } - }, - "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_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.Insights/components/{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": [ - "appInsights" + "privateDnsZone" ] }, - "appInsights_diagnosticSettings": { + "privateDnsZone_virtualNetworkLinks": { "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')]" + "name": "privateDnsZone_virtualNetworkLinks", + "count": "[length(coalesce(parameters('virtualNetworkLinks'), createArray()))]" }, - "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')))]", + "name": "[format('{0}-PrivateDnsZone-VNetLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "appInsightsName": { + "privateDnsZoneName": { "value": "[parameters('name')]" }, - "storageAccountResourceId": { - "value": "[coalesce(parameters('linkedStorageAccountResourceId'), '')]" + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'name'), format('{0}-vnetlink', last(split(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId, '/'))))]" + }, + "virtualNetworkResourceId": { + "value": "[coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()].virtualNetworkResourceId]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'location'), 'global')]" + }, + "registrationEnabled": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'registrationEnabled'), false())]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "resolutionPolicy": { + "value": "[tryGet(coalesce(parameters('virtualNetworkLinks'), createArray())[copyIndex()], 'resolutionPolicy')]" } }, "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.33.93.31351", - "templateHash": "10861379689695100897" + "version": "0.34.44.8038", + "templateHash": "725891200086243555" }, - "name": "Application Insights Linked Storage Account", - "description": "This component deploys an Application Insights Linked Storage Account." + "name": "Private DNS Zone Virtual Network Link", + "description": "This module deploys a Private DNS Zone Virtual Network Link." }, "parameters": { - "appInsightsName": { + "privateDnsZoneName": { "type": "string", "metadata": { - "description": "Conditional. The name of the parent Application Insights instance. Required if the template is used in a standalone deployment." + "description": "Conditional. The name of the parent Private DNS zone. Required if the template is used in a standalone deployment." } }, - "storageAccountResourceId": { + "name": { "type": "string", + "defaultValue": "[format('{0}-vnetlink', last(split(parameters('virtualNetworkResourceId'), '/')))]", "metadata": { - "description": "Required. Linked storage account resource ID." + "description": "Optional. The name of the virtual network link." + } + }, + "location": { + "type": "string", + "defaultValue": "global", + "metadata": { + "description": "Optional. The location of the PrivateDNSZone. Should be global." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "registrationEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Is auto-registration of virtual machine records in the virtual network in the Private DNS zone enabled?." + } + }, + "virtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "Required. Link to another virtual network resource ID." + } + }, + "resolutionPolicy": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resolution policy on the virtual network link. Only applicable for virtual network links to privatelink zones, and for A,AAAA,CNAME queries. When set to `NxDomainRedirect`, Azure DNS resolver falls back to public resolution if private dns query resolution results in non-existent domain response. `Default` is configured as the default option." } } }, - "resources": [ - { - "type": "microsoft.insights/components/linkedStorageAccounts", - "apiVersion": "2020-03-01-preview", - "name": "[format('{0}/{1}', parameters('appInsightsName'), 'ServiceProfiler')]", + "resources": { + "privateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[parameters('privateDnsZoneName')]" + }, + "virtualNetworkLink": { + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", "properties": { - "linkedStorageAccount": "[parameters('storageAccountResourceId')]" + "registrationEnabled": "[parameters('registrationEnabled')]", + "virtualNetwork": { + "id": "[parameters('virtualNetworkResourceId')]" + }, + "resolutionPolicy": "[parameters('resolutionPolicy')]" } } - ], + }, "outputs": { "name": { "type": "string", "metadata": { - "description": "The name of the Linked Storage Account." + "description": "The name of the deployed virtual network link." }, - "value": "ServiceProfiler" + "value": "[parameters('name')]" }, "resourceId": { "type": "string", "metadata": { - "description": "The resource ID of the Linked Storage Account." + "description": "The resource ID of the deployed virtual network link." }, - "value": "[resourceId('microsoft.insights/components/linkedStorageAccounts', parameters('appInsightsName'), 'ServiceProfiler')]" + "value": "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', parameters('privateDnsZoneName'), parameters('name'))]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The resource group the agent pool was deployed into." + "description": "The resource group of the deployed virtual network link." }, "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('virtualNetworkLink', '2024-06-01', 'full').location]" } } } }, "dependsOn": [ - "appInsights" + "privateDnsZone" ] } }, "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." + "description": "The resource group the private DNS zone was deployed into." }, "value": "[resourceGroup().name]" }, - "applicationId": { - "type": "string", - "metadata": { - "description": "The application ID of the application insights component." - }, - "value": "[reference('appInsights').AppId]" - }, - "location": { + "name": { "type": "string", "metadata": { - "description": "The location the resource was deployed into." + "description": "The name of the private DNS zone." }, - "value": "[reference('appInsights', '2020-02-02', 'full').location]" + "value": "[parameters('name')]" }, - "instrumentationKey": { + "resourceId": { "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." + "description": "The resource ID of the private DNS zone." }, - "value": "[reference('appInsights').InstrumentationKey]" + "value": "[resourceId('Microsoft.Network/privateDnsZones', parameters('name'))]" }, - "connectionString": { + "location": { "type": "string", "metadata": { - "description": "Application Insights Connection String." + "description": "The location the resource was deployed into." }, - "value": "[reference('appInsights').ConnectionString]" + "value": "[reference('privateDnsZone', '2020-06-01', 'full').location]" } } } }, "dependsOn": [ - "logAnalyticsWorkspace" + "network" ] }, "keyvault": { @@ -23640,7 +23706,7 @@ "value": "[parameters('tags')]" }, "sku": { - "value": "premium" + "value": "standard" }, "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", "networkAcls": { @@ -23663,10 +23729,13 @@ "enableSoftDelete": { "value": true }, + "enablePurgeProtection": { + "value": "[parameters('enablePurgeProtection')]" + }, "softDeleteRetentionInDays": { "value": 7 }, - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', createArray()))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', createArray()))]", "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('keyVaultName')), 'customNetworkInterfaceName', format('nic-{0}', variables('keyVaultName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').keyVault)).outputs.resourceId.value))), 'service', 'vault', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]", "roleAssignments": { "value": [ @@ -23686,8 +23755,31 @@ { "name": "SQLDB-DATABASE", "value": "[variables('sqlDbName')]" + }, + { + "name": "AZURE-OPENAI-PREVIEW-API-VERSION", + "value": "[parameters('azureOpenaiAPIVersion')]" + }, + { + "name": "AZURE-OPENAI-ENDPOINT", + "value": "[reference('aiFoundryAiServices').outputs.endpoints.value['OpenAI Language Model Instance API']]" + }, + { + "name": "AZURE-OPENAI-EMBEDDING-MODEL", + "value": "[parameters('embeddingModel')]" + }, + { + "name": "AZURE-SEARCH-INDEX", + "value": "[variables('azureSearchIndex')]" + }, + { + "name": "AZURE-SEARCH-ENDPOINT", + "value": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]" } ] + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" } }, "template": { @@ -26808,6 +26900,7 @@ } }, "dependsOn": [ + "aiFoundryAiServices", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').keyVault)]", "logAnalyticsWorkspace", "network", @@ -26933,7 +27026,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "14489342617241779499" + "templateHash": "7757141333856577130" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service." @@ -27312,6 +27405,12 @@ "metadata": { "description": "Required. API endpoint for the AI project." } + }, + "aiprojectSystemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. System Assigned Managed Identity Principal Id of the AI project." + } } }, "metadata": { @@ -28160,7 +28259,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "15000514671917656766" + "templateHash": "13864482829550647329" } }, "definitions": { @@ -28537,6 +28636,12 @@ "metadata": { "description": "Required. API endpoint for the AI project." } + }, + "aiprojectSystemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. System Assigned Managed Identity Principal Id of the AI project." + } } }, "metadata": { @@ -30123,7 +30228,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "8500501911204164532" + "templateHash": "7781450680156271399" } }, "definitions": { @@ -30147,6 +30252,12 @@ "metadata": { "description": "Required. API endpoint for the AI project." } + }, + "aiprojectSystemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. System Assigned Managed Identity Principal Id of the AI project." + } } }, "metadata": { @@ -30200,7 +30311,10 @@ "variables": { "useExistingProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", "existingProjName": "[if(variables('useExistingProject'), last(split(parameters('existingFoundryProjectResourceId'), '/')), '')]", - "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('aiServicesName'), variables('existingProjName')), '')]" + "existingAiFoundryAiServicesSubscriptionId": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], '')]", + "existingAiFoundryAiServicesResourceGroupName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], '')]", + "existingAiFoundryAiServicesServiceName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], '')]", + "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName')), '')]" }, "resources": { "cogServiceReference": { @@ -30223,6 +30337,15 @@ "description": "[parameters('desc')]", "displayName": "[parameters('name')]" } + }, + "existingAiProject": { + "condition": "[variables('useExistingProject')]", + "existing": true, + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-06-01", + "subscriptionId": "[variables('existingAiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('existingAiFoundryAiServicesResourceGroupName')]", + "name": "[format('{0}/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName'))]" } }, "outputs": { @@ -30234,7 +30357,8 @@ "value": { "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", "resourceId": "[if(variables('useExistingProject'), parameters('existingFoundryProjectResourceId'), resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')))]", - "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]" + "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]", + "aiprojectSystemAssignedMIPrincipalId": "[if(variables('useExistingProject'), reference('existingAiProject', '2025-06-01', 'full').identity.principalId, reference('aiProject', '2025-06-01', 'full').identity.principalId)]" } } } @@ -30341,7 +30465,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "15000514671917656766" + "templateHash": "13864482829550647329" } }, "definitions": { @@ -30718,6 +30842,12 @@ "metadata": { "description": "Required. API endpoint for the AI project." } + }, + "aiprojectSystemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. System Assigned Managed Identity Principal Id of the AI project." + } } }, "metadata": { @@ -32304,7 +32434,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "8500501911204164532" + "templateHash": "7781450680156271399" } }, "definitions": { @@ -32328,6 +32458,12 @@ "metadata": { "description": "Required. API endpoint for the AI project." } + }, + "aiprojectSystemAssignedMIPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. System Assigned Managed Identity Principal Id of the AI project." + } } }, "metadata": { @@ -32381,7 +32517,10 @@ "variables": { "useExistingProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", "existingProjName": "[if(variables('useExistingProject'), last(split(parameters('existingFoundryProjectResourceId'), '/')), '')]", - "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('aiServicesName'), variables('existingProjName')), '')]" + "existingAiFoundryAiServicesSubscriptionId": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], '')]", + "existingAiFoundryAiServicesResourceGroupName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], '')]", + "existingAiFoundryAiServicesServiceName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], '')]", + "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName')), '')]" }, "resources": { "cogServiceReference": { @@ -32404,6 +32543,15 @@ "description": "[parameters('desc')]", "displayName": "[parameters('name')]" } + }, + "existingAiProject": { + "condition": "[variables('useExistingProject')]", + "existing": true, + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-06-01", + "subscriptionId": "[variables('existingAiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('existingAiFoundryAiServicesResourceGroupName')]", + "name": "[format('{0}/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName'))]" } }, "outputs": { @@ -32415,7 +32563,8 @@ "value": { "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", "resourceId": "[if(variables('useExistingProject'), parameters('existingFoundryProjectResourceId'), resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')))]", - "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]" + "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]", + "aiprojectSystemAssignedMIPrincipalId": "[if(variables('useExistingProject'), reference('existingAiProject', '2025-06-01', 'full').identity.principalId, reference('aiProject', '2025-06-01', 'full').identity.principalId)]" } } } @@ -32543,8 +32692,8 @@ }, "dependsOn": [ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "logAnalyticsWorkspace", "network", "userAssignedIdentity" @@ -32565,7 +32714,7 @@ "value": "[variables('cosmosDbResourceName')]" }, "location": { - "value": "[variables('solutionLocation')]" + "value": "[parameters('cosmosLocation')]" }, "tags": { "value": "[parameters('tags')]" @@ -32605,7 +32754,7 @@ } ] }, - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", "networkRestrictions": { "value": { "networkAclBypass": "None", @@ -32616,7 +32765,7 @@ "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]", "capabilitiesToAdd": "[if(parameters('enableRedundancy'), createObject('value', null()), createObject('value', createArray('EnableServerless')))]", "automaticFailover": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]", - "failoverLocations": "[if(parameters('enableRedundancy'), createObject('value', createArray(createObject('failoverPriority', 0, 'isZoneRedundant', true(), 'locationName', variables('solutionLocation')), createObject('failoverPriority', 1, 'isZoneRedundant', true(), 'locationName', variables('cosmosDbHaLocation')))), createObject('value', createArray(createObject('locationName', variables('solutionLocation'), 'failoverPriority', 0))))]" + "failoverLocations": "[if(parameters('enableRedundancy'), createObject('value', createArray(createObject('failoverPriority', 0, 'isZoneRedundant', true(), 'locationName', variables('solutionLocation')), createObject('failoverPriority', 1, 'isZoneRedundant', true(), 'locationName', variables('cosmosDbHaLocation')))), createObject('value', createArray(createObject('locationName', variables('solutionLocation'), 'failoverPriority', 0, 'isZoneRedundant', parameters('enableRedundancy')))))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -36453,7 +36602,9 @@ "containers": [ { "name": "data", - "publicAccess": "None" + "publicAccess": "None", + "denyEncryptionScopeOverride": false, + "defaultEncryptionScope": "$account-encryption-key" } ] } @@ -45333,7 +45484,7 @@ "sqlDBModule": { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "serverDeployment", + "name": "[take(format('avm.res.sql.server.{0}', variables('sqlDbName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -45366,7 +45517,7 @@ "retentionDays": 14 }, "collation": "SQL_Latin1_General_CP1_CI_AS", - "diagnosticSettings": "[if(parameters('enableMonitoring'), createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value)), null())]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value))), null())]", "elasticPoolResourceId": "[resourceId('Microsoft.Sql/servers/elasticPools', format('sql-{0}', variables('solutionSuffix')), 'sqlswaf-ep-001')]", "licenseType": "LicenseIncluded", "maxSizeBytes": 34359738368, @@ -52624,6 +52775,13 @@ "location": { "value": "[variables('solutionLocation')]" }, + "managedIdentities": { + "value": { + "userAssignedResourceIds": [ + "[reference('userAssignedIdentity').outputs.resourceId.value]" + ] + } + }, "kind": { "value": "app,linux,container" }, @@ -52656,7 +52814,7 @@ "AZURE_SEARCH_URL_COLUMN": "[variables('azureSearchUrlColumn')]", "AZURE_OPENAI_RESOURCE": "[reference('aiFoundryAiServices').outputs.name.value]", "AZURE_OPENAI_MODEL": "[parameters('gptModelName')]", - "AZURE_OPENAI_ENDPOINT": "[reference('aiFoundryAiServices').outputs.endpoint.value]", + "AZURE_OPENAI_ENDPOINT": "[reference('aiFoundryAiServices').outputs.endpoints.value['OpenAI Language Model Instance API']]", "AZURE_OPENAI_TEMPERATURE": "[variables('azureOpenAITemperature')]", "AZURE_OPENAI_TOP_P": "[variables('azureOpenAITopP')]", "AZURE_OPENAI_MAX_TOKENS": "[variables('azureOpenAIMaxTokens')]", @@ -52669,7 +52827,7 @@ "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN": "[variables('azureSearchPermittedGroupsField')]", "AZURE_SEARCH_STRICTNESS": "[variables('azureSearchStrictness')]", "AZURE_OPENAI_EMBEDDING_NAME": "[parameters('embeddingModel')]", - "AZURE_OPENAI_EMBEDDING_ENDPOINT": "[reference('aiFoundryAiServices').outputs.endpoint.value]", + "AZURE_OPENAI_EMBEDDING_ENDPOINT": "[reference('aiFoundryAiServices').outputs.endpoints.value['OpenAI Language Model Instance API']]", "SQLDB_SERVER": "[variables('sqlServerFqdn')]", "SQLDB_DATABASE": "[variables('sqlDbName')]", "USE_INTERNAL_STREAM": "[variables('useInternalStream')]", @@ -52678,7 +52836,7 @@ "AZURE_COSMOSDB_DATABASE": "[variables('cosmosDbDatabaseName')]", "AZURE_COSMOSDB_ENABLE_FEEDBACK": "[variables('azureCosmosDbEnableFeedback')]", "SQLDB_USER_MID": "[reference('userAssignedIdentity').outputs.clientId.value]", - "AZURE_AI_SEARCH_ENDPOINT": "[reference('searchService').outputs.endpoint.value]", + "AZURE_AI_SEARCH_ENDPOINT": "[format('https://{0}.search.windows.net', variables('aiSearchName'))]", "AZURE_SQL_SYSTEM_PROMPT": "[variables('functionAppSqlPrompt')]", "AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT": "[variables('functionAppCallTranscriptSystemPrompt')]", "AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT": "[variables('functionAppStreamTextSystemPrompt')]", @@ -52686,7 +52844,8 @@ "AZURE_AI_AGENT_ENDPOINT": "[reference('aiFoundryAiServices').outputs.aiProjectInfo.value.apiEndpoint]", "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME": "[parameters('gptModelName')]", "AZURE_AI_AGENT_API_VERSION": "[parameters('azureOpenaiAPIVersion')]", - "AZURE_SEARCH_CONNECTION_NAME": "" + "AZURE_SEARCH_CONNECTION_NAME": "[variables('aiSearchName')]", + "AZURE_CLIENT_ID": "[reference('userAssignedIdentity').outputs.clientId.value]" }, "applicationInsightResourceId": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.resourceId.value, null())]" } @@ -52697,7 +52856,7 @@ "vnetImagePullEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", "virtualNetworkSubnetId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('network').outputs.subnetWebResourceId.value), createObject('value', null()))]", "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", - "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('webSiteResourceName')), 'customNetworkInterfaceName', format('nic-{0}', variables('webSiteResourceName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').appService)).outputs.resourceId.value))), 'service', 'sites', 'subnetResourceId', reference('network').outputs.subnetWebResourceId.value))), createObject('value', null()))]" + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('webSiteResourceName')), 'customNetworkInterfaceName', format('nic-{0}', variables('webSiteResourceName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').appService)).outputs.resourceId.value))), 'service', 'sites', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', null()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -54685,7 +54844,6 @@ "cosmosDb", "logAnalyticsWorkspace", "network", - "searchService", "userAssignedIdentity", "webServerFarm" ] @@ -54693,7 +54851,7 @@ "searchService": { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "searchServiceDeployment", + "name": "[take(format('avm.res.search.search-service.{0}', variables('aiSearchName')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -54710,10 +54868,7 @@ } } }, - "cmkEnforcement": { - "value": "Enabled" - }, - "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value))), createObject('value', null()))]", + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", "disableLocalAuth": { "value": false }, @@ -54728,28 +54883,52 @@ "networkRuleSet": { "value": { "bypass": "AzureServices", - "ipRules": [ - { - "value": "40.74.28.0/23" - }, - { - "value": "87.147.204.13" - } - ] + "ipRules": [] } }, + "roleAssignments": { + "value": [ + { + "roleDefinitionIdOrName": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "principalId": "[if(not(variables('useExistingAiFoundryAiProject')), reference('aiFoundryAiServices').outputs.aiProjectInfo.value.aiprojectSystemAssignedMIPrincipalId, reference('existingAiFoundryAiServicesProject', '2025-04-01-preview', 'full').identity.principalId)]", + "principalType": "ServicePrincipal" + }, + { + "roleDefinitionIdOrName": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "principalId": "[if(not(variables('useExistingAiFoundryAiProject')), reference('aiFoundryAiServices').outputs.aiProjectInfo.value.aiprojectSystemAssignedMIPrincipalId, reference('existingAiFoundryAiServicesProject', '2025-04-01-preview', 'full').identity.principalId)]", + "principalType": "ServicePrincipal" + } + ] + }, "partitionCount": { - "value": 2 + "value": 1 }, "replicaCount": { - "value": 3 + "value": 1 }, "sku": { - "value": "basic" + "value": "standard" + }, + "semanticSearch": { + "value": "free" }, "tags": { "value": "[parameters('tags')]" - } + }, + "publicNetworkAccess": { + "value": "Enabled" + }, + "privateEndpoints": "[if(false(), createObject('value', createArray(createObject('name', format('pep-{0}', variables('aiSearchName')), 'customNetworkInterfaceName', format('nic-{0}', variables('aiSearchName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').searchService)).outputs.resourceId.value))), 'service', 'searchService', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -57040,7 +57219,194 @@ } }, "dependsOn": [ - "logAnalyticsWorkspace" + "aiFoundryAiServices", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').searchService)]", + "existingAiFoundryAiServicesProject", + "logAnalyticsWorkspace", + "network", + "userAssignedIdentity" + ] + }, + "existing_AIProject_SearchConnectionModule": { + "condition": "[variables('useExistingAiFoundryAiProject')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "aiProjectSearchConnectionDeployment", + "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "existingAIProjectName": { + "value": "[variables('aiFoundryAiProjectResourceName')]" + }, + "existingAIFoundryName": { + "value": "[variables('aiFoundryAiServicesResourceName')]" + }, + "aiSearchName": { + "value": "[variables('aiSearchName')]" + }, + "aiSearchResourceId": { + "value": "[reference('searchService').outputs.resourceId.value]" + }, + "aiSearchLocation": { + "value": "[reference('searchService').outputs.location.value]" + }, + "aiSearchConnectionName": { + "value": "[variables('aiSearchName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "6038840175458269917" + } + }, + "parameters": { + "existingAIProjectName": { + "type": "string", + "metadata": { + "description": "Required. Existing AI Project Name" + } + }, + "existingAIFoundryName": { + "type": "string", + "metadata": { + "description": "Required. Existing AI Foundry Name" + } + }, + "aiSearchName": { + "type": "string", + "metadata": { + "description": "Required. AI Search Name" + } + }, + "aiSearchResourceId": { + "type": "string", + "metadata": { + "description": "Required. AI Search Resource ID" + } + }, + "aiSearchLocation": { + "type": "string", + "metadata": { + "description": "Required. AI Search Location" + } + }, + "aiSearchConnectionName": { + "type": "string", + "metadata": { + "description": "Required. AI Search Connection Name" + } + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/projects/connections", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('existingAIFoundryName'), parameters('existingAIProjectName'), parameters('aiSearchConnectionName'))]", + "properties": { + "category": "CognitiveSearch", + "target": "[format('https://{0}.search.windows.net', parameters('aiSearchName'))]", + "authType": "AAD", + "isSharedToAll": true, + "metadata": { + "ApiType": "Azure", + "ResourceId": "[parameters('aiSearchResourceId')]", + "location": "[parameters('aiSearchLocation')]" + } + } + } + ] + } + }, + "dependsOn": [ + "searchService" + ] + }, + "searchServiceToExistingAiServicesRoleAssignment": { + "condition": "[variables('useExistingAiFoundryAiProject')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "searchToExistingAiServices-roleAssignment", + "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[reference('searchService').outputs.systemAssignedMIPrincipalId.value]" + }, + "roleDefinitionId": { + "value": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd" + }, + "targetResourceName": { + "value": "[reference('aiFoundryAiServices').outputs.name.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.37.4.10188", + "templateHash": "3644919950024112374" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to" + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The role definition ID to assign" + } + }, + "targetResourceName": { + "type": "string", + "metadata": { + "description": "The name of the target resource" + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), parameters('targetResourceName'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "ServicePrincipal" + } + } + ], + "outputs": { + "roleAssignmentId": { + "type": "string", + "value": "[resourceId('Microsoft.Authorization/roleAssignments', guid(parameters('principalId'), parameters('roleDefinitionId'), parameters('targetResourceName')))]" + } + } + } + }, + "dependsOn": [ + "aiFoundryAiServices", + "searchService" ] } }, @@ -57225,7 +57591,7 @@ "metadata": { "description": "The endpoint URL for the Azure OpenAI Embedding model." }, - "value": "[reference('aiFoundryAiServices').outputs.aiProjectInfo.value.apiEndpoint]" + "value": "[reference('aiFoundryAiServices').outputs.endpoints.value['OpenAI Language Model Instance API']]" }, "AZURE_OPENAI_EMBEDDING_NAME": { "type": "string", @@ -57239,7 +57605,7 @@ "metadata": { "description": "The endpoint URL for the Azure OpenAI service." }, - "value": "[reference('aiFoundryAiServices').outputs.endpoint.value]" + "value": "[reference('aiFoundryAiServices').outputs.endpoints.value['OpenAI Language Model Instance API']]" }, "AZURE_OPENAI_MAX_TOKENS": { "type": "string", From 895aab04dd4bd366f597fc240d4a0165a5dcaa85 Mon Sep 17 00:00:00 2001 From: Abdul-Microsoft Date: Tue, 9 Sep 2025 15:57:47 +0530 Subject: [PATCH 64/84] Refactor SQL database module configuration: streamline parameters, adjust SKU settings, and remove sql elastic pool --- infra/main.bicep | 108 +++++++---------------------------------------- 1 file changed, 16 insertions(+), 92 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 94510313b..4172fe497 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -916,84 +916,25 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { tenantId: subscription().tenantId } connectionPolicy: 'Redirect' - // customerManagedKey: { - // autoRotationEnabled: true - // keyName: keyvault.outputs.name - // keyVaultResourceId: keyvault.outputs.resourceId - // // keyVersion: keyvault.outputs. - // } databases: [ { - availabilityZone: 1 - backupLongTermRetentionPolicy: { - monthlyRetention: 'P6M' - } - backupShortTermRetentionPolicy: { - retentionDays: 14 - } + availabilityZone: enableRedundancy ? 1 : -1 collation: 'SQL_Latin1_General_CP1_CI_AS' diagnosticSettings: enableMonitoring - ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] + ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] : null - elasticPoolResourceId: resourceId('Microsoft.Sql/servers/elasticPools', 'sql-${solutionSuffix}', 'sqlswaf-ep-001') licenseType: 'LicenseIncluded' maxSizeBytes: 34359738368 name: 'sqldb-${solutionSuffix}' + minCapacity: '1' sku: { - capacity: 0 - name: 'ElasticPool' + name: 'GP_S_Gen5' tier: 'GeneralPurpose' + family: 'Gen5' + capacity: 2 } } ] - elasticPools: [ - { - availabilityZone: -1 - //maintenanceConfigurationId: '' - name: 'sqlswaf-ep-001' - sku: { - capacity: 10 - name: 'GP_Gen5' - tier: 'GeneralPurpose' - } - roleAssignments: [ - { - principalId: userAssignedIdentity.outputs.principalId - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'db_datareader' - } - { - principalId: userAssignedIdentity.outputs.principalId - principalType: 'ServicePrincipal' - roleDefinitionIdOrName: 'db_datawriter' - } - - //Enable if above access is not sufficient for your use case - // { - // principalId: userAssignedIdentity.outputs.principalId - // principalType: 'ServicePrincipal' - // roleDefinitionIdOrName: 'SQL DB Contributor' - // } - // { - // principalId: userAssignedIdentity.outputs.principalId - // principalType: 'ServicePrincipal' - // roleDefinitionIdOrName: 'SQL Server Contributor' - // } - ] - } - ] - firewallRules: [ - { - endIpAddress: '255.255.255.255' - name: 'AllowSpecificRange' - startIpAddress: '0.0.0.0' - } - { - endIpAddress: '0.0.0.0' - name: 'AllowAllWindowsAzureIps' - startIpAddress: '0.0.0.0' - } - ] location: solutionLocation managedIdentities: { systemAssigned: true @@ -1018,36 +959,19 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { } ] : [] - restrictOutboundNetworkAccess: 'Disabled' - securityAlertPolicies: [ + firewallRules: (!enablePrivateNetworking) ? [ { - emailAccountAdmins: true - name: 'Default' - state: 'Enabled' + endIpAddress: '255.255.255.255' + name: 'AllowSpecificRange' + startIpAddress: '0.0.0.0' } - ] + { + endIpAddress: '0.0.0.0' + name: 'AllowAllWindowsAzureIps' + startIpAddress: '0.0.0.0' + } + ] : [] tags: tags - virtualNetworkRules: enablePrivateNetworking - ? [ - { - ignoreMissingVnetServiceEndpoint: true - name: 'newVnetRule1' - virtualNetworkSubnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId - } - ] - : [] - vulnerabilityAssessmentsObj: { - name: 'default' - // recurringScans: { - // emails: [ - // 'test1@contoso.com' - // 'test2@contoso.com' - // ] - // emailSubscriptionAdmins: true - // isEnabled: true - // } - storageAccountResourceId: avmStorageAccount.outputs.resourceId - } } } From 7f59fb368add64522be4127e24e020f240a4e865 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Tue, 9 Sep 2025 11:12:56 +0000 Subject: [PATCH 65/84] Updated Post deployment Script to support Waf --- docs/DeploymentGuide.md | 2 +- infra/scripts/process_sample_data.sh | 571 +++++++++++++++++++-------- 2 files changed, 405 insertions(+), 168 deletions(-) diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index c1bb82b2c..974dc6017 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -21,7 +21,7 @@ By default, the **Gpt-4o-mini model capacity** in deployment is set to **30k tok Depending on your subscription quota and capacity, you can [adjust quota settings](AzureGPTQuotaSettings.md) to better meet your specific needs. You can also [adjust the deployment parameters](CustomizingAzdParameters.md) for additional optimization. ­ -## Deployment Options & Steps +## Deployment Options ### Sandbox or WAF Aligned Deployment Options diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index aa8b3e291..f7edd101a 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -1,167 +1,404 @@ -#!/bin/bash - -# Variables -resourceGroupName="$1" -cosmosDbAccountName="$2" -storageAccount="$3" -fileSystem="$4" -keyvaultName="$5" -sqlServerName="$6" -SqlDatabaseName="$7" -webAppManagedIdentityClientId="$8" -webAppManagedIdentityDisplayName="$9" -aiSearchName="${10}" -aif_resource_id="${11}" - -# get parameters from azd env, if not provided -if [ -z "$resourceGroupName" ]; then - resourceGroupName=$(azd env get-value RESOURCE_GROUP_NAME) -fi - - -if [ -z "$cosmosDbAccountName" ]; then - cosmosDbAccountName=$(azd env get-value COSMOSDB_ACCOUNT_NAME) -fi - -if [ -z "$storageAccount" ]; then - storageAccount=$(azd env get-value STORAGE_ACCOUNT_NAME) -fi - -if [ -z "$fileSystem" ]; then - fileSystem=$(azd env get-value STORAGE_CONTAINER_NAME) -fi - -if [ -z "$keyvaultName" ]; then - keyvaultName=$(azd env get-value KEY_VAULT_NAME) -fi - -if [ -z "$sqlServerName" ]; then - sqlServerName=$(azd env get-value SQLDB_SERVER_NAME) -fi - -if [ -z "$SqlDatabaseName" ]; then - SqlDatabaseName=$(azd env get-value SQLDB_DATABASE) -fi - -if [ -z "$webAppManagedIdentityClientId" ]; then - webAppManagedIdentityClientId=$(azd env get-value MANAGEDIDENTITY_WEBAPP_CLIENTID) -fi - -if [ -z "$webAppManagedIdentityDisplayName" ]; then - webAppManagedIdentityDisplayName=$(azd env get-value MANAGEDIDENTITY_WEBAPP_NAME) -fi - -if [ -z "$aiSearchName" ]; then - aiSearchName=$(azd env get-value AI_SEARCH_SERVICE_NAME) -fi - -if [ -z "$aif_resource_id" ]; then - aif_resource_id=$(azd env get-value AI_FOUNDRY_RESOURCE_ID) -fi - -azSubscriptionId=$(azd env get-value AZURE_SUBSCRIPTION_ID) - -# Check if all required arguments are provided -if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiSearchName" ] || [ -z "$aif_resource_id" ]; then - echo "Usage: $0 " - exit 1 -fi - -# Authenticate with Azure -if az account show &> /dev/null; then - echo "Already authenticated with Azure." -else - echo "Not authenticated with Azure. Attempting to authenticate..." - if [ -n "$managedIdentityClientId" ]; then - # Use managed identity if running in Azure - echo "Authenticating with Managed Identity..." - az login --identity --client-id ${managedIdentityClientId} - else - # Use Azure CLI login if running locally - echo "Authenticating with Azure CLI..." - az login - fi -fi - -#check if user has selected the correct subscription -currentSubscriptionId=$(az account show --query id -o tsv) -currentSubscriptionName=$(az account show --query name -o tsv) -if [ "$currentSubscriptionId" != "$azSubscriptionId" ]; then - echo "Current selected subscription is $currentSubscriptionName ( $currentSubscriptionId )." - read -rp "Do you want to continue with this subscription?(y/n): " confirmation - if [[ "$confirmation" != "y" && "$confirmation" != "Y" ]]; then - echo "Fetching available subscriptions..." - availableSubscriptions=$(az account list --query "[?state=='Enabled'].[name,id]" --output tsv) - while true; do - echo "" - echo "Available Subscriptions:" - echo "========================" - echo "$availableSubscriptions" | awk '{printf "%d. %s ( %s )\n", NR, $1, $2}' - echo "========================" - echo "" - read -rp "Enter the number of the subscription (1-$(echo "$availableSubscriptions" | wc -l)) to use: " subscriptionIndex - if [[ "$subscriptionIndex" =~ ^[0-9]+$ ]] && [ "$subscriptionIndex" -ge 1 ] && [ "$subscriptionIndex" -le $(echo "$availableSubscriptions" | wc -l) ]; then - selectedSubscription=$(echo "$availableSubscriptions" | sed -n "${subscriptionIndex}p") - selectedSubscriptionName=$(echo "$selectedSubscription" | cut -f1) - selectedSubscriptionId=$(echo "$selectedSubscription" | cut -f2) - - # Set the selected subscription - if az account set --subscription "$selectedSubscriptionId"; then - echo "Switched to subscription: $selectedSubscriptionName ( $selectedSubscriptionId )" - break - else - echo "Failed to switch to subscription: $selectedSubscriptionName ( $selectedSubscriptionId )." - fi - else - echo "Invalid selection. Please try again." - fi - done - else - echo "Proceeding with the current subscription: $currentSubscriptionName ( $currentSubscriptionId )" - az account set --subscription "$currentSubscriptionId" - fi -else - echo "Proceeding with the subscription: $currentSubscriptionName ( $currentSubscriptionId )" - az account set --subscription "$currentSubscriptionId" -fi - -# Call add_cosmosdb_access.sh -echo "Running add_cosmosdb_access.sh" -bash infra/scripts/add_cosmosdb_access.sh "$resourceGroupName" "$cosmosDbAccountName" -if [ $? -ne 0 ]; then - echo "Error: add_cosmosdb_access.sh failed." - exit 1 -fi -echo "add_cosmosdb_access.sh completed successfully." - -# Call copy_kb_files.sh -echo "Running copy_kb_files.sh" -bash infra/scripts/copy_kb_files.sh "$storageAccount" "$fileSystem" -if [ $? -ne 0 ]; then - echo "Error: copy_kb_files.sh failed." - exit 1 -fi -echo "copy_kb_files.sh completed successfully." - -# Call run_create_index_scripts.sh -echo "Running run_create_index_scripts.sh" -bash infra/scripts/run_create_index_scripts.sh "$keyvaultName" "" "" "$resourceGroupName" "$sqlServerName" "$aiSearchName" "$aif_resource_id" -if [ $? -ne 0 ]; then - echo "Error: run_create_index_scripts.sh failed." - exit 1 -fi -echo "run_create_index_scripts.sh completed successfully." - -# Call create_sql_user_and_role.sh -echo "Running create_sql_user_and_role.sh" -bash infra/scripts/add_user_scripts/create_sql_user_and_role.sh "$sqlServerName.database.windows.net" "$SqlDatabaseName" '[ - {"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datareader"}, - {"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datawriter"} -]' -if [ $? -ne 0 ]; then - echo "Error: create_sql_user_and_role.sh failed." - exit 1 -fi -echo "create_sql_user_and_role.sh completed successfully." - -echo "All scripts executed successfully." + #!/bin/bash + + # Variables + resourceGroupName="$1" + cosmosDbAccountName="$2" + storageAccount="$3" + fileSystem="$4" + keyvaultName="$5" + sqlServerName="$6" + SqlDatabaseName="$7" + webAppManagedIdentityClientId="$8" + webAppManagedIdentityDisplayName="$9" + aiSearchName="${10}" + aif_resource_id="${11}" + + # Global variables to track original network access states + original_storage_public_access="" + original_storage_default_action="" + original_foundry_public_access="" + aif_resource_group="" + aif_account_resource_id="" + # Add global variable for SQL Server public access + original_sql_public_access="" + + # Function to enable public network access temporarily + enable_public_access() { + echo "=== Temporarily enabling public network access for services ===" + + # Enable public access for Storage Account + echo "Enabling public access for Storage Account: $storageAccount" + original_storage_public_access=$(az storage account show \ + --name "$storageAccount" \ + --resource-group "$resourceGroupName" \ + --query "publicNetworkAccess" \ + -o tsv) + original_storage_default_action=$(az storage account show \ + --name "$storageAccount" \ + --resource-group "$resourceGroupName" \ + --query "networkRuleSet.defaultAction" \ + -o tsv) + + if [ "$original_storage_public_access" != "Enabled" ]; then + az storage account update \ + --name "$storageAccount" \ + --resource-group "$resourceGroupName" \ + --public-network-access Enabled \ + --output none + if [ $? -eq 0 ]; then + echo "✓ Storage Account public access enabled" + else + echo "✗ Failed to enable Storage Account public access" + return 1 + fi + else + echo "✓ Storage Account public access already enabled" + fi + + # Also ensure the default network action allows access + if [ "$original_storage_default_action" != "Allow" ]; then + echo "Setting Storage Account network default action to Allow" + az storage account update \ + --name "$storageAccount" \ + --resource-group "$resourceGroupName" \ + --default-action Allow \ + --output none + if [ $? -eq 0 ]; then + echo "✓ Storage Account network default action set to Allow" + else + echo "✗ Failed to set Storage Account network default action" + return 1 + fi + else + echo "✓ Storage Account network default action already set to Allow" + fi + + # Enable public access for AI Foundry + # Extract the account resource ID (remove /projects/... part if present) + aif_account_resource_id=$(echo "$aif_resource_id" | sed 's|/projects/.*||') + aif_resource_name=$(basename "$aif_account_resource_id") + # Extract resource group from the AI Foundry account resource ID + aif_resource_group=$(echo "$aif_account_resource_id" | sed -n 's|.*/resourceGroups/\([^/]*\)/.*|\1|p') + + original_foundry_public_access=$(az cognitiveservices account show \ + --name "$aif_resource_name" \ + --resource-group "$aif_resource_group" \ + --query "properties.publicNetworkAccess" \ + --output tsv) + if [ -z "$original_foundry_public_access" ] || [ "$original_foundry_public_access" = "null" ]; then + echo "⚠ Info: Could not retrieve AI Foundry network access status." + echo " AI Foundry network access might be managed differently." + elif [ "$original_foundry_public_access" != "Enabled" ]; then + echo "Current AI Foundry public access: $original_foundry_public_access" + echo "Enabling public access for AI Foundry resource: $aif_resource_name (Resource Group: $aif_resource_group)" + if MSYS_NO_PATHCONV=1 az resource update \ + --ids "$aif_account_resource_id" \ + --api-version 2024-10-01 \ + --set properties.publicNetworkAccess=Enabled properties.apiProperties="{}" \ + --output none; then + echo "✓ AI Foundry public access enabled" + else + echo "⚠ Warning: Failed to enable AI Foundry public access automatically." + fi + else + echo "✓ AI Foundry public access already enabled" + fi + + + # Enable public access for SQL Server + echo "Enabling public access for SQL Server: $sqlServerName" + original_sql_public_access=$(az sql server show \ + --name "$sqlServerName" \ + --resource-group "$resourceGroupName" \ + --query "publicNetworkAccess" \ + -o tsv) + if [ "$original_sql_public_access" != "Enabled" ]; then + az sql server update \ + --name "$sqlServerName" \ + --resource-group "$resourceGroupName" \ + --enable-public-network true \ + --output none + if [ $? -eq 0 ]; then + echo "✓ SQL Server public access enabled" + else + echo "✗ Failed to enable SQL Server public access" + return 1 + fi + else + echo "✓ SQL Server public access already enabled" + fi + + # Wait a bit for changes to take effect + echo "Waiting for network access changes to propagate..." + sleep 10 + echo "=== Public network access enabled successfully ===" + return 0 + } + + # Function to restore original network access settings + restore_network_access() { + echo "=== Restoring original network access settings ===" + + # Restore Storage Account access + if [ -n "$original_storage_public_access" ] && [ "$original_storage_public_access" != "Enabled" ]; then + echo "Restoring Storage Account public access to: $original_storage_public_access" + # Handle case sensitivity - convert to proper case + case "$original_storage_public_access" in + "enabled"|"Enabled") restore_value="Enabled" ;; + "disabled"|"Disabled") restore_value="Disabled" ;; + *) restore_value="$original_storage_public_access" ;; + esac + az storage account update \ + --name "$storageAccount" \ + --resource-group "$resourceGroupName" \ + --public-network-access "$restore_value" \ + --output none + if [ $? -eq 0 ]; then + echo "✓ Storage Account access restored" + else + echo "✗ Failed to restore Storage Account access" + fi + else + echo "Storage Account access unchanged (already at desired state)" + fi + + # Restore Storage Account network default action + if [ -n "$original_storage_default_action" ] && [ "$original_storage_default_action" != "Allow" ]; then + echo "Restoring Storage Account network default action to: $original_storage_default_action" + az storage account update \ + --name "$storageAccount" \ + --resource-group "$resourceGroupName" \ + --default-action "$original_storage_default_action" \ + --output none + if [ $? -eq 0 ]; then + echo "✓ Storage Account network default action restored" + else + echo "✗ Failed to restore Storage Account network default action" + fi + else + echo "Storage Account network default action unchanged (already at desired state)" + fi + + # Restore AI Foundry access + if [ -n "$original_foundry_public_access" ] && [ "$original_foundry_public_access" != "Enabled" ]; then + echo "Restoring AI Foundry public access to: $original_foundry_public_access" + # Try using the working approach to restore the original setting + if MSYS_NO_PATHCONV=1 az resource update \ + --ids "$aif_account_resource_id" \ + --api-version 2024-10-01 \ + --set properties.publicNetworkAccess="$original_foundry_public_access" properties.apiProperties="{}" \ + --output none 2>/dev/null; then + echo "✓ AI Foundry access restored" + else + echo "⚠ Warning: Failed to restore AI Foundry access automatically." + echo " Please manually restore network access in the Azure portal if needed." + fi + else + echo "AI Foundry access unchanged (already at desired state)" + fi + + echo "=== Network access restoration completed ===" + + # Restore SQL Server public access + if [ -n "$original_sql_public_access" ] && [ "$original_sql_public_access" != "Enabled" ]; then + echo "Restoring SQL Server public access to: $original_sql_public_access" + # Handle case sensitivity + case "$original_sql_public_access" in + "enabled"|"Enabled") restore_value=true ;; + "disabled"|"Disabled") restore_value=false ;; + *) restore_value="$original_sql_public_access" ;; + esac + az sql server update \ + --name "$sqlServerName" \ + --resource-group "$resourceGroupName" \ + --enable-public-network $restore_value \ + --output none + if [ $? -eq 0 ]; then + echo "✓ SQL Server public access restored" + else + echo "✗ Failed to restore SQL Server public access" + fi + else + echo "SQL Server public access unchanged (already at desired state)" + fi + + } + + # Function to handle script cleanup on exit + cleanup_on_exit() { + exit_code=$? + echo "" + if [ $exit_code -ne 0 ]; then + echo "Script failed with exit code: $exit_code" + fi + echo "Performing cleanup..." + restore_network_access + exit $exit_code + } + + # Set up trap to ensure cleanup happens on exit + trap cleanup_on_exit EXIT INT TERM + + # get parameters from azd env, if not provided + if [ -z "$resourceGroupName" ]; then + resourceGroupName=$(azd env get-value RESOURCE_GROUP_NAME) + fi + + + if [ -z "$cosmosDbAccountName" ]; then + cosmosDbAccountName=$(azd env get-value COSMOSDB_ACCOUNT_NAME) + fi + + if [ -z "$storageAccount" ]; then + storageAccount=$(azd env get-value STORAGE_ACCOUNT_NAME) + fi + + if [ -z "$fileSystem" ]; then + fileSystem=$(azd env get-value STORAGE_CONTAINER_NAME) + fi + + if [ -z "$keyvaultName" ]; then + keyvaultName=$(azd env get-value KEY_VAULT_NAME) + fi + + if [ -z "$sqlServerName" ]; then + sqlServerName=$(azd env get-value SQLDB_SERVER_NAME) + fi + + if [ -z "$SqlDatabaseName" ]; then + SqlDatabaseName=$(azd env get-value SQLDB_DATABASE) + fi + + if [ -z "$webAppManagedIdentityClientId" ]; then + webAppManagedIdentityClientId=$(azd env get-value MANAGEDIDENTITY_WEBAPP_CLIENTID) + fi + + if [ -z "$webAppManagedIdentityDisplayName" ]; then + webAppManagedIdentityDisplayName=$(azd env get-value MANAGEDIDENTITY_WEBAPP_NAME) + fi + + if [ -z "$aiSearchName" ]; then + aiSearchName=$(azd env get-value AI_SEARCH_SERVICE_NAME) + fi + + if [ -z "$aif_resource_id" ]; then + aif_resource_id=$(azd env get-value AI_FOUNDRY_RESOURCE_ID) + fi + + azSubscriptionId=$(azd env get-value AZURE_SUBSCRIPTION_ID) + + # Check if all required arguments are provided + if [ -z "$resourceGroupName" ] || [ -z "$cosmosDbAccountName" ] || [ -z "$storageAccount" ] || [ -z "$fileSystem" ] || [ -z "$keyvaultName" ] || [ -z "$sqlServerName" ] || [ -z "$SqlDatabaseName" ] || [ -z "$webAppManagedIdentityClientId" ] || [ -z "$webAppManagedIdentityDisplayName" ] || [ -z "$aiSearchName" ] || [ -z "$aif_resource_id" ]; then + echo "Usage: $0 " + exit 1 + fi + + # Authenticate with Azure + if az account show &> /dev/null; then + echo "Already authenticated with Azure." + else + echo "Not authenticated with Azure. Attempting to authenticate..." + if [ -n "$managedIdentityClientId" ]; then + # Use managed identity if running in Azure + echo "Authenticating with Managed Identity..." + az login --identity --client-id ${managedIdentityClientId} + else + # Use Azure CLI login if running locally + echo "Authenticating with Azure CLI..." + az login + fi + fi + + #check if user has selected the correct subscription + currentSubscriptionId=$(az account show --query id -o tsv) + currentSubscriptionName=$(az account show --query name -o tsv) + if [ "$currentSubscriptionId" != "$azSubscriptionId" ]; then + echo "Current selected subscription is $currentSubscriptionName ( $currentSubscriptionId )." + read -rp "Do you want to continue with this subscription?(y/n): " confirmation + if [[ "$confirmation" != "y" && "$confirmation" != "Y" ]]; then + echo "Fetching available subscriptions..." + availableSubscriptions=$(az account list --query "[?state=='Enabled'].[name,id]" --output tsv) + while true; do + echo "" + echo "Available Subscriptions:" + echo "========================" + echo "$availableSubscriptions" | awk '{printf "%d. %s ( %s )\n", NR, $1, $2}' + echo "========================" + echo "" + read -rp "Enter the number of the subscription (1-$(echo "$availableSubscriptions" | wc -l)) to use: " subscriptionIndex + if [[ "$subscriptionIndex" =~ ^[0-9]+$ ]] && [ "$subscriptionIndex" -ge 1 ] && [ "$subscriptionIndex" -le $(echo "$availableSubscriptions" | wc -l) ]; then + selectedSubscription=$(echo "$availableSubscriptions" | sed -n "${subscriptionIndex}p") + selectedSubscriptionName=$(echo "$selectedSubscription" | cut -f1) + selectedSubscriptionId=$(echo "$selectedSubscription" | cut -f2) + + # Set the selected subscription + if az account set --subscription "$selectedSubscriptionId"; then + echo "Switched to subscription: $selectedSubscriptionName ( $selectedSubscriptionId )" + break + else + echo "Failed to switch to subscription: $selectedSubscriptionName ( $selectedSubscriptionId )." + fi + else + echo "Invalid selection. Please try again." + fi + done + else + echo "Proceeding with the current subscription: $currentSubscriptionName ( $currentSubscriptionId )" + az account set --subscription "$currentSubscriptionId" + fi + else + echo "Proceeding with the subscription: $currentSubscriptionName ( $currentSubscriptionId )" + az account set --subscription "$currentSubscriptionId" + fi + + + # Enable public network access for required services + enable_public_access + if [ $? -ne 0 ]; then + echo "Error: Failed to enable public network access for services." + exit 1 + fi + + + # Call add_cosmosdb_access.sh + echo "Running add_cosmosdb_access.sh" + bash infra/scripts/add_cosmosdb_access.sh "$resourceGroupName" "$cosmosDbAccountName" + if [ $? -ne 0 ]; then + echo "Error: add_cosmosdb_access.sh failed." + exit 1 + fi + echo "add_cosmosdb_access.sh completed successfully." + + # Call copy_kb_files.sh + echo "Running copy_kb_files.sh" + bash infra/scripts/copy_kb_files.sh "$storageAccount" "$fileSystem" + if [ $? -ne 0 ]; then + echo "Error: copy_kb_files.sh failed." + exit 1 + fi + echo "copy_kb_files.sh completed successfully." + + # Call run_create_index_scripts.sh + echo "Running run_create_index_scripts.sh" + bash infra/scripts/run_create_index_scripts.sh "$keyvaultName" "" "" "$resourceGroupName" "$sqlServerName" "$aiSearchName" "$aif_resource_id" + if [ $? -ne 0 ]; then + echo "Error: run_create_index_scripts.sh failed." + exit 1 + fi + echo "run_create_index_scripts.sh completed successfully." + + # Call create_sql_user_and_role.sh + echo "Running create_sql_user_and_role.sh" + bash infra/scripts/add_user_scripts/create_sql_user_and_role.sh "$sqlServerName.database.windows.net" "$SqlDatabaseName" '[ + {"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datareader"}, + {"clientId":"'"$webAppManagedIdentityClientId"'", "displayName":"'"$webAppManagedIdentityDisplayName"'", "role":"db_datawriter"} + ]' + if [ $? -ne 0 ]; then + echo "Error: create_sql_user_and_role.sh failed." + exit 1 + fi + echo "create_sql_user_and_role.sh completed successfully." + + echo "All scripts executed successfully." + echo "Network access will be restored to original settings..." + # Note: cleanup_on_exit will be called automatically via the trap \ No newline at end of file From d2fdd1b0fe8c6b30860285196e3707ef8ad422fe Mon Sep 17 00:00:00 2001 From: Abdul-Microsoft Date: Tue, 9 Sep 2025 17:13:46 +0530 Subject: [PATCH 66/84] Fix reference to Log Analytics Workspace Resource ID in SQL DB module configuration --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 4172fe497..bc16c4c7f 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -921,7 +921,7 @@ module sqlDBModule 'br/public:avm/res/sql/server:0.20.1' = { availabilityZone: enableRedundancy ? 1 : -1 collation: 'SQL_Latin1_General_CP1_CI_AS' diagnosticSettings: enableMonitoring - ? [{ workspaceResourceId: logAnalyticsWorkspace!.outputs.resourceId }] + ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null licenseType: 'LicenseIncluded' maxSizeBytes: 34359738368 From 500380cc431dc4ea93e9092b10f0e92a61a086ce Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Wed, 10 Sep 2025 03:53:13 +0000 Subject: [PATCH 67/84] Removed unnecessary scopes in main.bicep --- infra/main.bicep | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index bc16c4c7f..d851dd156 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -106,7 +106,7 @@ var solutionSuffix= toLower(trim(replace( param enablePrivateNetworking bool = false @description('Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false.') -param enableMonitoring bool = true +param enableMonitoring bool = false @description('Optional. Enable scalability for applicable resources, aligned with the Well Architected Framework recommendations. Defaults to false.') param enableScalability bool = false @@ -254,16 +254,6 @@ var allTags = union( tags ) -var resourcesName = toLower(trim(replace( - replace( - replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''), - ' ', - '' - ), - '*', - '' -))) - // Paired location calculated based on 'location' parameter. This location will be used by applicable resources if `enableScalability` is set to `true` var cosmosDbHaLocation = cosmosDbZoneRedundantHaRegionPairs[resourceGroup().location] @@ -398,9 +388,9 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id module network 'modules/network.bicep' = if (enablePrivateNetworking) { - name: take('network-${resourcesName}-deployment', 64) + name: take('network-${solutionSuffix}-deployment', 64) params: { - resourcesName: resourcesName + resourcesName: solutionSuffix // logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspaceResourceId vmAdminUsername: vmAdminUsername ?? 'JumpboxAdminUser' @@ -782,7 +772,6 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { ] } dependsOn: [keyvault, avmStorageAccount] - scope: resourceGroup(resourceGroup().name) } // ========== AVM WAF ========== // @@ -867,7 +856,6 @@ module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { // } } dependsOn: [keyvault] - scope: resourceGroup(resourceGroup().name) } // working version of saving storage account secrets in key vault using AVM module @@ -1082,22 +1070,7 @@ module webSite 'modules/web-sites.bicep' = { vnetRouteAllEnabled: enablePrivateNetworking ? true : false vnetImagePullEnabled: enablePrivateNetworking ? true : false virtualNetworkSubnetId: enablePrivateNetworking ? network!.outputs.subnetWebResourceId : null - publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' - privateEndpoints: enablePrivateNetworking - ? [ - { - name: 'pep-${webSiteResourceName}' - customNetworkInterfaceName: 'nic-${webSiteResourceName}' - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.appService]!.outputs.resourceId } - ] - } - service: 'sites' - subnetResourceId: network!.outputs.subnetPrivateEndpointsResourceId - } - ] - : null + publicNetworkAccess: 'Enabled' } } From daf0612545fac8d954cc48ef3a05f66cf4edba05 Mon Sep 17 00:00:00 2001 From: Prekshith-Microsoft Date: Wed, 10 Sep 2025 12:46:17 +0530 Subject: [PATCH 68/84] fix: Return consistent response for 'what is the asset value' query. (#663) * Updated the prompt and now giving the consistent response for the 'what is the asset value' question * Updated the prompts into to a generic way for asset values --- infra/main.bicep | 3 ++- src/App/backend/agents/agent_factory.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 2b8858531..ba54fa1e5 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -158,7 +158,8 @@ var functionAppSqlPrompt = '''Generate a valid T-SQL query to find {query} for t ALWAYS select Client Name (Column: Client) in the query. Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed. When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons. - For asset values: if question is about total \"asset value\"/\"portfolio value\"/\"AUM\" → return SUM of latest investments; if about \"current asset/investment value\" → return all latest investments without SUM. + For asset values: If the question is about "asset value", "total asset value", "portfolio value", or "AUM" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about "current asset value" or "current investment value" → return all latest investments without SUM. + For trend queries: If the question contains "how did change", "over the last", "trend", or "progression" → return time series data for the requested period with SUM for each time period and show chronological progression. Only return the generated SQL query. Do not return anything else.''' var functionAppCallTranscriptSystemPrompt = '''You are an assistant who supports wealth advisors in preparing for client meetings. diff --git a/src/App/backend/agents/agent_factory.py b/src/App/backend/agents/agent_factory.py index 0e4ac3467..37f67d97a 100644 --- a/src/App/backend/agents/agent_factory.py +++ b/src/App/backend/agents/agent_factory.py @@ -183,7 +183,8 @@ async def get_sql_agent(cls) -> dict: - Do not use client name for filtering - Assets table contains snapshots by date; do not sum values across dates - Use StartTime for time-based filtering (meetings) - - For asset values: if question is about total "asset value"/"portfolio value"/"AUM" → return SUM of latest investments; if about "current asset/investment value" → return all latest investments without SUM. + - For asset values: If the question is about "asset value", "total asset value", "portfolio value", or "AUM" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about "current asset value" or "current investment value" → return all latest investments without SUM. + - For trend queries: If the question contains "how did change", "over the last", "trend", or "progression" → return time series data for the requested period with SUM for each time period and show chronological progression. - Only return the raw T-SQL query. No explanations or comments. """ From 550f682b5f32e5fdca6f26996fac2fa6192868d1 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Wed, 10 Sep 2025 08:36:55 +0000 Subject: [PATCH 69/84] Add temporary firewall rule for SQL Server public access during data load --- infra/scripts/process_sample_data.sh | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index f7edd101a..794ba243e 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -21,6 +21,8 @@ aif_account_resource_id="" # Add global variable for SQL Server public access original_sql_public_access="" + created_sql_allow_all_firewall_rule="false" + original_full_range_rule_present="false" # Function to enable public network access temporarily enable_public_access() { @@ -127,6 +129,50 @@ else echo "✓ SQL Server public access already enabled" fi + + # Add (or verify) a firewall rule allowing all IPs (TEMPORARY) + echo "Ensuring temporary wide-open firewall rule exists for data load" + sql_allow_all_rule_name="temp-allow-all-ip" + + # Detect if a full-range rule (any name) already existed before we potentially create one + pre_existing_full_range_rule=$(az sql server firewall-rule list \ + --server "$sqlServerName" \ + --resource-group "$resourceGroupName" \ + --query "[?startIpAddress=='0.0.0.0' && endIpAddress=='255.255.255.255'] | [0].name" \ + -o tsv 2>/dev/null) + if [ -n "$pre_existing_full_range_rule" ]; then + original_full_range_rule_present="true" + fi + + existing_allow_all_rule=$(az sql server firewall-rule list \ + --server "$sqlServerName" \ + --resource-group "$resourceGroupName" \ + --query "[?name=='${sql_allow_all_rule_name}'] | [0].name" \ + -o tsv 2>/dev/null) + + if [ -z "$existing_allow_all_rule" ]; then + if [ -n "$pre_existing_full_range_rule" ]; then + echo "✓ Existing rule ($pre_existing_full_range_rule) already allows full IP range." + else + echo "Creating temporary allow-all firewall rule ($sql_allow_all_rule_name)..." + if az sql server firewall-rule create \ + --resource-group "$resourceGroupName" \ + --server "$sqlServerName" \ + --name "$sql_allow_all_rule_name" \ + --start-ip-address 0.0.0.0 \ + --end-ip-address 255.255.255.255 \ + --output none; then + created_sql_allow_all_firewall_rule="true" + echo "✓ Temporary allow-all firewall rule created" + else + echo "⚠ Warning: Failed to create allow-all firewall rule" + fi + fi + else + echo "✓ Temporary allow-all firewall rule already present" + # Since it was present beforehand, mark that a full-range rule existed originally + original_full_range_rule_present="true" + fi # Wait a bit for changes to take effect echo "Waiting for network access changes to propagate..." From 396ad67435d152c9271dec59d1209e65be02e1c8 Mon Sep 17 00:00:00 2001 From: Ajit Padhi Date: Thu, 11 Sep 2025 13:21:08 +0530 Subject: [PATCH 70/84] script update for foundry --- infra/scripts/process_sample_data.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index 794ba243e..89ac9b051 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -232,7 +232,9 @@ if MSYS_NO_PATHCONV=1 az resource update \ --ids "$aif_account_resource_id" \ --api-version 2024-10-01 \ - --set properties.publicNetworkAccess="$original_foundry_public_access" properties.apiProperties="{}" \ + --set properties.publicNetworkAccess="$original_foundry_public_access" \ + --set properties.apiProperties.qnaAzureSearchEndpointKey="" \ + --set properties.networkAcls.bypass="AzureServices" \ --output none 2>/dev/null; then echo "✓ AI Foundry access restored" else @@ -242,8 +244,6 @@ else echo "AI Foundry access unchanged (already at desired state)" fi - - echo "=== Network access restoration completed ===" # Restore SQL Server public access if [ -n "$original_sql_public_access" ] && [ "$original_sql_public_access" != "Enabled" ]; then @@ -269,7 +269,8 @@ fi } - + echo "=== Network access restoration completed ===" + # Function to handle script cleanup on exit cleanup_on_exit() { exit_code=$? From efeb0701308570fff7cd7771d54aeed30e510a03 Mon Sep 17 00:00:00 2001 From: Prajwal D C Date: Thu, 11 Sep 2025 18:35:30 +0530 Subject: [PATCH 71/84] fix: Updated the param name --- infra/main.parameters.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 2219f100e..2b2c3a1fd 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -8,7 +8,7 @@ "cosmosLocation": { "value": "${AZURE_ENV_COSMOS_LOCATION}" }, - "deploymentType": { + "gptModelDeploymentType": { "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" }, "gptModelName": { From be4ff7078e587ffc6270bfb72eaf41116fb058b1 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 12 Sep 2025 05:53:25 +0000 Subject: [PATCH 72/84] fix: Simplify SQL Server access restoration messages --- infra/scripts/process_sample_data.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/scripts/process_sample_data.sh b/infra/scripts/process_sample_data.sh index 89ac9b051..c0b9f8a34 100644 --- a/infra/scripts/process_sample_data.sh +++ b/infra/scripts/process_sample_data.sh @@ -260,12 +260,12 @@ --enable-public-network $restore_value \ --output none if [ $? -eq 0 ]; then - echo "✓ SQL Server public access restored" + echo "✓ SQL Server access restored" else - echo "✗ Failed to restore SQL Server public access" + echo "✗ Failed to restore SQL Server access" fi else - echo "SQL Server public access unchanged (already at desired state)" + echo "SQL Server access unchanged (already at desired state)" fi } From ec381d7eb7bf4b70165587d72d17674b59fbfaf8 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 12 Sep 2025 16:11:04 +0530 Subject: [PATCH 73/84] Refactor bicep code --- infra/main.bicep | 40 +- infra/modules/project.bicep | 2 +- infra/old/deploy_ai_foundry.bicep | 469 ----------------- .../old/deploy_aifp_aisearch_connection.bicep | 32 -- infra/old/deploy_app_service.bicep | 477 ------------------ infra/old/deploy_cosmos_db.bicep | 84 --- ...deploy_foundry_model_role_assignment.bicep | 63 --- infra/old/deploy_keyvault.bicep | 123 ----- infra/old/deploy_managed_identity.bicep | 112 ---- infra/old/deploy_sql_db.bicep | 110 ---- infra/old/deploy_storage_account.bicep | 129 ----- 11 files changed, 6 insertions(+), 1635 deletions(-) delete mode 100644 infra/old/deploy_ai_foundry.bicep delete mode 100644 infra/old/deploy_aifp_aisearch_connection.bicep delete mode 100644 infra/old/deploy_app_service.bicep delete mode 100644 infra/old/deploy_cosmos_db.bicep delete mode 100644 infra/old/deploy_foundry_model_role_assignment.bicep delete mode 100644 infra/old/deploy_keyvault.bicep delete mode 100644 infra/old/deploy_managed_identity.bicep delete mode 100644 infra/old/deploy_sql_db.bicep delete mode 100644 infra/old/deploy_storage_account.bicep diff --git a/infra/main.bicep b/infra/main.bicep index 918786d65..9f06f99f0 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -86,8 +86,6 @@ param azureAiServiceLocation string param AZURE_LOCATION string = '' var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION -//var solutionSuffix = 'ca${padLeft(take(uniqueId, 12), 12, '0')}' - @maxLength(5) @description('Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.') param solutionUniqueToken string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) @@ -135,10 +133,6 @@ param enablePurgeProtection bool = false // Load the abbrevations file required to name the azure resources. //var abbrs = loadJsonContent('./abbreviations.json') -//var resourceGroupLocation = resourceGroup().location -//var solutionLocation = resourceGroupLocation -// var baseUrl = 'https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/' - var appEnvironment = 'Prod' var azureSearchIndex = 'transcripts_index' var azureSearchUseSemanticSearch = 'True' @@ -162,7 +156,6 @@ var azureSearchEnableInDomain = 'False' // Set to 'True' if you want to enable i var azureCosmosDbEnableFeedback = 'True' var useInternalStream = 'True' var useAIProjectClientFlag = 'False' -// var sqlServerFqdn = '${sqlDBModule.outputs.name}.database.windows.net' var sqlServerFqdn = 'sql-${solutionSuffix}.database.windows.net' @description('Optional. Size of the Jumpbox Virtual Machine when created. Set to custom value if enablePrivateNetworking is true.') @@ -269,11 +262,7 @@ resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) } -// Log Analytics Name, workspace ID, customer ID, and shared key (existing or new) -// var logAnalyticsWorkspaceName = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.name : logAnalyticsWorkspace!.outputs.name var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace!.outputs.resourceId -// var logAnalyticsPrimarySharedKey = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey -// var logAnalyticsWorkspaceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspace!.properties.customerId : logAnalyticsWorkspace!.outputs.logAnalyticsWorkspaceId @description('Optional created by user name') param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] @@ -387,7 +376,7 @@ module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-id } } - +// ========== Network Module ========== // module network 'modules/network.bicep' = if (enablePrivateNetworking) { name: take('network-${solutionSuffix}-deployment', 64) params: { @@ -586,7 +575,6 @@ var aiFoundryAiServicesEmbeddingModel = { raiPolicyName: 'Microsoft.Default' } -//TODO: update to AVM module when AI Projects and AI Projects RBAC are supported module aiFoundryAiServices 'modules/ai-services.bicep' = if (aiFoundryAIservicesEnabled) { name: take('avm.res.cognitive-services.account.${aiFoundryAiServicesResourceName}', 64) params: { @@ -689,9 +677,7 @@ module aiFoundryAiServices 'modules/ai-services.bicep' = if (aiFoundryAIservices //========== Cosmos DB module ========== // var cosmosDbResourceName = 'cosmos-${solutionSuffix}' var cosmosDbDatabaseName = 'db_conversation_history' -// var cosmosDbDatabaseMemoryContainerName = 'memory' var collectionName = 'conversations' -//TODO: update to latest version of AVM module module cosmosDb 'br/public:avm/res/document-db/database-account:0.15.0' = { name: take('avm.res.document-db.database-account.${cosmosDbResourceName}', 64) params: { @@ -846,15 +832,6 @@ module avmStorageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = { } ] } - // secretsExportConfiguration: { - // accessKey1Name: 'ADLS-ACCOUNT-NAME' - // connectionString1Name: storageAccountName - // accessKey2Name: 'ADLS-ACCOUNT-CONTAINER' - // connectionString2Name: 'data' - // accessKey3Name: 'ADLS-ACCOUNT-KEY' - // connectionString3Name: listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2021-04-01') - // keyVaultResourceId: keyvault.outputs.resourceId - // } } dependsOn: [keyvault] } @@ -1013,7 +990,6 @@ module webSite 'modules/web-sites.bicep' = { APP_ENV: appEnvironment APPINSIGHTS_INSTRUMENTATIONKEY: enableMonitoring ? applicationInsights!.outputs.instrumentationKey : '' APPLICATIONINSIGHTS_CONNECTION_STRING: enableMonitoring ? applicationInsights!.outputs.connectionString : '' - // AZURE_SEARCH_SERVICE: ''//azureSearchService AZURE_SEARCH_SERVICE: aiSearchName AZURE_SEARCH_INDEX: azureSearchIndex AZURE_SEARCH_USE_SEMANTIC_SEARCH: azureSearchUseSemanticSearch @@ -1039,7 +1015,6 @@ module webSite 'modules/web-sites.bicep' = { AZURE_SEARCH_PERMITTED_GROUPS_COLUMN: azureSearchPermittedGroupsField AZURE_SEARCH_STRICTNESS: azureSearchStrictness AZURE_OPENAI_EMBEDDING_NAME: embeddingModel - // AZURE_OPENAI_EMBEDDING_ENDPOINT: aiFoundryAiServices.outputs.endpoint AZURE_OPENAI_EMBEDDING_ENDPOINT : aiFoundryAiServices.outputs.endpoints['OpenAI Language Model Instance API'] SQLDB_SERVER: sqlServerFqdn SQLDB_DATABASE: sqlDbName @@ -1049,7 +1024,6 @@ module webSite 'modules/web-sites.bicep' = { AZURE_COSMOSDB_DATABASE: cosmosDbDatabaseName AZURE_COSMOSDB_ENABLE_FEEDBACK: azureCosmosDbEnableFeedback SQLDB_USER_MID: userAssignedIdentity.outputs.clientId - // AZURE_AI_SEARCH_ENDPOINT: '' //azureSearchServiceEndpoint AZURE_AI_SEARCH_ENDPOINT: 'https://${aiSearchName}.search.windows.net' AZURE_SQL_SYSTEM_PROMPT: functionAppSqlPrompt AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT: functionAppCallTranscriptSystemPrompt @@ -1058,7 +1032,6 @@ module webSite 'modules/web-sites.bicep' = { AZURE_AI_AGENT_ENDPOINT: aiFoundryAiServices.outputs.aiProjectInfo.apiEndpoint AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME: gptModelName AZURE_AI_AGENT_API_VERSION: azureOpenaiAPIVersion - // AZURE_SEARCH_CONNECTION_NAME: '' //aiSearchProjectConnectionName AZURE_SEARCH_CONNECTION_NAME: aiSearchName AZURE_CLIENT_ID: userAssignedIdentity.outputs.clientId } @@ -1098,9 +1071,6 @@ module searchService 'br/public:avm/res/search/search-service:0.11.1' = { aadAuthFailureMode: 'http401WithBearerChallenge' } } - // Customer-managed key enforcement (optional) - // cmkEnforcement: 'Enabled' - // Wire up diagnostic settings to the Log Analytics workspace when monitoring is enabled diagnosticSettings: enableMonitoring ? [ { workspaceResourceId: logAnalyticsWorkspaceResourceId @@ -1245,7 +1215,7 @@ output MANAGEDIDENTITY_WEBAPP_NAME string = userAssignedIdentity.outputs.name @description('Client ID of the managed identity used by the web app.') output MANAGEDIDENTITY_WEBAPP_CLIENTID string = userAssignedIdentity.outputs.clientId @description('Name of the AI Search service.') -output AI_SEARCH_SERVICE_NAME string = aiSearchName //aifoundry.outputs.aiSearchService +output AI_SEARCH_SERVICE_NAME string = aiSearchName @description('Name of the deployed web application.') output WEB_APP_NAME string = webSite.outputs.name @@ -1270,7 +1240,7 @@ output AZURE_AI_AGENT_ENDPOINT string = aiFoundryAiServices.outputs.aiProjectInf output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName @description('The endpoint URL of the Azure AI Search service.') -output AZURE_AI_SEARCH_ENDPOINT string = 'https://${aiSearchName}.search.windows.net' //aifoundry.outputs.aiSearchTarget +output AZURE_AI_SEARCH_ENDPOINT string = 'https://${aiSearchName}.search.windows.net' @description('The system prompt used for call transcript processing in Azure Functions.') output AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT string = functionAppCallTranscriptSystemPrompt @@ -1327,7 +1297,7 @@ output AZURE_OPENAI_TEMPERATURE string = azureOpenAITemperature output AZURE_OPENAI_TOP_P string = azureOpenAITopP @description('The name of the Azure AI Search connection.') -output AZURE_SEARCH_CONNECTION_NAME string = 'foundry-search-connection-${solutionSuffix}' //aiFoundryAiServices.outputs.aiSearchFoundryConnectionName +output AZURE_SEARCH_CONNECTION_NAME string = aiSearchName @description('The columns in Azure AI Search that contain content.') output AZURE_SEARCH_CONTENT_COLUMNS string = azureSearchContentColumns @@ -1351,7 +1321,7 @@ output AZURE_SEARCH_QUERY_TYPE string = azureSearchQueryType output AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG string = azureSearchSemanticSearchConfig @description('The name of the Azure AI Search service.') -output AZURE_SEARCH_SERVICE string = aiSearchName //aifoundry.outputs.aiSearchService +output AZURE_SEARCH_SERVICE string = aiSearchName @description('The strictness setting for Azure AI Search semantic ranking.') output AZURE_SEARCH_STRICTNESS string = azureSearchStrictness diff --git a/infra/modules/project.bicep b/infra/modules/project.bicep index dfe79810b..1a795412f 100644 --- a/infra/modules/project.bicep +++ b/infra/modules/project.bicep @@ -50,7 +50,7 @@ resource existingAiProject 'Microsoft.CognitiveServices/accounts/projects@2025-0 scope: resourceGroup(existingAiFoundryAiServicesSubscriptionId, existingAiFoundryAiServicesResourceGroupName) } -@description('AI Project metadata including name, resource ID, and API endpoint.') +@description('AI Project metadata including name, resource ID, and API endpoint, and SystemAssignedManagedIdentity Principal Id.') output aiProjectInfo aiProjectOutputType = { name: useExistingProject ? existingProjName : aiProject.name resourceId: useExistingProject ? existingFoundryProjectResourceId : aiProject.id diff --git a/infra/old/deploy_ai_foundry.bicep b/infra/old/deploy_ai_foundry.bicep deleted file mode 100644 index 86f210e1d..000000000 --- a/infra/old/deploy_ai_foundry.bicep +++ /dev/null @@ -1,469 +0,0 @@ -// Creates Azure dependent resources for Azure AI studio - -@description('Required. Solution Name') -param solutionName string - -@description('Required. Solution Location') -param solutionLocation string - -@description('Required. Contains Name of KeyVault.') -param keyVaultName string - -@description('Required. Indicates the type of Deployment.') -param deploymentType string - -@description('Optional. GPT Model Name') -param gptModelName string = 'gpt-4o-mini' - -@description('Required. Azure OepnAI API Version.') -param azureOpenaiAPIVersion string - -@description('Required. Param to get Deployment Capacity.') -param gptDeploymentCapacity int - -@description('Optional. Embedding Model.') -param embeddingModel string = 'text-embedding-ada-002' - -@description('Optional. Info about Embedding Deployment Capacity.') -param embeddingDeploymentCapacity int = 80 - -@description('Optional. Existing Log Analytics WorkspaceID.') -param existingLogAnalyticsWorkspaceId string = '' - -@description('Optional. Azure Existing AI Project ResourceID.') -param azureExistingAIProjectResourceId string = '' - -@description('Required. The name of the AI Foundry AI Project resource in Azure.') -param aiFoundryAiServicesAiProjectResourceName string - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -// Load the abbrevations file required to name the azure resources. -//var abbrs = loadJsonContent('./abbreviations.json') - -var aiFoundryName = 'aif-${solutionName}' -var applicationInsightsName = 'appi-${solutionName}' -var keyvaultName = keyVaultName -var location = solutionLocation //'eastus2' -var aiProjectName = '${aiFoundryAiServicesAiProjectResourceName}-${solutionName}' -var aiProjectFriendlyName = aiProjectName -var aiProjectDescription = 'AI Foundry Project' -var aiSearchName = 'srch-${solutionName}' -var workspaceName = 'log-${solutionName}' -var aiModelDeployments = [ - { - name: gptModelName - model: gptModelName - sku: { - name: deploymentType - capacity: gptDeploymentCapacity - } - raiPolicyName: 'Microsoft.Default' - } - { - name: embeddingModel - model: embeddingModel - sku: { - name: 'GlobalStandard' - capacity: embeddingDeploymentCapacity - } - raiPolicyName: 'Microsoft.Default' - } -] - -resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: keyVaultName -} - -var useExisting = !empty(existingLogAnalyticsWorkspaceId) -var existingLawSubscription = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[2] : '' -var existingLawResourceGroup = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[4] : '' -var existingLawName = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[8] : '' - -var existingOpenAIEndpoint = !empty(azureExistingAIProjectResourceId) - ? format('https://{0}.openai.azure.com/', split(azureExistingAIProjectResourceId, '/')[8]) - : '' -var existingProjEndpoint = !empty(azureExistingAIProjectResourceId) - ? format( - 'https://{0}.services.ai.azure.com/api/projects/{1}', - split(azureExistingAIProjectResourceId, '/')[8], - split(azureExistingAIProjectResourceId, '/')[10] - ) - : '' -var existingAIFoundryName = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[8] - : '' -var existingAIProjectName = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[10] - : '' -var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[2] - : '' -var existingAIServicesName = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[8] - : '' -var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[4] - : '' -var aiSearchConnectionName = 'foundry-search-connection-${solutionName}' -var aiAppInsightConnectionName = 'foundry-app-insights-connection-${solutionName}' - -resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = if (useExisting) { - name: existingLawName - scope: resourceGroup(existingLawSubscription, existingLawResourceGroup) -} - -resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = if (!useExisting) { - name: workspaceName - location: location - tags: tags - properties: { - retentionInDays: 30 - sku: { - name: 'PerGB2018' - } - } -} - -resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { - name: applicationInsightsName - location: location - kind: 'web' - properties: { - Application_Type: 'web' - publicNetworkAccessForIngestion: 'Enabled' - publicNetworkAccessForQuery: 'Enabled' - WorkspaceResourceId: useExisting ? existingLogAnalyticsWorkspace.id : logAnalytics.id - } - tags: tags -} - -resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { - name: aiFoundryName - location: location - sku: { - name: 'S0' - } - kind: 'AIServices' - identity: { - type: 'SystemAssigned' - } - properties: { - allowProjectManagement: true - customSubDomainName: aiFoundryName - networkAcls: { - defaultAction: 'Allow' - virtualNetworkRules: [] - ipRules: [] - } - publicNetworkAccess: 'Enabled' - disableLocalAuth: false - } - tags: tags -} - -resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { - parent: aiFoundry - name: aiProjectName - location: location - identity: { - type: 'SystemAssigned' - } - properties: { - description: aiProjectDescription - displayName: aiProjectFriendlyName - } - tags: tags -} - -resource existingAiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = if (!empty(azureExistingAIProjectResourceId)) { - name: existingAIFoundryName - scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) -} - -@batchSize(1) -resource aiFModelDeployments 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [ - for aiModeldeployment in aiModelDeployments: if (empty(azureExistingAIProjectResourceId)) { - parent: aiFoundry - name: aiModeldeployment.name - properties: { - model: { - format: 'OpenAI' - name: aiModeldeployment.model - } - raiPolicyName: aiModeldeployment.raiPolicyName - } - sku: { - name: aiModeldeployment.sku.name - capacity: aiModeldeployment.sku.capacity - } - } -] - -resource aiSearch 'Microsoft.Search/searchServices@2025-02-01-preview' = { - name: aiSearchName - location: solutionLocation - sku: { - name: 'basic' - } - identity: { - type: 'SystemAssigned' - } - properties: { - replicaCount: 1 - partitionCount: 1 - hostingMode: 'default' - publicNetworkAccess: 'enabled' - networkRuleSet: { - ipRules: [] - } - encryptionWithCmk: { - enforcement: 'Unspecified' - } - disableLocalAuth: false - authOptions: { - aadOrApiKey: { - aadAuthFailureMode: 'http403' - } - } - semanticSearch: 'free' - } - tags: tags -} - -resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/connections@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { - name: aiSearchConnectionName - parent: aiFoundry - properties: { - category: 'CognitiveSearch' - target: aiSearch.properties.endpoint - authType: 'AAD' - isSharedToAll: true - metadata: { - ApiType: 'Azure' - ResourceId: aiSearch.id - location: aiSearch.location - } - } -} - -module existing_AIProject_SearchConnectionModule 'deploy_aifp_aisearch_connection.bicep' = if (!empty(azureExistingAIProjectResourceId)) { - name: 'aiProjectSearchConnectionDeployment' - scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) - params: { - existingAIProjectName: existingAIProjectName - existingAIFoundryName: existingAIFoundryName - aiSearchName: aiSearchName - aiSearchResourceId: aiSearch.id - aiSearchLocation: aiSearch.location - aiSearchConnectionName: aiSearchConnectionName - } -} - -resource cognitiveServicesOpenAIUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' -} - -resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { - name: guid(resourceGroup().id, aiFoundry.id, cognitiveServicesOpenAIUser.id) - scope: aiFoundry - properties: { - principalId: aiSearch.identity.principalId - roleDefinitionId: cognitiveServicesOpenAIUser.id - principalType: 'ServicePrincipal' - } -} - -module assignOpenAIRoleToAISearchExisting 'deploy_foundry_model_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { - name: 'assignOpenAIRoleToAISearchExisting' - scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) - params: { - roleDefinitionId: cognitiveServicesOpenAIUser.id - roleAssignmentName: guid(resourceGroup().id, aiSearch.id, cognitiveServicesOpenAIUser.id, 'openai-foundry') - aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIFoundryName : aiFoundryName - principalId: aiSearch.identity.principalId - aiProjectName: !empty(azureExistingAIProjectResourceId) ? existingAIProjectName : aiProjectName - aiModelDeployments: aiModelDeployments - tags:tags - } -} - -@description('This is the built-in Search Index Data Reader role.') -resource searchIndexDataReaderRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - scope: aiSearch - name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' -} - -resource searchIndexDataReaderRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { - name: guid(aiSearch.id, aiFoundryProject.id, searchIndexDataReaderRoleDefinition.id) - scope: aiSearch - properties: { - roleDefinitionId: searchIndexDataReaderRoleDefinition.id - principalId: aiFoundryProject.identity.principalId - principalType: 'ServicePrincipal' - } -} -resource assignSearchIndexDataReaderToExistingAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(azureExistingAIProjectResourceId)) { - name: guid(resourceGroup().id, existingAIProjectName, searchIndexDataReaderRoleDefinition.id, 'Existing') - scope: aiSearch - properties: { - roleDefinitionId: searchIndexDataReaderRoleDefinition.id - principalId: assignOpenAIRoleToAISearchExisting.outputs.aiProjectPrincipalId - principalType: 'ServicePrincipal' - } -} - -@description('This is the built-in Search Service Contributor role.') -resource searchServiceContributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - scope: aiSearch - name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' -} - -resource searchServiceContributorRoleAssignmentToAIFP 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { - name: guid(aiSearch.id, aiFoundryProject.id, searchServiceContributorRoleDefinition.id) - scope: aiSearch - properties: { - roleDefinitionId: searchServiceContributorRoleDefinition.id - principalId: aiFoundryProject.identity.principalId - principalType: 'ServicePrincipal' - } -} - -resource searchServiceContributorRoleAssignmentExisting 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(azureExistingAIProjectResourceId)) { - name: guid(resourceGroup().id, existingAIProjectName, searchServiceContributorRoleDefinition.id, 'Existing') - scope: aiSearch - properties: { - roleDefinitionId: searchServiceContributorRoleDefinition.id - principalId: assignOpenAIRoleToAISearchExisting.outputs.aiProjectPrincipalId - principalType: 'ServicePrincipal' - } -} - -resource appInsightsFoundryConnection 'Microsoft.CognitiveServices/accounts/connections@2025-04-01-preview' = if (empty(azureExistingAIProjectResourceId)) { - name: aiAppInsightConnectionName - parent: aiFoundry - properties: { - category: 'AppInsights' - target: applicationInsights.id - authType: 'ApiKey' - isSharedToAll: true - credentials: { - key: applicationInsights.properties.ConnectionString - } - metadata: { - ApiType: 'Azure' - ResourceId: applicationInsights.id - } - } -} - -resource azureOpenAIApiVersionEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'AZURE-OPENAI-PREVIEW-API-VERSION' - properties: { - value: azureOpenaiAPIVersion //'2024-07-18' - } - tags:tags -} - -resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'AZURE-OPENAI-ENDPOINT' - properties: { - // value: aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint - value: !empty(existingOpenAIEndpoint) - ? existingOpenAIEndpoint - : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] - } - tags:tags -} - -resource azureOpenAIEmbeddingModelEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'AZURE-OPENAI-EMBEDDING-MODEL' - properties: { - value: embeddingModel - } - tags:tags -} - -resource azureSearchServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'AZURE-SEARCH-ENDPOINT' - properties: { - value: 'https://${aiSearch.name}.search.windows.net' - } - tags:tags -} - -resource azureSearchIndexEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'AZURE-SEARCH-INDEX' - properties: { - value: 'transcripts_index' - } - tags:tags -} -@description('Contains Name of KeyVault.') -output keyvaultName string = keyvaultName - -@description('Contains KeyVault ID.') -output keyvaultId string = keyVault.id - -@description('Contains AI Foundry ResourceGroup Name') -output resourceGroupNameFoundry string = !empty(existingAIServiceResourceGroup) - ? existingAIServiceResourceGroup - : resourceGroup().name - - @description('Contains Name of AI Foundry Project Endpoint.') - output aiFoundryProjectEndpoint string = !empty(existingProjEndpoint) - ? existingProjEndpoint - : aiFoundryProject.properties.endpoints['AI Foundry API'] - -@description('Contains AI Endpoint.') -output aoaiEndpoint string = !empty(existingOpenAIEndpoint) - ? existingOpenAIEndpoint - : aiFoundry.properties.endpoints['OpenAI Language Model Instance API'] //aiServices_m.properties.endpoint - -@description('Contains Name of AI Foundry.') -output aiFoundryName string = !empty(existingAIFoundryName) ? existingAIFoundryName : aiFoundryName //aiServicesName_m -output aiFoundryId string = !empty(azureExistingAIProjectResourceId) ? existingAiFoundry.id : aiFoundry.id - -@description('Contains AI Search Name.') -output aiSearchName string = aiSearchName - -@description('Contains AI SearchID.') -output aiSearchId string = aiSearch.id - -@description('Contains AI Search Target.') -output aiSearchTarget string = 'https://${aiSearch.name}.search.windows.net' - -@description('Contains AI Search Service.') -output aiSearchService string = aiSearch.name - -@description('Contains Name of AI Foundry Project.') -output aiFoundryProjectName string = !empty(existingAIProjectName) ? existingAIProjectName : aiFoundryProject.name - -@description('Contains Application Insights ID.') -output applicationInsightsId string = applicationInsights.id -@description('The Instrumentation Key for the Application Insights resource.') -output instrumentationKey string = applicationInsights.properties.InstrumentationKey - -@description('Contains Log Analytics Workspace Resource Name.') -output logAnalyticsWorkspaceResourceName string = useExisting ? existingLogAnalyticsWorkspace.name : logAnalytics.name - -@description('Contains Log Analytics Workspace ResourceGroup Name.') -output logAnalyticsWorkspaceResourceGroup string = useExisting ? existingLawResourceGroup : resourceGroup().name - -@description('Contains Application Insights Connection String.') -output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString - -@description('Contains AI Search Foundry Connection Name.') -output aiSearchFoundryConnectionName string = aiSearchConnectionName - -@description('Contains AI Foundry App Insights Connection Name.') -output aiAppInsightsFoundryConnectionName string = aiAppInsightConnectionName - -@description('Contains AI Model Deployments') -output aiModelDeployments array = aiModelDeployments diff --git a/infra/old/deploy_aifp_aisearch_connection.bicep b/infra/old/deploy_aifp_aisearch_connection.bicep deleted file mode 100644 index ee0424d33..000000000 --- a/infra/old/deploy_aifp_aisearch_connection.bicep +++ /dev/null @@ -1,32 +0,0 @@ -@description('Required. Existing AI Project Name') -param existingAIProjectName string - -@description('Required. Existing AI Foundry Name') -param existingAIFoundryName string - -@description('Required. AI Search Name') -param aiSearchName string - -@description('Required. AI Search Resource ID') -param aiSearchResourceId string - -@description('Required. AI Search Location') -param aiSearchLocation string - -@description('Required. AI Search Connection Name') -param aiSearchConnectionName string - -resource projectAISearchConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { - name: '${existingAIFoundryName}/${existingAIProjectName}/${aiSearchConnectionName}' - properties: { - category: 'CognitiveSearch' - target: 'https://${aiSearchName}.search.windows.net' - authType: 'AAD' - isSharedToAll: true - metadata: { - ApiType: 'Azure' - ResourceId: aiSearchResourceId - location: aiSearchLocation - } - } -} diff --git a/infra/old/deploy_app_service.bicep b/infra/old/deploy_app_service.bicep deleted file mode 100644 index 21ce3d9e7..000000000 --- a/infra/old/deploy_app_service.bicep +++ /dev/null @@ -1,477 +0,0 @@ -// ========== Key Vault ========== // -targetScope = 'resourceGroup' - -@description('Required. Solution Location') -param solutionLocation string - -@description('Optional. The pricing tier for the App Service plan') -@allowed(['F1', 'D1', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1', 'P2', 'P3', 'P4', 'P0v3']) -param hostingPlanSku string = 'B2' - -@description('Required. Name of App Service plan') -param hostingPlanName string - -@description('Required. Name of Web App') -param websiteName string - -@description('Specifies the application environment') -param appEnvironment string - -// @description('Name of Application Insights') -// param ApplicationInsightsName string = '${ solutionName }-app-insights' - -@description('Optional. Name of Azure Search Service') -param azureSearchService string = '' - -@description('Optional. Name of Azure Search Index') -param azureSearchIndex string = '' - -@description('Optional. Use semantic search') -param azureSearchUseSemanticSearch string = 'False' - -@description('Optional. Semantic search config') -param azureSearchSemanticSearchConfig string = 'default' - -@description('Optional. Top K results') -param azureSearchTopK string = '5' - -@description('Optional. Enable in domain') -param azureSearchEnableInDomain string = 'False' - -@description('Optional. Content columns') -param azureSearchContentColumns string = 'content' - -@description('Optional. Filename column') -param azureSearchFilenameColumn string = 'filename' - -@description('Optional. Title column') -param azureSearchTitleColumn string = 'client_id' - -@description('Optional. Url column') -param azureSearchUrlColumn string = 'sourceurl' - -@description('Required. Name of Azure OpenAI Resource') -param azureOpenAIResource string - -@description('Optional. Azure OpenAI Model Deployment Name') -param azureOpenAIModel string = 'gpt-4o-mini' - -@description('Optional. Azure Open AI Endpoint') -param azureOpenAIEndpoint string = '' - -@description('Optional. Azure OpenAI Temperature') -param azureOpenAITemperature string = '0' - -@description('Optional. Azure OpenAI Top P') -param azureOpenAITopP string = '1' - -@description('Optional. Azure OpenAI Max Tokens') -param azureOpenAIMaxTokens string = '1000' - -@description('Optional. Azure OpenAI Stop Sequence') -param azureOpenAIStopSequence string = '\n' - -@description('Optional. Azure OpenAI System Message') -param azureOpenAISystemMessage string = 'You are an AI assistant that helps people find information.' - -@description('Optional. Azure OpenAI Api Version') -param azureOpenAIApiVersion string = '2024-02-15-preview' - -@description('Optional. Whether or not to stream responses from Azure OpenAI') -param azureOpenAIStream string = 'True' - -@description('Optional. Azure Search Query Type') -@allowed(['simple', 'semantic', 'vector', 'vectorSimpleHybrid', 'vectorSemanticHybrid']) -param azureSearchQueryType string = 'simple' - -@description('Optional. Azure Search Vector Fields') -param azureSearchVectorFields string = 'contentVector' - -@description('Optional. Azure Search Permitted Groups Field') -param azureSearchPermittedGroupsField string = '' - -@description('Optional. Azure Search Strictness') -@allowed(['1', '2', '3', '4', '5']) -param azureSearchStrictness string = '3' - -@description('Optional. Azure OpenAI Embedding Deployment Name') -param azureOpenAIEmbeddingName string = '' - -@description('Optional. Azure Open AI Embedding Endpoint') -param azureOpenAIEmbeddingEndpoint string = '' - -@description('Optional. Use Azure Function') -param USE_INTERNAL_STREAM string = 'True' - -@description('Optional. SQL Database Server Name') -param SQLDB_SERVER string = '' - -@description('Optional. SQL Database Name') -param SQLDB_DATABASE string = '' - -@description('Optional. Azure Cosmos DB Account') -param AZURE_COSMOSDB_ACCOUNT string = '' - -@description('Optional. Azure Cosmos DB Conversations Container') -param AZURE_COSMOSDB_CONVERSATIONS_CONTAINER string = '' - -@description('Optional. Azure Cosmos DB Database') -param AZURE_COSMOSDB_DATABASE string = '' - -@description('Optional. Enable feedback in Cosmos DB') -param AZURE_COSMOSDB_ENABLE_FEEDBACK string = 'True' - -//@description('Power BI Embed URL') -//param VITE_POWERBI_EMBED_URL string = '' - -@description('Required. The container image tag to be deployed') -param imageTag string - -@description('Required. The resource ID of the user-assigned managed identity to be used by the deployed resources.') -param userassignedIdentityId string - -@description('Required. The client ID of the user-assigned managed identity.') -param userassignedIdentityClientId string - -@description('Required. The Instrumentation Key or Resource ID of the Application Insights resource used for monitoring.') -param applicationInsightsId string - -@description('Required. The endpoint URL of the Azure Cognitive Search service.') -param azureSearchServiceEndpoint string - -@description('Required. Azure Function App SQL System Prompt') -param sqlSystemPrompt string - -@description('Required. Azure Function App CallTranscript System Prompt') -param callTranscriptSystemPrompt string - -@description('Required. Azure Function App Stream Text System Prompt') -param streamTextSystemPrompt string - -@description('Required. AI Foundry project endpoint URL.') -param aiFoundryProjectEndpoint string - -@description('Optional. Flag to enable AI project client.') -param useAIProjectClientFlag string = 'false' - -@description('Required. Name of the AI Foundry project.') -param aiFoundryName string - -@description('Required. Application Insights connection string.') -param applicationInsightsConnectionString string - -@description('Required. Connection name for Azure Cognitive Search.') -param aiSearchProjectConnectionName string - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -// var webAppImageName = 'DOCKER|byoaiacontainer.azurecr.io/byoaia-app:latest' - -// var webAppImageName = 'DOCKER|ncwaappcontainerreg1.azurecr.io/ncqaappimage:v1.0.0' - -var webAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:${imageTag}' - -@description('Optional. Resource ID of the existing AI Foundry project.') -param azureExistingAIProjectResourceId string = '' - -var existingAIServiceSubscription = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[2] - : subscription().subscriptionId -var existingAIServiceResourceGroup = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[4] - : resourceGroup().name -var existingAIServicesName = !empty(azureExistingAIProjectResourceId) - ? split(azureExistingAIProjectResourceId, '/')[8] - : '' -var existingAIProjectName = !empty(azureExistingAIProjectResourceId) ? split(azureExistingAIProjectResourceId, '/')[10] : '' - -resource hostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { - name: hostingPlanName - location: solutionLocation - sku: { - name: hostingPlanSku - } - properties: { - name: hostingPlanName - reserved: true - } - kind: 'linux' - tags: tags -} - -resource website 'Microsoft.Web/sites@2020-06-01' = { - name: websiteName - location: solutionLocation - identity: { - type: 'SystemAssigned, UserAssigned' - userAssignedIdentities: { - '${userassignedIdentityId}': {} - } - } - properties: { - serverFarmId: hostingPlanName - siteConfig: { - appSettings: [ - { - name: 'APP_ENV' - value: appEnvironment - } - { - name: 'APPINSIGHTS_INSTRUMENTATIONKEY' - value: reference(applicationInsightsId, '2015-05-01').InstrumentationKey - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: applicationInsightsConnectionString - } - { - name: 'AZURE_SEARCH_SERVICE' - value: azureSearchService - } - { - name: 'AZURE_SEARCH_INDEX' - value: azureSearchIndex - } - { - name: 'AZURE_SEARCH_USE_SEMANTIC_SEARCH' - value: azureSearchUseSemanticSearch - } - { - name: 'AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG' - value: azureSearchSemanticSearchConfig - } - { - name: 'AZURE_SEARCH_TOP_K' - value: azureSearchTopK - } - { - name: 'AZURE_SEARCH_ENABLE_IN_DOMAIN' - value: azureSearchEnableInDomain - } - { - name: 'AZURE_SEARCH_CONTENT_COLUMNS' - value: azureSearchContentColumns - } - { - name: 'AZURE_SEARCH_FILENAME_COLUMN' - value: azureSearchFilenameColumn - } - { - name: 'AZURE_SEARCH_TITLE_COLUMN' - value: azureSearchTitleColumn - } - { - name: 'AZURE_SEARCH_URL_COLUMN' - value: azureSearchUrlColumn - } - { - name: 'AZURE_OPENAI_RESOURCE' - value: azureOpenAIResource - } - { - name: 'AZURE_OPENAI_MODEL' - value: azureOpenAIModel - } - { - name: 'AZURE_OPENAI_ENDPOINT' - value: azureOpenAIEndpoint - } - { - name: 'AZURE_OPENAI_TEMPERATURE' - value: azureOpenAITemperature - } - { - name: 'AZURE_OPENAI_TOP_P' - value: azureOpenAITopP - } - { - name: 'AZURE_OPENAI_MAX_TOKENS' - value: azureOpenAIMaxTokens - } - { - name: 'AZURE_OPENAI_STOP_SEQUENCE' - value: azureOpenAIStopSequence - } - { - name: 'AZURE_OPENAI_SYSTEM_MESSAGE' - value: azureOpenAISystemMessage - } - { - name: 'AZURE_OPENAI_PREVIEW_API_VERSION' - value: azureOpenAIApiVersion - } - { - name: 'AZURE_OPENAI_STREAM' - value: azureOpenAIStream - } - { - name: 'AZURE_SEARCH_QUERY_TYPE' - value: azureSearchQueryType - } - { - name: 'AZURE_SEARCH_VECTOR_COLUMNS' - value: azureSearchVectorFields - } - { - name: 'AZURE_SEARCH_PERMITTED_GROUPS_COLUMN' - value: azureSearchPermittedGroupsField - } - { - name: 'AZURE_SEARCH_STRICTNESS' - value: azureSearchStrictness - } - { - name: 'AZURE_OPENAI_EMBEDDING_NAME' - value: azureOpenAIEmbeddingName - } - { - name: 'AZURE_OPENAI_EMBEDDING_ENDPOINT' - value: azureOpenAIEmbeddingEndpoint - } - { - name: 'SQLDB_SERVER' - value: SQLDB_SERVER - } - { - name: 'SQLDB_DATABASE' - value: SQLDB_DATABASE - } - { - name: 'USE_INTERNAL_STREAM' - value: USE_INTERNAL_STREAM - } - { - name: 'AZURE_COSMOSDB_ACCOUNT' - value: AZURE_COSMOSDB_ACCOUNT - } - { - name: 'AZURE_COSMOSDB_CONVERSATIONS_CONTAINER' - value: AZURE_COSMOSDB_CONVERSATIONS_CONTAINER - } - { - name: 'AZURE_COSMOSDB_DATABASE' - value: AZURE_COSMOSDB_DATABASE - } - { - name: 'AZURE_COSMOSDB_ENABLE_FEEDBACK' - value: AZURE_COSMOSDB_ENABLE_FEEDBACK - } - //{name: 'VITE_POWERBI_EMBED_URL' - // value: VITE_POWERBI_EMBED_URL - //} - { - name: 'SQLDB_USER_MID' - value: userassignedIdentityClientId - } - { - name: 'AZURE_AI_SEARCH_ENDPOINT' - value: azureSearchServiceEndpoint - } - { - name: 'AZURE_SQL_SYSTEM_PROMPT' - value: sqlSystemPrompt - } - { - name: 'AZURE_CALL_TRANSCRIPT_SYSTEM_PROMPT' - value: callTranscriptSystemPrompt - } - { - name: 'AZURE_OPENAI_STREAM_TEXT_SYSTEM_PROMPT' - value: streamTextSystemPrompt - } - { - name: 'USE_AI_PROJECT_CLIENT' - value: useAIProjectClientFlag - } - { - name: 'AZURE_AI_AGENT_ENDPOINT' - value: aiFoundryProjectEndpoint - } - { - name: 'AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME' - value: azureOpenAIModel - } - { - name: 'AZURE_AI_AGENT_API_VERSION' - value: azureOpenAIApiVersion - } - { - name: 'AZURE_SEARCH_CONNECTION_NAME' - value: aiSearchProjectConnectionName - } - ] - linuxFxVersion: webAppImageName - } - } - tags: tags - dependsOn: [hostingPlan] -} - -// resource ApplicationInsights 'Microsoft.Insights/components@2020-02-02' = { -// name: ApplicationInsightsName -// location: resourceGroup().location -// tags: { -// 'hidden-link:${resourceId('Microsoft.Web/sites',ApplicationInsightsName)}': 'Resource' -// } -// properties: { -// Application_Type: 'web' -// } -// kind: 'web' -// } - -resource contributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-05-15' existing = { - name: '${AZURE_COSMOSDB_ACCOUNT}/00000000-0000-0000-0000-000000000002' -} - -module cosmosUserRole 'core/database/cosmos/cosmos-role-assign.bicep' = { - name: 'cosmos-sql-user-role-${websiteName}' - params: { - accountName: AZURE_COSMOSDB_ACCOUNT - roleDefinitionId: contributorRoleDefinition.id - principalId: website.identity.principalId - } - dependsOn: [ - website - ] -} - -resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: aiFoundryName - scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) -} - -@description('This is the built-in Azure AI User role.') -resource aiUserRoleDefinitionFoundry 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - scope: aiFoundry - name: '53ca6127-db72-4b80-b1b0-d745d6d5456d' -} - -resource assignAiUserRoleToAiProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (empty(azureExistingAIProjectResourceId)) { - name: guid(resourceGroup().id, aiFoundry.id, aiUserRoleDefinitionFoundry.id) - // scope: aiProject - properties: { - principalId: website.identity.principalId - roleDefinitionId: aiUserRoleDefinitionFoundry.id - principalType: 'ServicePrincipal' - } -} - -module assignAiUserRoleToAiProjectExisting 'deploy_foundry_model_role_assignment.bicep' = if (!empty(azureExistingAIProjectResourceId)) { - name: 'assignAiUserRoleToAiProjectExisting' - scope: resourceGroup(existingAIServiceSubscription, existingAIServiceResourceGroup) - params: { - principalId: website.identity.principalId - roleDefinitionId: aiUserRoleDefinitionFoundry.id - roleAssignmentName: guid(website.name, aiFoundry.id, aiUserRoleDefinitionFoundry.id) - aiFoundryName: !empty(azureExistingAIProjectResourceId) ? existingAIServicesName : aiFoundryName - aiProjectName: existingAIProjectName - tags: tags - } -} - -@description('URL of the deployed web application.') -output webAppUrl string = 'https://${websiteName}.azurewebsites.net' - -@description('Name of the deployed web application.') -output webAppName string = websiteName diff --git a/infra/old/deploy_cosmos_db.bicep b/infra/old/deploy_cosmos_db.bicep deleted file mode 100644 index 58eacf26c..000000000 --- a/infra/old/deploy_cosmos_db.bicep +++ /dev/null @@ -1,84 +0,0 @@ - -@minLength(3) -@maxLength(20) -@description('Required. Solution location.') -param solutionLocation string - -@description('Required. Name of the Azure Cosmos DB account.') -param cosmosDBName string - -@description('Optional. Name of the Cosmos DB database.') -param databaseName string = 'db_conversation_history' - -@description('Optional.Name of the Cosmos DB container (collection).') -param collectionName string = 'conversations' - -@description('Optional. List of Cosmos DB containers to be created.') -param containers array = [ - { - name: collectionName - id: collectionName - partitionKey: '/userId' - } -] - -@description('Optional. The API kind of the Cosmos DB account.') -@allowed([ 'GlobalDocumentDB', 'MongoDB', 'Parse' ]) -param kind string = 'GlobalDocumentDB' - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-08-15' = { - name: cosmosDBName - kind: kind - location: solutionLocation - tags: tags - properties: { - consistencyPolicy: { defaultConsistencyLevel: 'Session' } - locations: [ - { - locationName: solutionLocation - failoverPriority: 0 - isZoneRedundant: false - } - ] - databaseAccountOfferType: 'Standard' - enableAutomaticFailover: false - enableMultipleWriteLocations: false - disableLocalAuth: true - apiProperties: (kind == 'MongoDB') ? { serverVersion: '4.0' } : {} - capabilities: [ { name: 'EnableServerless' } ] - } -} - - -resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = { - name: '${cosmosDBName}/${databaseName}' - properties: { - resource: { id: databaseName } - } - tags: tags - resource list 'containers' = [for container in containers: { - name: container.name - properties: { - resource: { - id: container.id - partitionKey: { paths: [ container.partitionKey ] } - } - options: {} - } - }] - - dependsOn: [ - cosmos - ] -} -@description('Name of the Cosmos DB account.') -output cosmosAccountName string = cosmos.name - -@description('Name of the Cosmos DB database.') -output cosmosDatabaseName string = databaseName - -@description('Name of the Cosmos DB container.') -output cosmosContainerName string = collectionName diff --git a/infra/old/deploy_foundry_model_role_assignment.bicep b/infra/old/deploy_foundry_model_role_assignment.bicep deleted file mode 100644 index b4da8843c..000000000 --- a/infra/old/deploy_foundry_model_role_assignment.bicep +++ /dev/null @@ -1,63 +0,0 @@ -@description('Optional. Principal ID to assign the role to.') -param principalId string = '' - -@description('Required. ID of the role definition to assign.') -param roleDefinitionId string - -@description('Optional. Name of the role assignment.') -param roleAssignmentName string = '' - -@description('Required. Name of the AI Foundry resource.') -param aiFoundryName string - -@description('Optional. Name of the AI project.') -param aiProjectName string = '' - -@description('Optional. List of AI model deployments.') -param aiModelDeployments array = [] - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { - name: aiFoundryName -} - -// Call the model deployments module -@batchSize(1) -resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = [for aiModeldeployment in aiModelDeployments: if (!empty(aiModelDeployments)) { - parent: aiServices - name: aiModeldeployment.name - properties: { - model: { - format: 'OpenAI' - name: aiModeldeployment.model - } - raiPolicyName: aiModeldeployment.raiPolicyName - } - sku: { - name: aiModeldeployment.sku.name - capacity: aiModeldeployment.sku.capacity - } - tags : tags -}] - -resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { - name: aiProjectName - parent: aiServices -} - -resource roleAssignmentToFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: roleAssignmentName - scope: aiServices - properties: { - roleDefinitionId: roleDefinitionId - principalId: principalId - principalType: 'ServicePrincipal' - } -} -@description('Principal ID of the AI Services resource.') -output aiServicesPrincipalId string = aiServices.identity.principalId - -@description('Principal ID of the AI Project resource if defined.') -output aiProjectPrincipalId string = !empty(aiProjectName) ? aiProject.identity.principalId : '' diff --git a/infra/old/deploy_keyvault.bicep b/infra/old/deploy_keyvault.bicep deleted file mode 100644 index d8141baa8..000000000 --- a/infra/old/deploy_keyvault.bicep +++ /dev/null @@ -1,123 +0,0 @@ -// ========== Key Vault ========== // -targetScope = 'resourceGroup' - -@description('Required. Solution Name') -param solutionName string - -@description('Required. Solution Location') -param solutionLocation string - -@description('Optional. Current UTC timestamp.') -param utc string = utcNow() - -@description('Required. Name of the Azure Key Vault.') -param kvName string - -@description('Optional. Specifies the create mode for the resource.') -param createMode string = 'default' - -@description('Optional. Enabled For Deployment. Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.') -param enableForDeployment bool = true - -@description('Optional. Enabled For Disk Encryption. Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.') -param enableForDiskEncryption bool = true - -@description('Optional. Enabled For Template Deployment. Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault.') -param enableForTemplateDeployment bool = true - -@description('Optional. Enable RBAC Authorization. Property that controls how data actions are authorized.') -param enableRBACAuthorization bool = true - -@description('Optional. Soft Delete Retention in Days. softDelete data retention days. It accepts >=7 and <=90.') -param softDeleteRetentionInDays int = 7 - -@description('Optional. Public Network Access, Property to specify whether the vault will accept traffic from public internet.') -@allowed([ - 'enabled' - 'disabled' -]) -param publicNetworkAccess string = 'enabled' - -@description('Optional. SKU') -@allowed([ - 'standard' - 'premium' -]) -param sku string = 'standard' - -@description('Optional. Vault URI. The URI of the vault for performing operations on keys and secrets.') -var vaultUri = 'https://${ kvName }.vault.azure.net/' - -@description('Required. Object ID of the managed identity.') -param managedIdentityObjectId string - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { - name: kvName - location: solutionLocation - tags: { - ...tags - app: solutionName - location: solutionLocation - } - properties: { - accessPolicies: [ - { - objectId: managedIdentityObjectId - permissions: { - certificates: [ - 'all' - ] - keys: [ - 'all' - ] - secrets: [ - 'all' - ] - storage: [ - 'all' - ] - } - tenantId: subscription().tenantId - } - ] - createMode: createMode - enabledForDeployment: enableForDeployment - enabledForDiskEncryption: enableForDiskEncryption - enabledForTemplateDeployment: enableForTemplateDeployment - enableRbacAuthorization: enableRBACAuthorization - softDeleteRetentionInDays: softDeleteRetentionInDays - provisioningState: 'RegisteringDns' - publicNetworkAccess: publicNetworkAccess - sku: { - family: 'A' - name: sku - } - tenantId: subscription().tenantId - vaultUri: vaultUri - } -} - -@description('This is the built-in Key Vault Administrator role.') -resource kvAdminRole 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { - scope: resourceGroup() - name: '00482a5a-887f-4fb3-b363-3b7fe8e74483' -} - -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(resourceGroup().id, managedIdentityObjectId, kvAdminRole.id) - properties: { - principalId: managedIdentityObjectId - roleDefinitionId:kvAdminRole.id - principalType: 'ServicePrincipal' - } -} - -@description('Name of the Key Vault.') -output keyvaultName string = keyVault.name - -@description('Resource ID of the Key Vault.') -output keyvaultId string = keyVault.id - diff --git a/infra/old/deploy_managed_identity.bicep b/infra/old/deploy_managed_identity.bicep deleted file mode 100644 index 16746b2e7..000000000 --- a/infra/old/deploy_managed_identity.bicep +++ /dev/null @@ -1,112 +0,0 @@ -// ========== Managed Identity ========== // -targetScope = 'resourceGroup' - -@minLength(3) -@maxLength(15) -@description('Required. Name of the solution.') -param solutionName string - -@description('Required. Deployment location for the solution.') -param solutionLocation string - -@description('Required. Name of the managed identity.') -param miName string - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: miName - location: solutionLocation - tags: { - ...tags - app: solutionName - location: solutionLocation - } -} - -@description('This is the built-in owner role. See https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#owner') -resource ownerRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { - scope: resourceGroup() - name: '8e3af657-a8ff-443c-a75c-2fe8c4bcb635' -} - -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(resourceGroup().id, managedIdentity.id, ownerRoleDefinition.id) - properties: { - principalId: managedIdentity.properties.principalId - roleDefinitionId: ownerRoleDefinition.id - principalType: 'ServicePrincipal' - } -} - -resource managedIdentityWebApp 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: '${miName}-webapp' - location: solutionLocation - tags: { - ...tags - app: solutionName - location: solutionLocation - } -} - - -// @description('Array of actions for the roleDefinition') -// param actions array = [ -// 'Microsoft.Synapse/workspaces/write' -// 'Microsoft.Synapse/workspaces/read' -// ] - -// @description('Array of notActions for the roleDefinition') -// param notActions array = [] - -// @description('Friendly name of the role definition') -// param roleName string = 'Synapse Administrator-${solutionName}' - -// @description('Detailed description of the role definition') -// param roleDescription string = 'Synapse Administrator-${solutionName}' - -// var roleDefName = guid(resourceGroup().id, string(actions), string(notActions)) - -// resource synadminRoleDef 'Microsoft.Authorization/roleDefinitions@2018-07-01' = { -// name: roleDefName -// properties: { -// roleName: roleName -// description: roleDescription -// type: 'customRole' -// permissions: [ -// { -// actions: actions -// notActions: notActions -// } -// ] -// assignableScopes: [ -// resourceGroup().id -// ] -// } -// } - -// resource synAdminroleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { -// name: guid(resourceGroup().id, managedIdentity.id, synadminRoleDef.id) -// properties: { -// principalId: managedIdentity.properties.principalId -// roleDefinitionId: synadminRoleDef.id -// principalType: 'ServicePrincipal' -// } -// } - -@description('Details of the managed identity resource.') -output managedIdentityOutput object = { - id: managedIdentity.id - objectId: managedIdentity.properties.principalId - clientId: managedIdentity.properties.clientId - name: miName -} - -@description('Details of the managed identity for the web app.') -output managedIdentityWebAppOutput object = { - id: managedIdentityWebApp.id - objectId: managedIdentityWebApp.properties.principalId - clientId: managedIdentityWebApp.properties.clientId - name: managedIdentityWebApp.name -} diff --git a/infra/old/deploy_sql_db.bicep b/infra/old/deploy_sql_db.bicep deleted file mode 100644 index e2a964b2c..000000000 --- a/infra/old/deploy_sql_db.bicep +++ /dev/null @@ -1,110 +0,0 @@ -@description('Required. Deployment location for the solution.') -param solutionLocation string - -@description('Required. Name of the Azure Key Vault.') -param keyVaultName string - -@description('Required. Object ID of the managed identity.') -param managedIdentityObjectId string - -@description('Required. Name of the managed identity.') -param managedIdentityName string - -@description('Required. The name of the SQL logical server.') -param serverName string - -@description('Required. The name of the SQL Database.') -param sqlDBName string - -@description('Required. Location for all resources.') -param location string = solutionLocation - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { - name: serverName - location: location - kind:'v12.0' - properties: { - publicNetworkAccess: 'Enabled' - version: '12.0' - restrictOutboundNetworkAccess: 'Disabled' - minimalTlsVersion: '1.2' - administrators: { - login: managedIdentityName - sid: managedIdentityObjectId - tenantId: subscription().tenantId - administratorType: 'ActiveDirectory' - azureADOnlyAuthentication: true - } - } - tags: tags -} - -resource firewallRule 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { - name: 'AllowSpecificRange' - parent: sqlServer - properties: { - startIpAddress: '0.0.0.0' - endIpAddress: '255.255.255.255' - } -} - -resource AllowAllWindowsAzureIps 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { - name: 'AllowAllWindowsAzureIps' - parent: sqlServer - properties: { - startIpAddress: '0.0.0.0' - endIpAddress: '0.0.0.0' - } -} - -resource sqlDB 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { - parent: sqlServer - name: sqlDBName - location: location - sku: { - name: 'GP_S_Gen5' - tier: 'GeneralPurpose' - family: 'Gen5' - capacity: 2 - } - kind:'v12.0,user,vcore,serverless' - properties: { - collation: 'SQL_Latin1_General_CP1_CI_AS' - autoPauseDelay:60 - minCapacity:1 - readScale: 'Disabled' - zoneRedundant: false - } - tags: tags -} - -resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: keyVaultName -} - -resource sqldbServerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'SQLDB-SERVER' - properties: { - value: '${serverName}.database.windows.net' - } - tags: tags -} - -resource sqldbDatabaseEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'SQLDB-DATABASE' - properties: { - value: sqlDBName - } - tags: tags -} -@description('Name of the SQL logical server.') -output sqlServerName string = serverName - -@description('Name of the SQL database.') -output sqlDbName string = sqlDBName -// output sqlDbUser string = administratorLogin diff --git a/infra/old/deploy_storage_account.bicep b/infra/old/deploy_storage_account.bicep deleted file mode 100644 index 9f269d39b..000000000 --- a/infra/old/deploy_storage_account.bicep +++ /dev/null @@ -1,129 +0,0 @@ -// ========== Storage Account ========== // -targetScope = 'resourceGroup' - -@description('Required. Deployment location for the solution.') -param solutionLocation string - -@description('Required. Name of the storage account.') -param saName string - -@description('Required. Object ID of the managed identity.') -param managedIdentityObjectId string - -@description('Required. Name of the Azure Key Vault.') -param keyVaultName string - -@description('Optional. Tags to be applied to the resources.') -param tags object = {} - -resource storageAccounts_resource 'Microsoft.Storage/storageAccounts@2022-09-01' = { - name: saName - location: solutionLocation - sku: { - name: 'Standard_LRS' - tier: 'Standard' - } - kind: 'StorageV2' - properties: { - minimumTlsVersion: 'TLS1_2' - allowBlobPublicAccess: false - isHnsEnabled: true - networkAcls: { - bypass: 'AzureServices' - virtualNetworkRules: [] - ipRules: [] - defaultAction: 'Allow' - } - supportsHttpsTrafficOnly: true - encryption: { - services: { - file: { - keyType: 'Account' - enabled: true - } - blob: { - keyType: 'Account' - enabled: true - } - } - keySource: 'Microsoft.Storage' - } - accessTier: 'Hot' - allowSharedKeyAccess: false - } - tags : tags -} - -resource storageAccounts_default 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01' = { - parent: storageAccounts_resource - name: 'default' - properties: { - cors: { - corsRules: [] - } - deleteRetentionPolicy: { - allowPermanentDelete: false - enabled: false - } - } -} - - -resource storageAccounts_default_power_platform_dataflows 'Microsoft.Storage/storageAccounts/blobServices/containers@2022-09-01' = { - parent: storageAccounts_default - name: 'data' - properties: { - defaultEncryptionScope: '$account-encryption-key' - denyEncryptionScopeOverride: false - publicAccess: 'None' - } - dependsOn: [ - storageAccounts_resource - ] -} - -@description('This is the built-in Storage Blob Data Contributor.') -resource blobDataContributor 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = { - scope: resourceGroup() - name: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' -} - -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(resourceGroup().id, managedIdentityObjectId, blobDataContributor.id) - properties: { - principalId: managedIdentityObjectId - roleDefinitionId:blobDataContributor.id - principalType: 'ServicePrincipal' - } -} - - -//var storageAccountString = 'DefaultEndpointsProtocol=https;AccountName=${storageAccounts_resource.name};AccountKey=${storageAccounts_resource.listKeys().keys[0].value};EndpointSuffix=${environment().suffixes.storage}' - -resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { - name: keyVaultName -} - -resource adlsAccountNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'ADLS-ACCOUNT-NAME' - properties: { - value: saName - } - tags : tags -} - -resource adlsAccountContainerEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = { - parent: keyVault - name: 'ADLS-ACCOUNT-CONTAINER' - properties: { - value: 'data' - } - tags : tags -} - -@description('Name of the storage account.') -output storageName string = saName - -@description('Name of the default storage container.') -output storageContainer string = 'data' From e5d3464c219831f0662a5c816bf7277cdf8da5ab Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 12 Sep 2025 17:05:37 +0530 Subject: [PATCH 74/84] fix: Updated parameter name for Azure AI servicedeployment location --- .github/workflows/CAdeploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 6822facb9..1bc1c28a7 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -144,7 +144,7 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters aiDeploymentsLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ + --parameters azureAiServiceLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT" From 6704362ce3773a2216dd1f256f38bb8829ff56fd Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Fri, 12 Sep 2025 17:34:39 +0530 Subject: [PATCH 75/84] Updated paramters in CADeploy pipeline --- .github/workflows/CAdeploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 1bc1c28a7..3c42bd234 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -144,7 +144,7 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters azureAiServiceLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptDeploymentCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} imageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ + --parameters azureAiServiceLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptModelCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} containerImageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT" From f0bfe3b1acc5d0540c6fcc0ba188b08fcf695456 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Wed, 17 Sep 2025 18:46:04 +0530 Subject: [PATCH 76/84] update image tag to 'latest_waf' in deployment workflows and documentation --- .github/workflows/CAdeploy.yml | 4 ++-- .github/workflows/build-docker.yml | 2 +- docs/CustomizingAzdParameters.md | 2 +- docs/DeploymentGuide.md | 2 +- infra/main.bicep | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 3c42bd234..4d5db8cf4 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -127,10 +127,10 @@ jobs: id: determine_tag run: | BRANCH=${{ github.ref_name }} - if [[ "$BRANCH" == "main" ]]; then TAG="latest" + if [[ "$BRANCH" == "main" ]]; then TAG="latest_waf" elif [[ "$BRANCH" == "dev" ]]; then TAG="dev" elif [[ "$BRANCH" == "demo" ]]; then TAG="demo" - else TAG="latest"; fi + else TAG="latest_waf"; fi echo "IMAGE_TAG=$TAG" >> $GITHUB_ENV echo "Image Tag: $TAG" - name: Deploy and extract values from deployment output diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9918750a7..5cdb4dae2 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -52,7 +52,7 @@ jobs: id: determine_tag run: | if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "tagname=latest" >> $GITHUB_OUTPUT + echo "tagname=latest_waf" >> $GITHUB_OUTPUT elif [[ "${{ github.ref_name }}" == "dev" ]]; then echo "tagname=dev" >> $GITHUB_OUTPUT elif [[ "${{ github.ref_name }}" == "demo" ]]; then diff --git a/docs/CustomizingAzdParameters.md b/docs/CustomizingAzdParameters.md index d37917948..1112a84f8 100644 --- a/docs/CustomizingAzdParameters.md +++ b/docs/CustomizingAzdParameters.md @@ -17,7 +17,7 @@ By default this template will use the environment name as the prefix to prevent | `AZURE_ENV_MODEL_CAPACITY` | integer | `30` | Set the model capacity for GPT deployment. Choose based on your Azure quota and usage needs. | | `AZURE_ENV_EMBEDDING_MODEL_NAME` | string | `text-embedding-ada-002` | Set the model name used for embeddings. | | `AZURE_ENV_EMBEDDING_MODEL_CAPACITY` | integer | `80` | Set the capacity for embedding model deployment. | -| `AZURE_ENV_IMAGETAG` | string | `latest` | Set the image tag (allowed values: `latest`, `dev`, `hotfix`). | +| `AZURE_ENV_IMAGETAG` | string | `latest_waf` | Set the image tag (allowed values: `latest_waf`, `dev`, `hotfix`). | | `AZURE_LOCATION` | string | `` | Sets the Azure region for resource deployment. | | `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | Guide to get your [Existing Workspace ID](/docs/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of provisioning a new one. | | `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` | string | `` | Reuses an existing AI Foundry Project Resource Id instead of provisioning a new one. | diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md index 974dc6017..449db6bfe 100644 --- a/docs/DeploymentGuide.md +++ b/docs/DeploymentGuide.md @@ -148,7 +148,7 @@ When you start the deployment, most parameters will have **default values**, but | **GPT Model Deployment Capacity** | Configure capacity for **GPT models**. Choose based on Azure OpenAI quota. | `30` | | **Embedding Model** | OpenAI embedding model used for vector similarity. | `text-embedding-ada-002` | | **Embedding Model Capacity** | Set the capacity for **embedding models**. Choose based on usage and quota. | `80` | -| **Image Tag** | The version of the Docker image to use (e.g., `latest`, `dev`, `hotfix`). | `latest` | +| **Image Tag** | The version of the Docker image to use (e.g., `latest_waf`, `dev`, `hotfix`). | `latest_waf` | | **Azure OpenAI API Version** | Set the API version for OpenAI model deployments. | `2025-04-01-preview` | | **AZURE_LOCATION** | Sets the Azure region for resource deployment. | `` | | **Existing Log Analytics Workspace** | To reuse an existing Log Analytics Workspace ID instead of creating a new one. | *(empty)* | diff --git a/infra/main.bicep b/infra/main.bicep index 9f06f99f0..24d3ee71d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -122,7 +122,7 @@ param containerRegistryHostname string = 'bycwacontainerreg.azurecr.io' param containerImageName string = 'byc-wa-app' @description('Optional. The Container Image Tag to deploy on the webapp.') -param containerImageTag string = 'latest' +param containerImageTag string = 'latest_waf' @description('Optional. Resource ID of an existing Foundry project') param existingFoundryProjectResourceId string = '' From 44a7e57d677541466538f66e5692d3cb1cae5730 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Wed, 17 Sep 2025 18:49:27 +0530 Subject: [PATCH 77/84] rebuild main.json --- infra/main.json | 118 +++++++++++------------------------------------- 1 file changed, 26 insertions(+), 92 deletions(-) diff --git a/infra/main.json b/infra/main.json index d81dc2a6f..da06bcd27 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "1330795539540033187" + "templateHash": "8667168677735334522" } }, "parameters": { @@ -152,7 +152,7 @@ }, "enableMonitoring": { "type": "bool", - "defaultValue": true, + "defaultValue": false, "metadata": { "description": "Optional. Enable monitoring applicable resources, aligned with the Well Architected Framework recommendations. This setting enables Application Insights and Log Analytics and configures all the resources applicable resources to send logs. Defaults to false." } @@ -194,7 +194,7 @@ }, "containerImageTag": { "type": "string", - "defaultValue": "latest", + "defaultValue": "latest_waf", "metadata": { "description": "Optional. The Container Image Tag to deploy on the webapp." } @@ -279,7 +279,7 @@ "useInternalStream": "True", "useAIProjectClientFlag": "False", "sqlServerFqdn": "[format('sql-{0}.database.windows.net', variables('solutionSuffix'))]", - "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n For asset values: if question is about total \\\"asset value\\\"/\\\"portfolio value\\\"/\\\"AUM\\\" → return SUM of latest investments; if about \\\"current asset/investment value\\\" → return all latest investments without SUM.\n Only return the generated SQL query. Do not return anything else.", + "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\n Only return the generated SQL query. Do not return anything else.", "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \n You have access to the client’s past meeting call transcripts. \n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \n If no data is available, state 'No relevant data found for previous meetings.", "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\n Always send clientId as '{client_id}'.", "replicaRegionPairs": { @@ -308,7 +308,6 @@ "westeurope": "northeurope" }, "allTags": "[union(createObject('azd-env-name', parameters('solutionName')), parameters('tags'))]", - "resourcesName": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", "cosmosDbHaLocation": "[variables('cosmosDbZoneRedundantHaRegionPairs')[resourceGroup().location]]", "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", "existingLawSubscription": "[if(variables('useExistingLogAnalytics'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[2], '')]", @@ -4777,7 +4776,7 @@ "condition": "[parameters('enablePrivateNetworking')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[take(format('network-{0}-deployment', variables('resourcesName')), 64)]", + "name": "[take(format('network-{0}-deployment', variables('solutionSuffix')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -4785,7 +4784,7 @@ "mode": "Incremental", "parameters": { "resourcesName": { - "value": "[variables('resourcesName')]" + "value": "[variables('solutionSuffix')]" }, "logAnalyticsWorkSpaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value))]", "vmAdminUsername": { @@ -27026,7 +27025,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "7757141333856577130" + "templateHash": "9573727846743928038" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service." @@ -28259,7 +28258,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "13864482829550647329" + "templateHash": "16444475951283055894" } }, "definitions": { @@ -30228,7 +30227,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "7781450680156271399" + "templateHash": "346451728741152022" } }, "definitions": { @@ -30352,7 +30351,7 @@ "aiProjectInfo": { "$ref": "#/definitions/aiProjectOutputType", "metadata": { - "description": "AI Project metadata including name, resource ID, and API endpoint." + "description": "AI Project metadata including name, resource ID, and API endpoint, and SystemAssignedManagedIdentity Principal Id." }, "value": { "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", @@ -30465,7 +30464,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "13864482829550647329" + "templateHash": "16444475951283055894" } }, "definitions": { @@ -32434,7 +32433,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "7781450680156271399" + "templateHash": "346451728741152022" } }, "definitions": { @@ -32558,7 +32557,7 @@ "aiProjectInfo": { "$ref": "#/definitions/aiProjectOutputType", "metadata": { - "description": "AI Project metadata including name, resource ID, and API endpoint." + "description": "AI Project metadata including name, resource ID, and API endpoint, and SystemAssignedManagedIdentity Principal Id." }, "value": { "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", @@ -32691,9 +32690,9 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "logAnalyticsWorkspace", "network", "userAssignedIdentity" @@ -32703,7 +32702,6 @@ "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[take(format('avm.res.document-db.database-account.{0}', variables('cosmosDbResourceName')), 64)]", - "resourceGroup": "[resourceGroup().name]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -36544,7 +36542,6 @@ "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[take(format('avm.res.storage.storage-account.{0}', variables('storageAccountName')), 64)]", - "resourceGroup": "[resourceGroup().name]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -45509,66 +45506,22 @@ "databases": { "value": [ { - "availabilityZone": 1, - "backupLongTermRetentionPolicy": { - "monthlyRetention": "P6M" - }, - "backupShortTermRetentionPolicy": { - "retentionDays": 14 - }, + "availabilityZone": "[if(parameters('enableRedundancy'), 1, -1)]", "collation": "SQL_Latin1_General_CP1_CI_AS", "diagnosticSettings": "[if(parameters('enableMonitoring'), createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value))), null())]", - "elasticPoolResourceId": "[resourceId('Microsoft.Sql/servers/elasticPools', format('sql-{0}', variables('solutionSuffix')), 'sqlswaf-ep-001')]", "licenseType": "LicenseIncluded", "maxSizeBytes": 34359738368, "name": "[format('sqldb-{0}', variables('solutionSuffix'))]", + "minCapacity": "1", "sku": { - "capacity": 0, - "name": "ElasticPool", - "tier": "GeneralPurpose" + "name": "GP_S_Gen5", + "tier": "GeneralPurpose", + "family": "Gen5", + "capacity": 2 } } ] }, - "elasticPools": { - "value": [ - { - "availabilityZone": -1, - "name": "sqlswaf-ep-001", - "sku": { - "capacity": 10, - "name": "GP_Gen5", - "tier": "GeneralPurpose" - }, - "roleAssignments": [ - { - "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "db_datareader" - }, - { - "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", - "principalType": "ServicePrincipal", - "roleDefinitionIdOrName": "db_datawriter" - } - ] - } - ] - }, - "firewallRules": { - "value": [ - { - "endIpAddress": "255.255.255.255", - "name": "AllowSpecificRange", - "startIpAddress": "0.0.0.0" - }, - { - "endIpAddress": "0.0.0.0", - "name": "AllowAllWindowsAzureIps", - "startIpAddress": "0.0.0.0" - } - ] - }, "location": { "value": "[variables('solutionLocation')]" }, @@ -45584,27 +45537,9 @@ "value": "[reference('userAssignedIdentity').outputs.resourceId.value]" }, "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').sqlServer)).outputs.resourceId.value))), 'service', 'sqlServer', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value, 'tags', parameters('tags')))), createObject('value', createArray()))]", - "restrictOutboundNetworkAccess": { - "value": "Disabled" - }, - "securityAlertPolicies": { - "value": [ - { - "emailAccountAdmins": true, - "name": "Default", - "state": "Enabled" - } - ] - }, + "firewallRules": "[if(not(parameters('enablePrivateNetworking')), createObject('value', createArray(createObject('endIpAddress', '255.255.255.255', 'name', 'AllowSpecificRange', 'startIpAddress', '0.0.0.0'), createObject('endIpAddress', '0.0.0.0', 'name', 'AllowAllWindowsAzureIps', 'startIpAddress', '0.0.0.0'))), createObject('value', createArray()))]", "tags": { "value": "[parameters('tags')]" - }, - "virtualNetworkRules": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('ignoreMissingVnetServiceEndpoint', true(), 'name', 'newVnetRule1', 'virtualNetworkSubnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', createArray()))]", - "vulnerabilityAssessmentsObj": { - "value": { - "name": "default", - "storageAccountResourceId": "[reference('avmStorageAccount').outputs.resourceId.value]" - } } }, "template": { @@ -52179,7 +52114,6 @@ }, "dependsOn": [ "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').sqlServer)]", - "avmStorageAccount", "logAnalyticsWorkspace", "network", "userAssignedIdentity" @@ -52855,8 +52789,9 @@ "vnetRouteAllEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", "vnetImagePullEnabled": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", "virtualNetworkSubnetId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('network').outputs.subnetWebResourceId.value), createObject('value', null()))]", - "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", - "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('webSiteResourceName')), 'customNetworkInterfaceName', format('nic-{0}', variables('webSiteResourceName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').appService)).outputs.resourceId.value))), 'service', 'sites', 'subnetResourceId', reference('network').outputs.subnetPrivateEndpointsResourceId.value))), createObject('value', null()))]" + "publicNetworkAccess": { + "value": "Enabled" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -54840,7 +54775,6 @@ "dependsOn": [ "aiFoundryAiServices", "applicationInsights", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').appService)]", "cosmosDb", "logAnalyticsWorkspace", "network", @@ -57682,7 +57616,7 @@ "metadata": { "description": "The name of the Azure AI Search connection." }, - "value": "[format('foundry-search-connection-{0}', variables('solutionSuffix'))]" + "value": "[variables('aiSearchName')]" }, "AZURE_SEARCH_CONTENT_COLUMNS": { "type": "string", From ff922b0966d82c07238c92d3a207f4b94ed83d4b Mon Sep 17 00:00:00 2001 From: Roopan-Microsoft <168007406+Roopan-Microsoft@users.noreply.github.com> Date: Wed, 17 Sep 2025 19:07:41 +0530 Subject: [PATCH 78/84] Update main.parameters.json --- infra/main.parameters.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 2b2c3a1fd..092e5a315 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -27,7 +27,7 @@ "value": "${AZURE_ENV_EMBEDDING_MODEL_CAPACITY}" }, "imageTag": { - "value": "${AZURE_ENV_IMAGETAG}" + "value": "${AZURE_ENV_IMAGETAG=latest_waf}" }, "location": { "value": "${AZURE_LOCATION}" @@ -44,4 +44,4 @@ } } } -} \ No newline at end of file +} From 25067ef77e6327342e147052679315744f19ab8a Mon Sep 17 00:00:00 2001 From: Roopan-Microsoft <168007406+Roopan-Microsoft@users.noreply.github.com> Date: Wed, 17 Sep 2025 19:08:01 +0530 Subject: [PATCH 79/84] Update main.waf.parameters.json --- infra/main.waf.parameters.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index b92d12734..fcdb73193 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -27,7 +27,7 @@ "value": "${AZURE_ENV_EMBEDDING_MODEL_CAPACITY}" }, "imageTag": { - "value": "${AZURE_ENV_IMAGETAG}" + "value": "${AZURE_ENV_IMAGETAG=latest_waf}" }, "location": { "value": "${AZURE_LOCATION}" @@ -62,4 +62,4 @@ "value": "${AZURE_ENV_VM_ADMIN_PASSWORD}" } } -} \ No newline at end of file +} From 4f16d5614f0405cfe4e4f8e6f9d415385197a41f Mon Sep 17 00:00:00 2001 From: Kanchan-Microsoft Date: Wed, 17 Sep 2025 20:06:27 +0530 Subject: [PATCH 80/84] chore: Fixed Security Vulnerablities (#671) * chore: Dev to Main changes merge (#657) * model deployment when reusing existing foundry * change model deployment name * removed system managed identity code * seggregated the files * fix: Updated file names * chore: down merging main into dev (#638) * fixed opent telemetry issue CustomDomainInUse, FlagMustBeSetForRestore (#618) (#619) Co-authored-by: VishalS-Microsoft * directory update in dependabot template (#634) --------- Co-authored-by: NirajC-Microsoft Co-authored-by: VishalS-Microsoft Co-authored-by: Prajwal-Microsoft * added for cross-subscription existing AI project resource ID * update * fix: agent cleanup (#639) * Agent deletion handled successfully * use get azure credentials * remove unused import --------- Co-authored-by: Shreyas-Microsoft * TS( 21657) Bicep Standard code changes * Added required env variables for local debugging in .env generated by azd up * Added "Required/Optional" and changed aiProjectName suffix. * removed location tag * foundry project documentation * corrected name * updated CAdeploy.yml * update CAdeploy.yml * changed pipeline variable * updated main.json file * updated yml file * test * test * update * update * test * update CAdeploy.yml * CustomizingAzdparamMd * deploymentMdUpdate * Updated * Update infra/main.bicep Fixed typo error. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/App/backend/plugins/chat_with_data_plugin.py Updated the finally block for function "get_SQL_Response" Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update infra/main.bicep Removed Commented-out code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revert Copilot suggestion: `and project_client` is unnecessary. `project_client` is always initialized before thread creation, so `if thread:` is sufficient. * docs: Added Troubleshoot.md files for BYOC-Client Advisor (#650) * Added Troubleshoot.md files for BYOC-Client Advisor * fixed Check Markdown Broken Links * updated troubleshotingsteps file * Changed MACAE Github issues link with BYOC issue link --------- Co-authored-by: NirajC-Microsoft * added colon Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Replace 'u' with 'you' for proper grammarUpdate docs/TroubleShootingSteps.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * extra space removed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The command block is not properly closed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The command should be wrapped in a proper code block with language identifier for better formatting and readability. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The total asset value and current asset value are responding correctly, and the total value matches the asset value displayed in the left panel. (#651) * Bicep file changes to add CreatedBy tag (#655) * Update docs/TroubleShootingSteps.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: Adding a hardcoded createdBy parameter to the CAdeploy.yml file to prevent validation pipeline failures caused by the deployer function in the Bicep file. (#659) * Adding a hardcoded createdBy parameter to the CAdeploy.yml file to prevent validation pipeline failures caused by the deployer function in the Bicep file. * Consolidate parameters in CAdeploy.yml --------- Co-authored-by: Priyanka-Microsoft Co-authored-by: Prajwal D C Co-authored-by: Rafi-Microsoft Co-authored-by: VishalS-Microsoft Co-authored-by: Kanchan Nagshetti (Persistent Systems Inc) Co-authored-by: Shreyas-Microsoft Co-authored-by: Shreyas-Microsoft Co-authored-by: Bangarraju-Microsoft Co-authored-by: Prasanjeet-Microsoft Co-authored-by: UtkarshMishra-Microsoft Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Roopan P M Co-authored-by: Prekshith-Microsoft Co-authored-by: Roopan-Microsoft <168007406+Roopan-Microsoft@users.noreply.github.com> * fix: merging dev to main changes (#661) * model deployment when reusing existing foundry * change model deployment name * removed system managed identity code * seggregated the files * fix: Updated file names * chore: down merging main into dev (#638) * fixed opent telemetry issue CustomDomainInUse, FlagMustBeSetForRestore (#618) (#619) Co-authored-by: VishalS-Microsoft * directory update in dependabot template (#634) --------- Co-authored-by: NirajC-Microsoft Co-authored-by: VishalS-Microsoft Co-authored-by: Prajwal-Microsoft * added for cross-subscription existing AI project resource ID * update * fix: agent cleanup (#639) * Agent deletion handled successfully * use get azure credentials * remove unused import --------- Co-authored-by: Shreyas-Microsoft * TS( 21657) Bicep Standard code changes * Added required env variables for local debugging in .env generated by azd up * Added "Required/Optional" and changed aiProjectName suffix. * removed location tag * foundry project documentation * corrected name * updated CAdeploy.yml * update CAdeploy.yml * changed pipeline variable * updated main.json file * updated yml file * test * test * update * update * test * update CAdeploy.yml * CustomizingAzdparamMd * deploymentMdUpdate * Updated * Update infra/main.bicep Fixed typo error. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/App/backend/plugins/chat_with_data_plugin.py Updated the finally block for function "get_SQL_Response" Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update infra/main.bicep Removed Commented-out code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revert Copilot suggestion: `and project_client` is unnecessary. `project_client` is always initialized before thread creation, so `if thread:` is sufficient. * docs: Added Troubleshoot.md files for BYOC-Client Advisor (#650) * Added Troubleshoot.md files for BYOC-Client Advisor * fixed Check Markdown Broken Links * updated troubleshotingsteps file * Changed MACAE Github issues link with BYOC issue link --------- Co-authored-by: NirajC-Microsoft * added colon Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Replace 'u' with 'you' for proper grammarUpdate docs/TroubleShootingSteps.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * extra space removed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The command block is not properly closed Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The command should be wrapped in a proper code block with language identifier for better formatting and readability. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * The total asset value and current asset value are responding correctly, and the total value matches the asset value displayed in the left panel. (#651) * Bicep file changes to add CreatedBy tag (#655) * Update docs/TroubleShootingSteps.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: Adding a hardcoded createdBy parameter to the CAdeploy.yml file to prevent validation pipeline failures caused by the deployer function in the Bicep file. (#659) * Adding a hardcoded createdBy parameter to the CAdeploy.yml file to prevent validation pipeline failures caused by the deployer function in the Bicep file. * Consolidate parameters in CAdeploy.yml --------- Co-authored-by: Priyanka-Microsoft Co-authored-by: Prajwal D C Co-authored-by: Rafi-Microsoft Co-authored-by: NirajC-Microsoft Co-authored-by: VishalS-Microsoft Co-authored-by: Kanchan Nagshetti (Persistent Systems Inc) Co-authored-by: Shreyas-Microsoft Co-authored-by: Shreyas-Microsoft Co-authored-by: Bangarraju-Microsoft Co-authored-by: Prasanjeet-Microsoft Co-authored-by: UtkarshMishra-Microsoft Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Prekshith-Microsoft * fixed vulnerabilities * removed duplicate variable --------- Co-authored-by: NirajC-Microsoft Co-authored-by: Priyanka-Microsoft Co-authored-by: Prajwal D C Co-authored-by: Rafi-Microsoft Co-authored-by: VishalS-Microsoft Co-authored-by: Shreyas-Microsoft Co-authored-by: Shreyas-Microsoft Co-authored-by: Bangarraju-Microsoft Co-authored-by: Prasanjeet-Microsoft Co-authored-by: UtkarshMishra-Microsoft Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Roopan P M Co-authored-by: Prekshith-Microsoft Co-authored-by: Roopan-Microsoft <168007406+Roopan-Microsoft@users.noreply.github.com> --- src/App/frontend/package-lock.json | 482 +++++++++++++++++++---------- src/App/frontend/package.json | 2 +- 2 files changed, 312 insertions(+), 172 deletions(-) diff --git a/src/App/frontend/package-lock.json b/src/App/frontend/package-lock.json index 426d34e1c..1163cfa4e 100644 --- a/src/App/frontend/package-lock.json +++ b/src/App/frontend/package-lock.json @@ -17,7 +17,7 @@ "lodash-es": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^7.1.5", + "react-router-dom": "^7.5.2", "react-syntax-highlighter": "^15.6.1", "react-uuid": "^2.0.0", "rehype-raw": "^7.0.0", @@ -91,14 +91,15 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -233,19 +234,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -260,25 +263,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -540,25 +545,24 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", - "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -592,13 +596,14 @@ } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2110,247 +2115,294 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", - "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", + "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", - "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", + "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", - "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", + "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", - "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", + "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", - "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", + "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", - "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", + "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", - "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", + "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", - "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", + "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", - "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", + "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", - "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", + "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", - "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", + "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", - "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", + "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", - "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", + "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", + "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", - "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", + "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", - "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", + "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", - "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", + "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", + "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", - "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", + "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", - "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", + "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", - "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", + "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2578,7 +2630,8 @@ "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true }, "node_modules/@types/debug": { "version": "4.1.12", @@ -2605,10 +2658,11 @@ "dev": true }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -3009,10 +3063,11 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3603,10 +3658,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5630,10 +5686,11 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -5714,14 +5771,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -10213,9 +10272,10 @@ } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -10388,14 +10448,13 @@ } }, "node_modules/react-router": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz", - "integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz", + "integrity": "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==", + "license": "MIT", "dependencies": { - "@types/cookie": "^0.6.0", "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0", - "turbo-stream": "2.4.0" + "set-cookie-parser": "^2.6.0" }, "engines": { "node": ">=20.0.0" @@ -10411,11 +10470,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.2.0.tgz", - "integrity": "sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.2.tgz", + "integrity": "sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==", + "license": "MIT", "dependencies": { - "react-router": "7.2.0" + "react-router": "7.8.2" }, "engines": { "node": ">=20.0.0" @@ -10429,6 +10489,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", "engines": { "node": ">=18" } @@ -10447,15 +10508,16 @@ } }, "node_modules/react-syntax-highlighter": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", - "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "version": "15.6.6", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz", + "integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", - "prismjs": "^1.27.0", + "prismjs": "^1.30.0", "refractor": "^3.6.0" }, "peerDependencies": { @@ -10629,11 +10691,6 @@ "node": ">=6" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -10942,12 +10999,13 @@ } }, "node_modules/rollup": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", - "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", + "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -10957,25 +11015,27 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.8", - "@rollup/rollup-android-arm64": "4.34.8", - "@rollup/rollup-darwin-arm64": "4.34.8", - "@rollup/rollup-darwin-x64": "4.34.8", - "@rollup/rollup-freebsd-arm64": "4.34.8", - "@rollup/rollup-freebsd-x64": "4.34.8", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", - "@rollup/rollup-linux-arm-musleabihf": "4.34.8", - "@rollup/rollup-linux-arm64-gnu": "4.34.8", - "@rollup/rollup-linux-arm64-musl": "4.34.8", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", - "@rollup/rollup-linux-riscv64-gnu": "4.34.8", - "@rollup/rollup-linux-s390x-gnu": "4.34.8", - "@rollup/rollup-linux-x64-gnu": "4.34.8", - "@rollup/rollup-linux-x64-musl": "4.34.8", - "@rollup/rollup-win32-arm64-msvc": "4.34.8", - "@rollup/rollup-win32-ia32-msvc": "4.34.8", - "@rollup/rollup-win32-x64-msvc": "4.34.8", + "@rollup/rollup-android-arm-eabi": "4.50.1", + "@rollup/rollup-android-arm64": "4.50.1", + "@rollup/rollup-darwin-arm64": "4.50.1", + "@rollup/rollup-darwin-x64": "4.50.1", + "@rollup/rollup-freebsd-arm64": "4.50.1", + "@rollup/rollup-freebsd-x64": "4.50.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", + "@rollup/rollup-linux-arm-musleabihf": "4.50.1", + "@rollup/rollup-linux-arm64-gnu": "4.50.1", + "@rollup/rollup-linux-arm64-musl": "4.50.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", + "@rollup/rollup-linux-ppc64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-musl": "4.50.1", + "@rollup/rollup-linux-s390x-gnu": "4.50.1", + "@rollup/rollup-linux-x64-gnu": "4.50.1", + "@rollup/rollup-linux-x64-musl": "4.50.1", + "@rollup/rollup-openharmony-arm64": "4.50.1", + "@rollup/rollup-win32-arm64-msvc": "4.50.1", + "@rollup/rollup-win32-ia32-msvc": "4.50.1", + "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" } }, @@ -11103,7 +11163,8 @@ "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -11721,6 +11782,54 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -11928,11 +12037,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, - "node_modules/turbo-stream": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", - "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -12072,10 +12176,11 @@ } }, "node_modules/undici": { - "version": "5.28.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", - "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", "dev": true, + "license": "MIT", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -12288,14 +12393,18 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", + "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -12358,6 +12467,37 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/src/App/frontend/package.json b/src/App/frontend/package.json index d64f01f7c..82cb91c5a 100644 --- a/src/App/frontend/package.json +++ b/src/App/frontend/package.json @@ -25,7 +25,7 @@ "lodash-es": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^7.1.5", + "react-router-dom": "^7.5.2", "react-syntax-highlighter": "^15.6.1", "react-uuid": "^2.0.0", "rehype-raw": "^7.0.0", From 6eefc04ac226aed8928f1e0da19d733cb70bc3a4 Mon Sep 17 00:00:00 2001 From: Vamshi-Microsoft Date: Thu, 18 Sep 2025 10:30:16 +0530 Subject: [PATCH 81/84] fix: update aiSearchName variable to use solutionSuffix instead of solutionName --- infra/main.bicep | 2 +- infra/main.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 24d3ee71d..2c513db8f 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1059,7 +1059,7 @@ resource existingAiFoundryAiServicesProject 'Microsoft.CognitiveServices/account } -var aiSearchName = 'srch-${solutionName}' +var aiSearchName = 'srch-${solutionSuffix}' module searchService 'br/public:avm/res/search/search-service:0.11.1' = { name: take('avm.res.search.search-service.${aiSearchName}', 64) params: { diff --git a/infra/main.json b/infra/main.json index da06bcd27..9a2213322 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "8667168677735334522" + "templateHash": "12557623614553091212" } }, "parameters": { @@ -279,9 +279,9 @@ "useInternalStream": "True", "useAIProjectClientFlag": "False", "sqlServerFqdn": "[format('sql-{0}.database.windows.net', variables('solutionSuffix'))]", - "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\n Only return the generated SQL query. Do not return anything else.", - "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \n You have access to the client’s past meeting call transcripts. \n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \n If no data is available, state 'No relevant data found for previous meetings.", - "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\n Always send clientId as '{client_id}'.", + "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\r\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\r\n Only return the generated SQL query. Do not return anything else.", + "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", + "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'.", "replicaRegionPairs": { "australiaeast": "australiasoutheast", "centralus": "westus", @@ -381,7 +381,7 @@ "sqlDbName": "[format('sqldb-{0}', variables('solutionSuffix'))]", "webServerFarmResourceName": "[format('asp-{0}', variables('solutionSuffix'))]", "webSiteResourceName": "[format('app-{0}', variables('solutionSuffix'))]", - "aiSearchName": "[format('srch-{0}', parameters('solutionName'))]" + "aiSearchName": "[format('srch-{0}', variables('solutionSuffix'))]" }, "resources": { "existingLogAnalyticsWorkspace": { From 00b50e3669de2795a2a9b03ba345c44ede90f6fc Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 18 Sep 2025 12:09:18 +0530 Subject: [PATCH 82/84] update location parameter to enforce allowed Azure regions for deployment (#674) --- infra/main.bicep | 17 ++++++++++++++--- infra/main.json | 32 ++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 2c513db8f..1419cd689 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -82,9 +82,20 @@ param embeddingDeploymentCapacity int = 80 @description('Required. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed.') param azureAiServiceLocation string -@description('Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') -param AZURE_LOCATION string = '' -var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION +@allowed([ + 'australiaeast' + 'centralus' + 'eastasia' + 'eastus2' + 'japaneast' + 'northeurope' + 'southeastasia' + 'uksouth' +]) +@metadata({ azd: { type: 'location' } }) +@description('Required. Azure region for all services. Regions are restricted to guarantee compatibility with paired regions and replica locations for data redundancy and failover scenarios based on articles [Azure regions list](https://learn.microsoft.com/azure/reliability/regions-list) and [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions).') +param location string +var solutionLocation = empty(location) ? resourceGroup().location : location @maxLength(5) @description('Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.') diff --git a/infra/main.json b/infra/main.json index 9a2213322..18ddee175 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "12557623614553091212" + "templateHash": "14303228942844903685" } }, "parameters": { @@ -128,11 +128,23 @@ "description": "Required. Location for AI Foundry deployment. This is the location where the AI Foundry resources will be deployed." } }, - "AZURE_LOCATION": { + "location": { "type": "string", - "defaultValue": "", + "allowedValues": [ + "australiaeast", + "centralus", + "eastasia", + "eastus2", + "japaneast", + "northeurope", + "southeastasia", + "uksouth" + ], "metadata": { - "description": "Optional. Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default." + "azd": { + "type": "location" + }, + "description": "Required. Azure region for all services. Regions are restricted to guarantee compatibility with paired regions and replica locations for data redundancy and failover scenarios based on articles [Azure regions list](https://learn.microsoft.com/azure/reliability/regions-list) and [Azure Database for MySQL Flexible Server - Azure Regions](https://learn.microsoft.com/azure/mysql/flexible-server/overview#azure-regions)." } }, "solutionUniqueToken": { @@ -253,7 +265,7 @@ } }, "variables": { - "solutionLocation": "[if(empty(parameters('AZURE_LOCATION')), resourceGroup().location, parameters('AZURE_LOCATION'))]", + "solutionLocation": "[if(empty(parameters('location')), resourceGroup().location, parameters('location'))]", "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueToken')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", "appEnvironment": "Prod", "azureSearchIndex": "transcripts_index", @@ -279,9 +291,9 @@ "useInternalStream": "True", "useAIProjectClientFlag": "False", "sqlServerFqdn": "[format('sql-{0}.database.windows.net', variables('solutionSuffix'))]", - "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\r\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\r\n Only return the generated SQL query. Do not return anything else.", - "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", - "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'.", + "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\n Only return the generated SQL query. Do not return anything else.", + "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \n You have access to the client’s past meeting call transcripts. \n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \n If no data is available, state 'No relevant data found for previous meetings.", + "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\n Always send clientId as '{client_id}'.", "replicaRegionPairs": { "australiaeast": "australiasoutheast", "centralus": "westus", @@ -32690,9 +32702,9 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "logAnalyticsWorkspace", "network", "userAssignedIdentity" @@ -42299,8 +42311,8 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", "keyvault", "network", "userAssignedIdentity" From 959982958ad99003fe1f21c779dff859b115bc5f Mon Sep 17 00:00:00 2001 From: Prajwal D C Date: Thu, 18 Sep 2025 12:43:38 +0530 Subject: [PATCH 83/84] fix: Resolved cnflict issue --- infra/main.bicep | 3 - infra/main.json | 172 ++++++++++++++++++++++++++++------------------- 2 files changed, 104 insertions(+), 71 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index a3888d280..1419cd689 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -278,9 +278,6 @@ var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnal @description('Optional created by user name') param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] -@description('Optional created by user name') -param createdBy string = empty(deployer().userPrincipalName) ? '' : split(deployer().userPrincipalName, '@')[0] - // ========== Resource Group Tag ========== // resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { name: 'default' diff --git a/infra/main.json b/infra/main.json index 18ddee175..3da4d7094 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "14303228942844903685" + "templateHash": "9604046552134506224" } }, "parameters": { @@ -291,9 +291,9 @@ "useInternalStream": "True", "useAIProjectClientFlag": "False", "sqlServerFqdn": "[format('sql-{0}.database.windows.net', variables('solutionSuffix'))]", - "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\n 1. Table: Clients\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\n 2. Table: InvestmentGoals\n Columns: ClientId, InvestmentGoal\n 3. Table: Assets\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\n 4. Table: ClientSummaries\n Columns: ClientId, ClientSummary\n 5. Table: InvestmentGoalsDetails\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\n 6. Table: Retirement\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\n 7. Table: ClientMeetings\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\n Always use the Investment column from the Assets table as the value.\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\n Do not use client name in filters.\n Do not include assets values unless asked for.\n ALWAYS use ClientId = {clientid} in the query filter.\n ALWAYS select Client Name (Column: Client) in the query.\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\n Only return the generated SQL query. Do not return anything else.", - "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \n You have access to the client’s past meeting call transcripts. \n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \n If no data is available, state 'No relevant data found for previous meetings.", - "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\n Always send clientId as '{client_id}'.", + "functionAppSqlPrompt": "Generate a valid T-SQL query to find {query} for tables and columns provided below:\r\n 1. Table: Clients\r\n Columns: ClientId, Client, Email, Occupation, MaritalStatus, Dependents\r\n 2. Table: InvestmentGoals\r\n Columns: ClientId, InvestmentGoal\r\n 3. Table: Assets\r\n Columns: ClientId, AssetDate, Investment, ROI, Revenue, AssetType\r\n 4. Table: ClientSummaries\r\n Columns: ClientId, ClientSummary\r\n 5. Table: InvestmentGoalsDetails\r\n Columns: ClientId, InvestmentGoal, TargetAmount, Contribution\r\n 6. Table: Retirement\r\n Columns: ClientId, StatusDate, RetirementGoalProgress, EducationGoalProgress\r\n 7. Table: ClientMeetings\r\n Columns: ClientId, ConversationId, Title, StartTime, EndTime, Advisor, ClientEmail\r\n Always use the Investment column from the Assets table as the value.\r\n Assets table has snapshots of values by date. Do not add numbers across different dates for total values.\r\n Do not use client name in filters.\r\n Do not include assets values unless asked for.\r\n ALWAYS use ClientId = {clientid} in the query filter.\r\n ALWAYS select Client Name (Column: Client) in the query.\r\n Query filters are IMPORTANT. Add filters like AssetType, AssetDate, etc. if needed.\r\n When answering scheduling or time-based meeting questions, always use the StartTime column from ClientMeetings table. Use correct logic to return the most recent past meeting (last/previous) or the nearest future meeting (next/upcoming), and ensure only StartTime column is used for meeting timing comparisons.\r\n For asset values: If the question is about \"asset value\", \"total asset value\", \"portfolio value\", or \"AUM\" → ALWAYS return the SUM of the latest investments (do not return individual rows). If the question is about \"current asset value\" or \"current investment value\" → return all latest investments without SUM.\r\n For trend queries: If the question contains \"how did change\", \"over the last\", \"trend\", or \"progression\" → return time series data for the requested period with SUM for each time period and show chronological progression.\r\n Only return the generated SQL query. Do not return anything else.", + "functionAppCallTranscriptSystemPrompt": "You are an assistant who supports wealth advisors in preparing for client meetings. \r\n You have access to the client’s past meeting call transcripts. \r\n When answering questions, especially summary requests, provide a detailed and structured response that includes key topics, concerns, decisions, and trends. \r\n If no data is available, state 'No relevant data found for previous meetings.", + "functionAppStreamTextSystemPrompt": "The currently selected client's name is '{SelectedClientName}'. Treat any case-insensitive or partial mention as referring to this client.\r\n If the user mentions no name, assume they are asking about '{SelectedClientName}'.\r\n If the user references a name that clearly differs from '{SelectedClientName}' or comparing with other clients, respond only with: 'Please only ask questions about the selected client or select another client.' Otherwise, provide thorough answers for every question using only data from SQL or call transcripts.'\r\n If no data is found, respond with 'No data found for that client.' Remove any client identifiers from the final response.\r\n Always send clientId as '{client_id}'.", "replicaRegionPairs": { "australiaeast": "australiasoutheast", "centralus": "westus", @@ -27037,7 +27037,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "9573727846743928038" + "templateHash": "13656850596361779072" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service." @@ -27416,12 +27416,6 @@ "metadata": { "description": "Required. API endpoint for the AI project." } - }, - "aiprojectSystemAssignedMIPrincipalId": { - "type": "string", - "metadata": { - "description": "Required. System Assigned Managed Identity Principal Id of the AI project." - } } }, "metadata": { @@ -28270,7 +28264,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "16444475951283055894" + "templateHash": "17521005359575826172" } }, "definitions": { @@ -28647,12 +28641,6 @@ "metadata": { "description": "Required. API endpoint for the AI project." } - }, - "aiprojectSystemAssignedMIPrincipalId": { - "type": "string", - "metadata": { - "description": "Required. System Assigned Managed Identity Principal Id of the AI project." - } } }, "metadata": { @@ -30239,7 +30227,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "346451728741152022" + "templateHash": "3927256115051099098" } }, "definitions": { @@ -30263,12 +30251,6 @@ "metadata": { "description": "Required. API endpoint for the AI project." } - }, - "aiprojectSystemAssignedMIPrincipalId": { - "type": "string", - "metadata": { - "description": "Required. System Assigned Managed Identity Principal Id of the AI project." - } } }, "metadata": { @@ -30322,10 +30304,7 @@ "variables": { "useExistingProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", "existingProjName": "[if(variables('useExistingProject'), last(split(parameters('existingFoundryProjectResourceId'), '/')), '')]", - "existingAiFoundryAiServicesSubscriptionId": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], '')]", - "existingAiFoundryAiServicesResourceGroupName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], '')]", - "existingAiFoundryAiServicesServiceName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], '')]", - "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName')), '')]" + "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('aiServicesName'), variables('existingProjName')), '')]" }, "resources": { "cogServiceReference": { @@ -30349,27 +30328,63 @@ "displayName": "[parameters('name')]" } }, - "existingAiProject": { - "condition": "[variables('useExistingProject')]", + "searchIndexDataReaderRoleDefinition": { "existing": true, - "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-06-01", - "subscriptionId": "[variables('existingAiFoundryAiServicesSubscriptionId')]", - "resourceGroup": "[variables('existingAiFoundryAiServicesResourceGroupName')]", - "name": "[format('{0}/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName'))]" + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2022-04-01", + "name": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "metadata": { + "description": "This is the built-in Search Index Data Reader role." + } + }, + "searchServiceToAiServicesRoleAssignment": { + "condition": "[not(variables('useExistingProject'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), resourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", + "principalId": "[reference('aiProject', '2025-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "aiProject" + ] + }, + "searchServiceContributorRoleDefinition": { + "existing": true, + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2022-04-01", + "name": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "metadata": { + "description": "This is the built-in Search Service Contributor role." + } + }, + "searchServiceContributorRoleAssignmentToAIFP": { + "condition": "[not(variables('useExistingProject'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", + "principalId": "[reference('aiProject', '2025-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "aiProject" + ] } }, "outputs": { "aiProjectInfo": { "$ref": "#/definitions/aiProjectOutputType", "metadata": { - "description": "AI Project metadata including name, resource ID, and API endpoint, and SystemAssignedManagedIdentity Principal Id." + "description": "AI Project metadata including name, resource ID, and API endpoint." }, "value": { "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", "resourceId": "[if(variables('useExistingProject'), parameters('existingFoundryProjectResourceId'), resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')))]", - "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]", - "aiprojectSystemAssignedMIPrincipalId": "[if(variables('useExistingProject'), reference('existingAiProject', '2025-06-01', 'full').identity.principalId, reference('aiProject', '2025-06-01', 'full').identity.principalId)]" + "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]" } } } @@ -30476,7 +30491,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "16444475951283055894" + "templateHash": "17521005359575826172" } }, "definitions": { @@ -30853,12 +30868,6 @@ "metadata": { "description": "Required. API endpoint for the AI project." } - }, - "aiprojectSystemAssignedMIPrincipalId": { - "type": "string", - "metadata": { - "description": "Required. System Assigned Managed Identity Principal Id of the AI project." - } } }, "metadata": { @@ -32445,7 +32454,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "346451728741152022" + "templateHash": "3927256115051099098" } }, "definitions": { @@ -32469,12 +32478,6 @@ "metadata": { "description": "Required. API endpoint for the AI project." } - }, - "aiprojectSystemAssignedMIPrincipalId": { - "type": "string", - "metadata": { - "description": "Required. System Assigned Managed Identity Principal Id of the AI project." - } } }, "metadata": { @@ -32528,10 +32531,7 @@ "variables": { "useExistingProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", "existingProjName": "[if(variables('useExistingProject'), last(split(parameters('existingFoundryProjectResourceId'), '/')), '')]", - "existingAiFoundryAiServicesSubscriptionId": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], '')]", - "existingAiFoundryAiServicesResourceGroupName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], '')]", - "existingAiFoundryAiServicesServiceName": "[if(variables('useExistingProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], '')]", - "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName')), '')]" + "existingProjEndpoint": "[if(variables('useExistingProject'), format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('aiServicesName'), variables('existingProjName')), '')]" }, "resources": { "cogServiceReference": { @@ -32555,27 +32555,63 @@ "displayName": "[parameters('name')]" } }, - "existingAiProject": { - "condition": "[variables('useExistingProject')]", + "searchIndexDataReaderRoleDefinition": { "existing": true, - "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-06-01", - "subscriptionId": "[variables('existingAiFoundryAiServicesSubscriptionId')]", - "resourceGroup": "[variables('existingAiFoundryAiServicesResourceGroupName')]", - "name": "[format('{0}/{1}', variables('existingAiFoundryAiServicesServiceName'), variables('existingProjName'))]" + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2022-04-01", + "name": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "metadata": { + "description": "This is the built-in Search Index Data Reader role." + } + }, + "searchServiceToAiServicesRoleAssignment": { + "condition": "[not(variables('useExistingProject'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), resourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')]", + "principalId": "[reference('aiProject', '2025-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "aiProject" + ] + }, + "searchServiceContributorRoleDefinition": { + "existing": true, + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2022-04-01", + "name": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "metadata": { + "description": "This is the built-in Search Service Contributor role." + } + }, + "searchServiceContributorRoleAssignmentToAIFP": { + "condition": "[not(variables('useExistingProject'))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')]", + "principalId": "[reference('aiProject', '2025-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "aiProject" + ] } }, "outputs": { "aiProjectInfo": { "$ref": "#/definitions/aiProjectOutputType", "metadata": { - "description": "AI Project metadata including name, resource ID, and API endpoint, and SystemAssignedManagedIdentity Principal Id." + "description": "AI Project metadata including name, resource ID, and API endpoint." }, "value": { "name": "[if(variables('useExistingProject'), variables('existingProjName'), parameters('name'))]", "resourceId": "[if(variables('useExistingProject'), parameters('existingFoundryProjectResourceId'), resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')))]", - "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]", - "aiprojectSystemAssignedMIPrincipalId": "[if(variables('useExistingProject'), reference('existingAiProject', '2025-06-01', 'full').identity.principalId, reference('aiProject', '2025-06-01', 'full').identity.principalId)]" + "apiEndpoint": "[if(variables('useExistingProject'), variables('existingProjEndpoint'), reference('aiProject').endpoints['AI Foundry API'])]" } } } @@ -32702,9 +32738,9 @@ } }, "dependsOn": [ + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "logAnalyticsWorkspace", "network", "userAssignedIdentity" From 185a83a155ed027921ff95d3883ab9df4d4d46ab Mon Sep 17 00:00:00 2001 From: Prajwal D C Date: Thu, 18 Sep 2025 12:55:08 +0530 Subject: [PATCH 84/84] fix: Added location param for deployment --- .github/workflows/CAdeploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml index 4d5db8cf4..46a8bdfa0 100644 --- a/.github/workflows/CAdeploy.yml +++ b/.github/workflows/CAdeploy.yml @@ -144,7 +144,7 @@ jobs: DEPLOY_OUTPUT=$(az deployment group create \ --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ --template-file infra/main.bicep \ - --parameters azureAiServiceLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptModelCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} containerImageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ + --parameters location=${{ env.AZURE_LOCATION }} azureAiServiceLocation=${{ env.AZURE_LOCATION }} solutionName=${{ env.SOLUTION_PREFIX }} cosmosLocation=westus gptModelCapacity=${{ env.GPT_MIN_CAPACITY }} embeddingDeploymentCapacity=${{ env.TEXT_EMBEDDING_MIN_CAPACITY }} containerImageTag=${{ env.IMAGE_TAG }} createdBy="Pipeline" \ --query "properties.outputs" -o json) echo "Deployment output: $DEPLOY_OUTPUT"

mG*6s+LhFA>6mfX=K(i)8&<<`}6<_il?nf751 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