From e47b85c63edbd7b90d56980864a83c7a10655137 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Wed, 6 May 2026 16:11:07 +0530 Subject: [PATCH 1/8] Migrate Content Understanding from preview to GA and consolidate AI Services account Migrate Azure AI Content Understanding from 2024-12-01-preview to GA 2025-11-01 (ADO 41641), and consolidate the standalone Content Understanding Cognitive Services account into the existing unified Azure AI Services account (now hosting both Azure OpenAI and CU). Infra - Drop avmAiServices_cu module, contentUnderstandingPrivateEndpoint, and the contentUnderstandingLocation parameter from main.bicep and main_custom.bicep; mirror the changes in main.json. - Restrict azureAiServiceLocation @allowed to the 11-region intersection where both CU GA and gpt-5.1 GlobalStandard are available. - Add two Cognitive Services User role assignments (API and Workflow managed identities) on the unified account so CU calls don't 403. - Re-route APP_CONTENT_UNDERSTANDING_ENDPOINT to the unified account. - Drop AZURE_ENV_CU_LOCATION mapping from main.parameters.json and main.waf.parameters.json. - Remove contentUnderstandingLocation override from .github/workflows/deploy.yml. Application code - Bump api-version to 2025-11-01 and switch to the GA REST surface: :analyzeBinary for stream payloads, knowledgeSources[] for training data, and /files/{id} for figure retrieval. - Update Pydantic models for GA: add Warning, relax Page optionals (angle/spans/words/lines), and surface the new top-level DocumentContent.paragraphs field. - Add unit tests for the new Warning model and relaxed Page optionals; bump existing apiVersion fixtures. Docs - CustomizingAzdParameters.md: drop AZURE_ENV_CU_LOCATION row, rewrite AZURE_ENV_AI_SERVICE_LOCATION row, and append a usageName note for the Standard deployment type. - LocalDevelopmentSetup.md: replace stale aicu-{suffix} reference. - TroubleShootingSteps.md: update the CU 403 row for the consolidated account name and DNS zones. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/deploy.yml | 1 - docs/CustomizingAzdParameters.md | 5 +- docs/LocalDevelopmentSetup.md | 2 +- docs/TroubleShootingSteps.md | 2 +- infra/main.bicep | 115 +- infra/main.json | 3499 +---------------- infra/main.parameters.json | 3 - infra/main.waf.parameters.json | 3 - infra/main_custom.bicep | 115 +- .../azure_helper/content_understanding.py | 82 +- .../model/content_understanding.py | 25 +- .../test_content_understanding_model.py | 50 +- .../test_content_understanding_model.py | 50 +- 13 files changed, 364 insertions(+), 3588 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e4dfaebd..e739d864 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -146,7 +146,6 @@ jobs: --parameters \ solutionName="${{ env.ENVIRONMENT_NAME }}" \ enablePrivateNetworking="false" \ - contentUnderstandingLocation="WestUS" \ deploymentType="GlobalStandard" \ gptModelName="gpt-5.1" \ gptModelVersion="2025-11-13" \ diff --git a/docs/CustomizingAzdParameters.md b/docs/CustomizingAzdParameters.md index 0c3b5eca..398b53e0 100644 --- a/docs/CustomizingAzdParameters.md +++ b/docs/CustomizingAzdParameters.md @@ -11,9 +11,8 @@ By default this template will use the environment name as the prefix to prevent | -------------------------------------- | ------- | --------------------------- | ------------------------------------------------------------------------------------- | | `AZURE_ENV_NAME` | string | `cps` | Sets the environment name prefix for all Azure resources (3-20 characters). | | `AZURE_LOCATION` | string | `eastus2` | Sets the primary Azure region for resource deployment. Allowed: `australiaeast`, `centralus`, `eastasia`, `eastus2`, `japaneast`, `northeurope`, `southeastasia`, `uksouth`. | -| `AZURE_ENV_CU_LOCATION` | string | `WestUS` | Sets the location for the Azure AI Content Understanding service. Allowed: `WestUS`, `SwedenCentral`, `AustraliaEast`. | -| `AZURE_ENV_AI_SERVICE_LOCATION` | string | `eastus` | Sets the location for Azure AI Services (OpenAI) deployment. | -| `AZURE_ENV_MODEL_DEPLOYMENT_TYPE` | string | `GlobalStandard` | Defines the model deployment type. Allowed: `Standard`, `GlobalStandard`. | +| `AZURE_ENV_AI_SERVICE_LOCATION` | string | `eastus2` | Sets the location for Azure AI Services. This single account hosts both Azure OpenAI and Content Understanding. Allowed: `australiaeast`, `eastus`, `eastus2`, `japaneast`, `southcentralus`, `southeastasia`, `swedencentral`, `uksouth`, `westeurope`, `westus`, `westus3`. | +| `AZURE_ENV_MODEL_DEPLOYMENT_TYPE` | string | `GlobalStandard` | Defines the model deployment type. Allowed: `Standard`, `GlobalStandard`.
**Note:** the `azd` location-picker filters regions using the `usageName` metadata on `azureAiServiceLocation` in `infra/main.bicep` (currently `OpenAI.GlobalStandard.gpt-5.1,300`). If you set this parameter to `Standard`, also edit that metadata to `OpenAI.Standard.gpt-5.1,300` so the picker shows the correct subset of regions. | | `AZURE_ENV_GPT_MODEL_NAME` | string | `gpt-5.1` | Specifies the GPT model name. Default: `gpt-5.1`. | | `AZURE_ENV_GPT_MODEL_VERSION` | string | `2025-11-13` | Specifies the GPT model version. | | `AZURE_ENV_GPT_MODEL_CAPACITY` | integer | `300` | Sets the model capacity (minimum 1). Default: 300. Optimal: 500 for multi-document claim processing. | diff --git a/docs/LocalDevelopmentSetup.md b/docs/LocalDevelopmentSetup.md index 38d868e4..7095cd7d 100644 --- a/docs/LocalDevelopmentSetup.md +++ b/docs/LocalDevelopmentSetup.md @@ -160,7 +160,7 @@ Example resource names from deployment: - App Configuration: `appcs-{suffix}.azconfig.io` - Cosmos DB: `cosmos-{suffix}.documents.azure.com` - Storage Account: `st{suffix}.queue.core.windows.net` -- Content Understanding: `aicu-{suffix}.cognitiveservices.azure.com` +- Content Understanding: `aif-{suffix}.cognitiveservices.azure.com` ### Required Azure RBAC Permissions diff --git a/docs/TroubleShootingSteps.md b/docs/TroubleShootingSteps.md index 6d4d60cd..3f78e276 100644 --- a/docs/TroubleShootingSteps.md +++ b/docs/TroubleShootingSteps.md @@ -128,7 +128,7 @@ Use these as quick reference guides to unblock your deployments. | **RouteTableCannotBeAttachedForAzureBastionSubnet** | Route table attached to Azure Bastion subnet | This error occurs because Azure Bastion subnet (`AzureBastionSubnet`) has a platform restriction that prevents route tables from being attached.

**How to reproduce:**

**Resolution:**
| | **VMSizeIsNotPermittedToEnableAcceleratedNetworking** | VM size does not support accelerated networking | This error occurs when you attempt to enable accelerated networking on a VM size that does not support it. This deployment's jumpbox VM **requires** accelerated networking.

**Default VM size:** `Standard_D2s_v5` — supports accelerated networking.

**How this error happens:**

**Resolution:**
| | **NetworkSecurityGroupNotCompliantForAzureBastionSubnet** / **SecurityRuleParameterContainsUnsupportedValue** | NSG rules blocking required Azure Bastion ports | This error occurs when the Network Security Group (NSG) attached to `AzureBastionSubnet` explicitly denies inbound TCP ports 443 and/or 4443, which Azure Bastion requires for management and tunneling.

**How to reproduce:**

**Resolution:**
| -| **403 Forbidden - Content Understanding** | Azure AI Content Understanding returns 403 Forbidden in WAF (private networking) deployment | This error occurs when the **Azure AI Content Understanding** service returns a `403 Forbidden` response during document processing in a **WAF-enabled (private networking)** deployment.

**Why this happens:**
In WAF deployments (`enablePrivateNetworking=true`), the Content Understanding AI Services account (`aicu-`) is configured with `publicNetworkAccess: Disabled`. All traffic must flow through the **private endpoint** (`pep-aicu-`) and resolve via private DNS zones (`privatelink.cognitiveservices.azure.com`, `privatelink.services.ai.azure.com`, `privatelink.contentunderstanding.ai.azure.com`). If any part of this chain is misconfigured, the request either reaches the public endpoint (which is blocked) or fails to route entirely, resulting in a 403.

**Common causes:**
  • Private DNS zones not linked to the VNet — DNS resolution falls back to the public IP, which is blocked
  • Private endpoint connection is not in **Approved** state
  • Content Understanding is deployed in a different region (`contentUnderstandingLocation`, defaults to `WestUS`) than the main deployment — the private endpoint still works cross-region, but DNS misconfiguration is more likely
  • Container Apps are not injected into the VNet or are on a subnet that cannot reach the private endpoint
  • Managed Identity used by the Container App does not have the required **Cognitive Services User** role on the Content Understanding resource

**Resolution:**
  • **Verify private endpoint status:**
    `az network private-endpoint show --name pep-aicu- --resource-group --query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status"`
    Expected: `Approved`
  • **Verify private DNS zone VNet links:**
    `az network private-dns zone list --resource-group -o table`
    Ensure `privatelink.cognitiveservices.azure.com`, `privatelink.services.ai.azure.com`, and `privatelink.contentunderstanding.ai.azure.com` all have VNet links
  • **Test DNS resolution from the jumpbox VM** (inside the VNet):
    `nslookup aicu-.cognitiveservices.azure.com`
    Should resolve to a private IP (e.g., `10.x.x.x`), NOT a public IP
  • **Verify RBAC role assignments:** Ensure the Container App managed identity has **Cognitive Services User** role on the Content Understanding resource:
    `az role assignment list --scope /subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/aicu- --query "[?roleDefinitionName=='Cognitive Services User']" -o table`
  • **Check Container App VNet integration:** Confirm the Container App Environment is deployed into the VNet and can reach the backend subnet where the private endpoint resides
  • **Redeploy if needed:**
    `azd up`

**Reference:**
  • [Configure private endpoints for Azure AI Services](https://learn.microsoft.com/en-us/azure/ai-services/cognitive-services-virtual-networks)
  • [Azure Private DNS zones](https://learn.microsoft.com/en-us/azure/dns/private-dns-overview)
| +| **403 Forbidden - Content Understanding** | Azure AI Content Understanding returns 403 Forbidden in WAF (private networking) deployment | This error occurs when the **Azure AI Content Understanding** API on the unified AI Services account (`aif-`) returns a `403 Forbidden` response during document processing in a **WAF-enabled (private networking)** deployment.

**Why this happens:**
As of the CU GA migration, Content Understanding shares the same Azure AI Services account as Azure OpenAI (`aif-`). In WAF deployments (`enablePrivateNetworking=true`), that account is configured with `publicNetworkAccess: Disabled`. All traffic must flow through the unified private endpoint (`pep-aiservices-`) and resolve via four private DNS zones: `privatelink.cognitiveservices.azure.com`, `privatelink.openai.azure.com`, `privatelink.services.ai.azure.com`, and `privatelink.contentunderstanding.ai.azure.com`. If any link in this chain is misconfigured, the request either reaches the public endpoint (blocked) or fails to route, resulting in a 403.

**Common causes:**
  • Private DNS zones not linked to the VNet — DNS resolution falls back to the public IP, which is blocked
  • Private endpoint connection is not in **Approved** state
  • Container Apps are not injected into the VNet or are on a subnet that cannot reach the private endpoint
  • Managed Identity used by the Container App does not have the required **Cognitive Services User** role on the unified AI Services account
  • Reusing an existing AI Foundry project (`existingFoundryProjectResourceId`): the repo no longer creates a CU-specific PE; the existing account must have its own private endpoint covering the four DNS zones above

**Resolution:**
  • **Verify private endpoint status:**
    `az network private-endpoint show --name pep-aiservices- --resource-group --query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status"`
    Expected: `Approved`
  • **Verify private DNS zone VNet links:**
    `az network private-dns zone list --resource-group -o table`
    Ensure `privatelink.cognitiveservices.azure.com`, `privatelink.openai.azure.com`, `privatelink.services.ai.azure.com`, and `privatelink.contentunderstanding.ai.azure.com` all have VNet links
  • **Test DNS resolution from the jumpbox VM** (inside the VNet):
    `nslookup aif-.cognitiveservices.azure.com`
    Should resolve to a private IP (e.g., `10.x.x.x`), NOT a public IP
  • **Verify RBAC role assignments:** ensure the Container App managed identity has **Cognitive Services User** role on the unified account:
    `az role assignment list --scope /subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/aif- --query "[?roleDefinitionName=='Cognitive Services User']" -o table`
  • **Check Container App VNet integration:** confirm the Container App Environment is deployed into the VNet and can reach the backend subnet where the private endpoint resides
  • **Redeploy if needed:**
    `azd up`

**Reference:**
  • [Configure private endpoints for Azure AI Services](https://learn.microsoft.com/en-us/azure/ai-services/cognitive-services-virtual-networks)
  • [Azure Private DNS zones](https://learn.microsoft.com/en-us/azure/dns/private-dns-overview)
| --------------------------------- diff --git a/infra/main.bicep b/infra/main.bicep index b06f22c5..2bc2b88c 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -25,26 +25,20 @@ param solutionName string = 'cps' param location string @minLength(1) -@description('Optional. Location for the Azure AI Content Understanding service deployment.') -@allowed(['WestUS', 'SwedenCentral', 'AustraliaEast']) -@metadata({ - azd: { - type: 'location' - } -}) -param contentUnderstandingLocation string = 'WestUS' - @allowed([ 'australiaeast' - 'centralus' - 'eastasia' + 'eastus' 'eastus2' 'japaneast' - 'northeurope' + 'southcentralus' 'southeastasia' + 'swedencentral' 'uksouth' + 'westeurope' + 'westus' + 'westus3' ]) -@description('Required. Location for the Azure AI Services deployment.') +@description('Required. Location for the Azure AI Services deployment. Must support both Azure OpenAI gpt-5.1 (GlobalStandard) and Azure AI Content Understanding GA. If the deploymentType param is set to Standard, override the metadata.azd.usageName below to reference OpenAI.Standard.gpt-5.1 instead.') @metadata({ azd: { type: 'location' @@ -747,6 +741,16 @@ module avmAiServices 'modules/account/aifoundry.bicep' = { roleDefinitionIdOrName: 'Azure AI Developer' principalType: 'ServicePrincipal' } + { + principalId: avmContainerApp.outputs.systemAssignedMIPrincipalId! + roleDefinitionIdOrName: 'Cognitive Services User' + principalType: 'ServicePrincipal' + } + { + principalId: avmContainerApp_Workflow.outputs.systemAssignedMIPrincipalId! + roleDefinitionIdOrName: 'Cognitive Services User' + principalType: 'ServicePrincipal' + } ] networkAcls: { bypass: 'AzureServices' @@ -816,84 +820,6 @@ module cognitiveServicePrivateEndpoint 'br/public:avm/res/network/private-endpoi } } -module avmAiServices_cu 'br/public:avm/res/cognitive-services/account:0.14.2' = { - name: take('avm.res.cognitive-services.account.content-understanding.${solutionSuffix}', 64) - - params: { - name: 'aicu-${solutionSuffix}' - location: contentUnderstandingLocation - sku: 'S0' - managedIdentities: { - systemAssigned: false - userAssignedResourceIds: [ - avmManagedIdentity.outputs.resourceId // Use the managed identity created above - ] - } - kind: 'AIServices' - tags: { - app: solutionSuffix - location: location - } - customSubDomainName: 'aicu-${solutionSuffix}' - disableLocalAuth: true - enableTelemetry: enableTelemetry - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Allow' // Always allow for AI Services - } - roleAssignments: [ - { - principalId: avmContainerApp.outputs.systemAssignedMIPrincipalId! - roleDefinitionIdOrName: 'a97b65f3-24c7-4388-baec-2e87135dc908' - principalType: 'ServicePrincipal' - } - { - principalId: avmContainerApp_Workflow.outputs.systemAssignedMIPrincipalId! - roleDefinitionIdOrName: 'a97b65f3-24c7-4388-baec-2e87135dc908' - principalType: 'ServicePrincipal' - } - ] - - publicNetworkAccess: (enablePrivateNetworking) ? 'Disabled' : 'Enabled' - } -} - -module contentUnderstandingPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.12.0' = if (enablePrivateNetworking) { - name: take('avm.res.network.private-endpoint.aicu-${solutionSuffix}', 64) - params: { - name: 'pep-aicu-${solutionSuffix}' - location: location - tags: tags - customNetworkInterfaceName: 'nic-aicu-${solutionSuffix}' - privateLinkServiceConnections: [ - { - name: 'pep-aicu-${solutionSuffix}-cognitiveservices-connection' - properties: { - privateLinkServiceId: avmAiServices_cu.outputs.resourceId - groupIds: ['account'] - } - } - ] - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { - name: 'aicu-dns-zone-cognitiveservices' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId - } - { - name: 'ai-services-dns-zone-aiservices' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.aiServices]!.outputs.resourceId - } - { - name: 'aicu-dns-zone-contentunderstanding' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.contentUnderstanding]!.outputs.resourceId - } - ] - } - subnetResourceId: virtualNetwork!.outputs.backendSubnetResourceId - } -} - // ========== Container App Environment ========== // module avmContainerAppEnv 'br/public:avm/res/app/managed-environment:0.13.2' = { name: take('avm.res.app.managed-environment.${solutionSuffix}', 64) @@ -1408,7 +1334,7 @@ module avmAppConfig 'br/public:avm/res/app-configuration/configuration-store:0.9 } { name: 'APP_CONTENT_UNDERSTANDING_ENDPOINT' - value: avmAiServices_cu.outputs.endpoint //TODO: replace with actual endpoint + value: avmAiServices.outputs.endpoint } { name: 'APP_COSMOS_CONTAINER_PROCESS' @@ -1683,7 +1609,6 @@ module avmContainerApp_update 'br/public:avm/res/app/container-app:0.22.1' = { } dependsOn: [ cognitiveServicePrivateEndpoint - contentUnderstandingPrivateEndpoint ] } @@ -1922,8 +1847,8 @@ output CONTAINER_REGISTRY_NAME string = avmContainerRegistry.outputs.name @description('The login server of the Azure Container Registry.') output CONTAINER_REGISTRY_LOGIN_SERVER string = avmContainerRegistry.outputs.loginServer -@description('The name of the Content Understanding AI Services account.') -output CONTENT_UNDERSTANDING_ACCOUNT_NAME string = avmAiServices_cu.outputs.name +@description('The name of the AI Services account that hosts both Azure OpenAI and Content Understanding GA.') +output CONTENT_UNDERSTANDING_ACCOUNT_NAME string = avmAiServices.outputs.name @description('The resource group the resources were deployed into.') output AZURE_RESOURCE_GROUP string = resourceGroup().name diff --git a/infra/main.json b/infra/main.json index 1c3e3e3e..b66d5b32 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.42.1.51946", - "templateHash": "2184176346978633067" + "templateHash": "312988678863218513" }, "name": "Content Processing Solution Accelerator", "description": "Bicep template to deploy the Content Processing Solution Accelerator with AVM compliance." @@ -40,33 +40,20 @@ "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)." } }, - "contentUnderstandingLocation": { - "type": "string", - "defaultValue": "WestUS", - "allowedValues": [ - "WestUS", - "SwedenCentral", - "AustraliaEast" - ], - "metadata": { - "azd": { - "type": "location" - }, - "description": "Optional. Location for the Azure AI Content Understanding service deployment." - }, - "minLength": 1 - }, "azureAiServiceLocation": { "type": "string", "allowedValues": [ "australiaeast", - "centralus", - "eastasia", + "eastus", "eastus2", "japaneast", - "northeurope", + "southcentralus", "southeastasia", - "uksouth" + "swedencentral", + "uksouth", + "westeurope", + "westus", + "westus3" ], "metadata": { "azd": { @@ -75,8 +62,9 @@ "OpenAI.GlobalStandard.gpt-5.1,300" ] }, - "description": "Required. Location for the Azure AI Services deployment." - } + "description": "Required. Location for the Azure AI Services deployment. Must support both Azure OpenAI gpt-5.1 (GlobalStandard) and Azure AI Content Understanding GA. If the deploymentType param is set to Standard, override the metadata.azd.usageName below to reference OpenAI.Standard.gpt-5.1 instead." + }, + "minLength": 1 }, "deploymentType": { "type": "string", @@ -7485,6 +7473,13 @@ "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": "", @@ -8075,6 +8070,7 @@ }, "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())]", @@ -36261,6 +36257,16 @@ "principalId": "[reference('avmContainerApp_Workflow').outputs.systemAssignedMIPrincipalId.value]", "roleDefinitionIdOrName": "Azure AI Developer", "principalType": "ServicePrincipal" + }, + { + "principalId": "[reference('avmContainerApp').outputs.systemAssignedMIPrincipalId.value]", + "roleDefinitionIdOrName": "Cognitive Services User", + "principalType": "ServicePrincipal" + }, + { + "principalId": "[reference('avmContainerApp_Workflow').outputs.systemAssignedMIPrincipalId.value]", + "roleDefinitionIdOrName": "Cognitive Services User", + "principalType": "ServicePrincipal" } ] }, @@ -42475,17 +42481,17 @@ }, "dependsOn": [ "avmAiServices", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", "virtualNetwork" ] }, - "avmAiServices_cu": { + "avmContainerAppEnv": { "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", - "name": "[take(format('avm.res.cognitive-services.account.content-understanding.{0}', variables('solutionSuffix')), 64)]", + "name": "[take(format('avm.res.app.managed-environment.{0}', variables('solutionSuffix')), 64)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -42493,61 +42499,42 @@ "mode": "Incremental", "parameters": { "name": { - "value": "[format('aicu-{0}', variables('solutionSuffix'))]" + "value": "[format('cae-{0}', variables('solutionSuffix'))]" }, "location": { - "value": "[parameters('contentUnderstandingLocation')]" + "value": "[parameters('location')]" }, - "sku": { - "value": "S0" + "tags": { + "value": "[shallowMerge(createArray(resourceGroup().tags, parameters('tags')))]" }, "managedIdentities": { "value": { - "systemAssigned": false, - "userAssignedResourceIds": [ - "[reference('avmManagedIdentity').outputs.resourceId.value]" - ] - } - }, - "kind": { - "value": "AIServices" - }, - "tags": { - "value": { - "app": "[variables('solutionSuffix')]", - "location": "[parameters('location')]" + "systemAssigned": true } }, - "customSubDomainName": { - "value": "[format('aicu-{0}', variables('solutionSuffix'))]" - }, - "disableLocalAuth": { - "value": true + "appLogsConfiguration": "[if(parameters('enableMonitoring'), createObject('value', createObject('destination', 'log-analytics', 'logAnalyticsWorkspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value)), createObject('value', null()))]", + "workloadProfiles": { + "value": [ + { + "name": "Consumption", + "workloadProfileType": "Consumption" + } + ] }, "enableTelemetry": { "value": "[parameters('enableTelemetry')]" }, - "networkAcls": { - "value": { - "bypass": "AzureServices", - "defaultAction": "Allow" - } + "publicNetworkAccess": { + "value": "Enabled" }, - "roleAssignments": { - "value": [ - { - "principalId": "[reference('avmContainerApp').outputs.systemAssignedMIPrincipalId.value]", - "roleDefinitionIdOrName": "a97b65f3-24c7-4388-baec-2e87135dc908", - "principalType": "ServicePrincipal" - }, - { - "principalId": "[reference('avmContainerApp_Workflow').outputs.systemAssignedMIPrincipalId.value]", - "roleDefinitionIdOrName": "a97b65f3-24c7-4388-baec-2e87135dc908", - "principalType": "ServicePrincipal" - } - ] + "platformReservedCidr": { + "value": "172.17.17.0/24" }, - "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]" + "platformReservedDnsIP": { + "value": "172.17.17.17" + }, + "zoneRedundant": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", + "infrastructureSubnetResourceId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('virtualNetwork').outputs.containersSubnetResourceId.value), createObject('value', null()))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -42556,3385 +42543,201 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.41.2.15936", - "templateHash": "8642151282041103672" + "version": "0.42.1.51946", + "templateHash": "11924518395502120940" }, - "name": "Cognitive Services", - "description": "This module deploys a Cognitive Service." + "name": "App ManagedEnvironments", + "description": "This module deploys an App Managed Environment (also known as a Container App Environment)." }, "definitions": { - "privateEndpointOutputType": { + "certificateType": { "type": "object", "properties": { "name": { "type": "string", + "nullable": true, "metadata": { - "description": "The name of the private endpoint." - } - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the private endpoint." + "description": "Optional. The name of the certificate." } }, - "groupId": { + "certificateType": { "type": "string", + "allowedValues": [ + "ImagePullTrustedCA", + "ServerSSLCertificate" + ], "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." + "description": "Optional. The type of the certificate." } }, - "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": { + "certificateValue": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. Specify the name of cognitive service account deployment." + "description": "Optional. The value of the certificate. PFX or PEM blob." } }, - "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", - "nullable": true, - "metadata": { - "description": "Conditional. The version of Cognitive Services account deployment model. Required if the model does not have a default version." - } - } - }, + "certificatePassword": { + "type": "securestring", + "nullable": true, "metadata": { - "description": "Required. Properties of Cognitive Services account deployment model." + "description": "Optional. The password of the certificate." } }, - "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." - } - } - }, + "certificateKeyVaultProperties": { + "$ref": "#/definitions/certificateKeyVaultPropertiesType", "nullable": true, "metadata": { - "description": "Optional. The resource model definition representing SKU." + "description": "Optional. A key vault reference." } }, - "raiPolicyName": { + "location": { "type": "string", "nullable": true, "metadata": { - "description": "Optional. The name of RAI policy." + "description": "Optional. The location for the resource." } }, - "versionUpgradeOption": { - "type": "string", - "nullable": true, + "tags": { + "type": "object", "metadata": { - "description": "Optional. The version upgrade option." - } + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments/certificates@2025-10-02-preview#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true } }, "metadata": { "__bicep_export!": true, - "description": "The type for a cognitive services account deployment." + "description": "The type for a certificate." } }, - "endpointType": { + "storageType": { "type": "object", "properties": { - "name": { + "accessMode": { "type": "string", - "nullable": true, + "allowedValues": [ + "ReadOnly", + "ReadWrite" + ], "metadata": { - "description": "Type of the endpoint." + "description": "Required. Access mode for storage: \"ReadOnly\" or \"ReadWrite\"." } }, - "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": { + "kind": { "type": "string", + "allowedValues": [ + "NFS", + "SMB" + ], "metadata": { - "description": "Required. The key vault name where to store the keys and connection strings generated by the modules." + "description": "Required. Type of storage: \"SMB\" or \"NFS\"." } }, - "accessKey1Name": { + "storageAccountName": { "type": "string", - "nullable": true, "metadata": { - "description": "Optional. The name for the accessKey1 secret to create." + "description": "Required. Storage account name." } }, - "accessKey2Name": { + "name": { "type": "string", - "nullable": true, "metadata": { - "description": "Optional. The name for the accessKey2 secret to create." + "description": "Required. File share name." } } }, "metadata": { "__bicep_export!": true, - "description": "The type of the secrets exported to the provided Key Vault." + "description": "The type of the storage." } }, - "commitmentPlanType": { + "appLogsConfigurationType": { "type": "object", - "properties": { - "autoRenew": { - "type": "bool", - "metadata": { - "description": "Required. Whether the plan should auto-renew at the end of the current commitment period." - } - }, - "current": { - "type": "object", - "properties": { - "count": { - "type": "int", - "metadata": { - "description": "Required. The number of committed instances (e.g., number of containers or cores)." - } - }, - "tier": { - "type": "string", - "metadata": { - "description": "Required. The tier of the commitment plan (e.g., T1, T2)." - } - } - }, - "metadata": { - "description": "Required. The current commitment configuration." - } - }, - "hostingModel": { - "type": "string", - "metadata": { - "description": "Required. The hosting model for the commitment plan. (e.g., DisconnectedContainer, ConnectedContainer, ProvisionedWeb, Web)." - } - }, - "planType": { - "type": "string", - "metadata": { - "description": "Required. The plan type indicating which capability the plan applies to (e.g., NTTS, STT, CUSTOMSTT, ADDON)." - } - }, - "commitmentPlanGuid": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The unique identifier of an existing commitment plan to update. Set to null to create a new plan." - } - }, - "next": { - "type": "object", - "properties": { - "count": { - "type": "int", - "metadata": { - "description": "Required. The number of committed instances for the next period." - } - }, - "tier": { - "type": "string", - "metadata": { - "description": "Required. The tier for the next commitment period." - } - } + "discriminator": { + "propertyName": "destination", + "mapping": { + "azure-monitor": { + "$ref": "#/definitions/appLogsConfigurationMonitorType" }, - "nullable": true, - "metadata": { - "description": "Optional. The configuration of the next commitment period, if scheduled." + "log-analytics": { + "$ref": "#/definitions/appLogsConfigurationLawType" } } }, "metadata": { "__bicep_export!": true, - "description": "The type for a disconnected container commitment plan." + "description": "The type for the App Logs Configuration." } }, - "networkInjectionType": { + "appLogsConfigurationMonitorType": { "type": "object", "properties": { - "scenario": { + "destination": { "type": "string", "allowedValues": [ - "agent", - "none" + "azure-monitor" ], "metadata": { - "description": "Required. The scenario for the network injection." + "description": "Required. The destination of the logs." } - }, - "subnetResourceId": { + } + }, + "metadata": { + "description": "The type for the App Logs Configuration if using azure-monitor." + } + }, + "appLogsConfigurationLawType": { + "type": "object", + "properties": { + "destination": { "type": "string", + "allowedValues": [ + "log-analytics" + ], "metadata": { - "description": "Required. The Resource ID of the subnet on the Virtual Network on which to inject." + "description": "Required. The destination of the logs." } }, - "useMicrosoftManagedNetwork": { - "type": "bool", - "nullable": true, + "logAnalyticsWorkspaceResourceId": { + "type": "string", "metadata": { - "description": "Optional. Whether to use Microsoft Managed Network. Defaults to false." + "description": "Required. Existing Log Analytics Workspace resource ID." } } }, "metadata": { - "__bicep_export!": true, - "description": "Type for network configuration in AI Foundry where virtual network injection occurs to secure scenarios like Agents entirely within a private network." + "description": "The type for the App Logs Configuration if using log-analytics." } }, - "_1.secretSetOutputType": { + "certificateKeyVaultPropertiesType": { "type": "object", "properties": { - "secretResourceId": { - "type": "string", - "metadata": { - "description": "The resourceId of the exported secret." - } - }, - "secretUri": { + "identityResourceId": { "type": "string", "metadata": { - "description": "The secret URI of the exported secret." + "description": "Required. The resource ID of the identity. This is the identity that will be used to access the key vault." } }, - "secretUriWithVersion": { + "keyVaultUrl": { "type": "string", "metadata": { - "description": "The secret URI with version of the exported secret." + "description": "Required. A key vault URL referencing the wildcard certificate that will be used for the custom domain." } } }, "metadata": { - "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "description": "The type for the certificate's key vault properties.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + "sourceTemplate": "certificate/main.bicep" } } }, - "_2.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.1" - } - } - }, - "_2.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.6.1" - } - } - }, - "_2.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.6.1" - } - } - }, - "_2.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.6.1" - } - } - }, - "_2.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.1" - } - } - }, - "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.6.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.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" - } - } - }, - "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.6.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/_2.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/_2.privateEndpointCustomDnsConfigType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Custom DNS configurations." - } - }, - "ipConfigurations": { - "type": "array", - "items": { - "$ref": "#/definitions/_2.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/_2.lockType", - "nullable": true, - "metadata": { - "description": "Optional. Specify the type of lock." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/_2.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": "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" - } - } - }, - "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" - } - } - }, - "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.6.0" - } - } - } - }, - "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." - } - }, - "sku": { - "type": "string", - "defaultValue": "S0", - "allowedValues": [ - "C2", - "C3", - "C4", - "F0", - "F1", - "S", - "S0", - "S1", - "S10", - "S2", - "S3", - "S4", - "S5", - "S6", - "S7", - "S8", - "S9", - "DC0" - ], - "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." - } - }, - "networkInjections": { - "$ref": "#/definitions/networkInjectionType", - "nullable": true, - "metadata": { - "description": "Optional. Specifies in AI Foundry where virtual network injection occurs to secure scenarios like Agents entirely within a private 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", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.CognitiveServices/accounts@2025-04-01-preview#properties/properties/properties/userOwnedStorage" - }, - "description": "Optional. The storage accounts for this resource." - }, - "nullable": true - }, - "managedIdentities": { - "$ref": "#/definitions/managedIdentityAllType", - "nullable": true, - "metadata": { - "description": "Optional. The managed identity definition for this resource." - } - }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." - } - }, - "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." - } - }, - "allowProjectManagement": { - "type": "bool", - "nullable": true, - "metadata": { - "description": "Optional. Enable/Disable project management feature for AI Foundry." - } - }, - "commitmentPlans": { - "type": "array", - "items": { - "$ref": "#/definitions/commitmentPlanType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Commitment plans to deploy for the cognitive services account." - } - } - }, - "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()), 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": { - "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')]" - }, - "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]" - }, - "resources": { - "cMKKeyVault::cMKKey": { - "condition": "[and(and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))), and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))))]", - "existing": true, - "type": "Microsoft.KeyVault/vaults/keys", - "apiVersion": "2025-05-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.cognitiveservices-account.{0}.{1}', replace('0.14.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "resources": [], - "outputs": { - "telemetry": { - "type": "String", - "value": "For more information, see https://aka.ms/avm/TelemetryInfo" - } - } - } - } - }, - "cMKKeyVault": { - "condition": "[and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK')))]", - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2025-05-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": "2025-01-31-preview", - "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]", - "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]", - "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]" - }, - "cognitiveService": { - "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": { - "allowProjectManagement": "[parameters('allowProjectManagement')]", - "customSubDomainName": "[parameters('customSubDomainName')]", - "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())]", - "networkInjections": "[if(not(empty(parameters('networkInjections'))), createArray(createObject('scenario', tryGet(parameters('networkInjections'), 'scenario'), 'subnetArmId', tryGet(parameters('networkInjections'), 'subnetResourceId'), 'useMicrosoftManagedNetwork', coalesce(tryGet(parameters('networkInjections'), 'useMicrosoftManagedNetwork'), false()))), 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')]", - "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', if(not(variables('isHSMManagedCMK')), reference('cMKKeyVault').vaultUri, format('https://{0}.managedhsm.azure.net/', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')))), 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(not(variables('isHSMManagedCMK')), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')), fail('Managed HSM CMK encryption requires specifying the ''keyVersion''.'))))), null())]", - "migrationToken": "[parameters('migrationToken')]", - "restore": "[parameters('restore')]", - "restrictOutboundNetworkAccess": "[parameters('restrictOutboundNetworkAccess')]", - "userOwnedStorage": "[if(not(empty(parameters('userOwnedStorage'))), parameters('userOwnedStorage'), null())]", - "dynamicThrottlingEnabled": "[parameters('dynamicThrottlingEnabled')]" - }, - "dependsOn": [ - "cMKKeyVault", - "cMKKeyVault::cMKKey", - "cMKUserAssignedIdentity" - ] - }, - "cognitiveService_deployments": { - "copy": { - "name": "cognitiveService_deployments", - "count": "[length(coalesce(parameters('deployments'), createArray()))]", - "mode": "serial", - "batchSize": 1 - }, - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2025-06-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')))]", - "dependsOn": [ - "cognitiveService" - ] - }, - "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": "[resourceId('Microsoft.CognitiveServices/accounts', 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": [ - "cognitiveService" - ] - }, - "cognitiveService_commitmentPlans": { - "copy": { - "name": "cognitiveService_commitmentPlans", - "count": "[length(coalesce(parameters('commitmentPlans'), createArray()))]" - }, - "type": "Microsoft.CognitiveServices/accounts/commitmentPlans", - "apiVersion": "2025-06-01", - "name": "[format('{0}/{1}', parameters('name'), format('{0}-{1}', coalesce(parameters('commitmentPlans'), createArray())[copyIndex()].hostingModel, coalesce(parameters('commitmentPlans'), createArray())[copyIndex()].planType))]", - "properties": "[coalesce(parameters('commitmentPlans'), createArray())[copyIndex()]]", - "dependsOn": [ - "cognitiveService" - ] - }, - "cognitiveService_diagnosticSettings": { - "copy": { - "name": "cognitiveService_diagnosticSettings", - "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" - }, - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[resourceId('Microsoft.CognitiveServices/accounts', 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": [ - "cognitiveService" - ] - }, - "cognitiveService_roleAssignments": { - "copy": { - "name": "cognitiveService_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.CognitiveServices/accounts', 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')]" - }, - "dependsOn": [ - "cognitiveService" - ] - }, - "cognitiveService_privateEndpoints": { - "copy": { - "name": "cognitiveService_privateEndpoints", - "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-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.38.5.1644", - "templateHash": "16604612898799598358" - }, - "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, - "description": "The type of a private dns zone group." - } - }, - "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.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": { - "description": "The type of a private DNS zone group configuration.", - "__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.6.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", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" - }, - "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." - }, - "nullable": true - }, - "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", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" - }, - "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." - }, - "nullable": true - }, - "customDnsConfigs": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" - }, - "description": "Optional. Custom DNS configurations." - }, - "nullable": true - }, - "manualPrivateLinkServiceConnections": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" - }, - "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." - }, - "nullable": true - }, - "privateLinkServiceConnections": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" - }, - "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." - }, - "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)))))]" - } - ], - "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": "2025-04-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{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" - } - } - } - } - }, - "privateEndpoint": { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-10-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": "[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": [ - "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": "2025-04-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.38.5.1644", - "templateHash": "24141742673128945" - }, - "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, - "description": "The type of a private DNS zone group configuration." - } - } - }, - "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." - } - } - }, - "resources": { - "privateEndpoint": { - "existing": true, - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-10-01", - "name": "[parameters('privateEndpointName')]" - }, - "privateDnsZoneGroup": { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-10-01", - "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", - "properties": { - "copy": [ - { - "name": "privateDnsZoneConfigs", - "count": "[length(parameters('privateDnsZoneConfigs'))]", - "input": { - "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]", - "properties": { - "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]" - } - } - } - ] - } - } - }, - "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-10-01', 'full').location]" - }, - "customDnsConfigs": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", - "output": true - }, - "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": [ - "cognitiveService" - ] - }, - "secretsExport": { - "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-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.41.2.15936", - "templateHash": "13968722110082077308" - } - }, - "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.6.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.6.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": "2025-05-01", - "name": "[parameters('keyVaultName')]" - }, - "secrets": { - "copy": { - "name": "secrets", - "count": "[length(parameters('secretsToSet'))]" - }, - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2025-05-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": [ - "cognitiveService" - ] - } - }, - "outputs": { - "name": { - "type": "string", - "metadata": { - "description": "The name of the cognitive services account." - }, - "value": "[parameters('name')]" - }, - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the cognitive services account." - }, - "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group the cognitive services account was deployed into." - }, - "value": "[resourceGroup().name]" - }, - "endpoint": { - "type": "string", - "metadata": { - "description": "The service endpoint of the cognitive services account." - }, - "value": "[reference('cognitiveService').endpoint]" - }, - "endpoints": { - "$ref": "#/definitions/endpointType", - "metadata": { - "description": "All endpoints available for the cognitive services account, types depends on the cognitive service kind." - }, - "value": "[reference('cognitiveService').endpoints]" - }, - "systemAssignedMIPrincipalId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "The principal ID of the system assigned identity." - }, - "value": "[tryGet(tryGet(reference('cognitiveService', '2025-06-01', 'full'), 'identity'), 'principalId')]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('cognitiveService', '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(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]" - } - } - }, - "primaryKey": { - "type": "securestring", - "nullable": true, - "metadata": { - "description": "The primary access key." - }, - "value": "[if(not(parameters('disableLocalAuth')), listKeys('cognitiveService', '2025-06-01').key1, null())]" - }, - "secondaryKey": { - "type": "securestring", - "nullable": true, - "metadata": { - "description": "The secondary access key." - }, - "value": "[if(not(parameters('disableLocalAuth')), listKeys('cognitiveService', '2025-06-01').key2, null())]" - } - } - } - }, - "dependsOn": [ - "avmContainerApp", - "avmContainerApp_Workflow", - "avmManagedIdentity" - ] - }, - "contentUnderstandingPrivateEndpoint": { - "condition": "[parameters('enablePrivateNetworking')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[take(format('avm.res.network.private-endpoint.aicu-{0}', variables('solutionSuffix')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('pep-aicu-{0}', variables('solutionSuffix'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "customNetworkInterfaceName": { - "value": "[format('nic-aicu-{0}', variables('solutionSuffix'))]" - }, - "privateLinkServiceConnections": { - "value": [ - { - "name": "[format('pep-aicu-{0}-cognitiveservices-connection', variables('solutionSuffix'))]", - "properties": { - "privateLinkServiceId": "[reference('avmAiServices_cu').outputs.resourceId.value]", - "groupIds": [ - "account" - ] - } - } - ] - }, - "privateDnsZoneGroup": { - "value": { - "privateDnsZoneGroupConfigs": [ - { - "name": "aicu-dns-zone-cognitiveservices", - "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)).outputs.resourceId.value]" - }, - { - "name": "ai-services-dns-zone-aiservices", - "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)).outputs.resourceId.value]" - }, - { - "name": "aicu-dns-zone-contentunderstanding", - "privateDnsZoneResourceId": "[reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)).outputs.resourceId.value]" - } - ] - } - }, - "subnetResourceId": { - "value": "[reference('virtualNetwork').outputs.backendSubnetResourceId.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.41.2.15936", - "templateHash": "18436885663402767850" - }, - "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, - "description": "The type of a private dns zone group." - } - }, - "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.7.0" - } - } - }, - "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": { - "description": "The type of a private DNS zone group configuration.", - "__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.7.0" - } - } - } - }, - "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", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/properties/properties/ipConfigurations" - }, - "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." - }, - "nullable": true - }, - "ipVersionType": { - "type": "string", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/properties/properties/ipVersionType" - }, - "description": "Optional. Specifies the IP version type for the private IPs of the private endpoint. If not defined, this defaults to IPv4." - }, - "defaultValue": "IPv4" - }, - "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", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/tags" - }, - "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." - }, - "nullable": true - }, - "customDnsConfigs": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/properties/properties/customDnsConfigs" - }, - "description": "Optional. Custom DNS configurations." - }, - "nullable": true - }, - "manualPrivateLinkServiceConnections": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/properties/properties/manualPrivateLinkServiceConnections" - }, - "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." - }, - "nullable": true - }, - "privateLinkServiceConnections": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/properties/properties/privateLinkServiceConnections" - }, - "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." - }, - "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)))))]" - } - ], - "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": "2025-04-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{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" - } - } - } - } - }, - "privateEndpoint": { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2025-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')]" - }, - "ipVersionType": "[parameters('ipVersionType')]" - } - }, - "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": "[resourceId('Microsoft.Network/privateEndpoints', 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": [ - "privateEndpoint" - ] - }, - "privateEndpoint_roleAssignments": { - "copy": { - "name": "privateEndpoint_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[resourceId('Microsoft.Network/privateEndpoints', 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": "2025-04-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.41.2.15936", - "templateHash": "9935179114830442414" - }, - "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, - "description": "The type of a private DNS zone group configuration." - } - } - }, - "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." - } - } - }, - "resources": { - "privateEndpoint": { - "existing": true, - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2025-05-01", - "name": "[parameters('privateEndpointName')]" - }, - "privateDnsZoneGroup": { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2025-05-01", - "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", - "properties": { - "copy": [ - { - "name": "privateDnsZoneConfigs", - "count": "[length(parameters('privateDnsZoneConfigs'))]", - "input": { - "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]", - "properties": { - "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]" - } - } - } - ] - } - } - }, - "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', '2025-05-01', 'full').location]" - }, - "customDnsConfigs": { - "type": "array", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.Network/privateEndpoints@2025-05-01#properties/properties/properties/customDnsConfigs", - "output": true - }, - "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": [ - "avmAiServices_cu", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').contentUnderstanding)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", - "virtualNetwork" - ] - }, - "avmContainerAppEnv": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[take(format('avm.res.app.managed-environment.{0}', variables('solutionSuffix')), 64)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "name": { - "value": "[format('cae-{0}', variables('solutionSuffix'))]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[shallowMerge(createArray(resourceGroup().tags, parameters('tags')))]" - }, - "managedIdentities": { - "value": { - "systemAssigned": true - } - }, - "appLogsConfiguration": "[if(parameters('enableMonitoring'), createObject('value', createObject('destination', 'log-analytics', 'logAnalyticsWorkspaceResourceId', reference('logAnalyticsWorkspace').outputs.resourceId.value)), createObject('value', null()))]", - "workloadProfiles": { - "value": [ - { - "name": "Consumption", - "workloadProfileType": "Consumption" - } - ] - }, - "enableTelemetry": { - "value": "[parameters('enableTelemetry')]" - }, - "publicNetworkAccess": { - "value": "Enabled" - }, - "platformReservedCidr": { - "value": "172.17.17.0/24" - }, - "platformReservedDnsIP": { - "value": "172.17.17.17" - }, - "zoneRedundant": "[if(parameters('enablePrivateNetworking'), createObject('value', true()), createObject('value', false()))]", - "infrastructureSubnetResourceId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('virtualNetwork').outputs.containersSubnetResourceId.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.42.1.51946", - "templateHash": "11924518395502120940" - }, - "name": "App ManagedEnvironments", - "description": "This module deploys an App Managed Environment (also known as a Container App Environment)." - }, - "definitions": { - "certificateType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name of the certificate." - } - }, - "certificateType": { - "type": "string", - "allowedValues": [ - "ImagePullTrustedCA", - "ServerSSLCertificate" - ], - "nullable": true, - "metadata": { - "description": "Optional. The type of the certificate." - } - }, - "certificateValue": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The value of the certificate. PFX or PEM blob." - } - }, - "certificatePassword": { - "type": "securestring", - "nullable": true, - "metadata": { - "description": "Optional. The password of the certificate." - } - }, - "certificateKeyVaultProperties": { - "$ref": "#/definitions/certificateKeyVaultPropertiesType", - "nullable": true, - "metadata": { - "description": "Optional. A key vault reference." - } - }, - "location": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The location for the resource." - } - }, - "tags": { - "type": "object", - "metadata": { - "__bicep_resource_derived_type!": { - "source": "Microsoft.App/managedEnvironments/certificates@2025-10-02-preview#properties/tags" - }, - "description": "Optional. Tags of the resource." - }, - "nullable": true - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for a certificate." - } - }, - "storageType": { - "type": "object", - "properties": { - "accessMode": { - "type": "string", - "allowedValues": [ - "ReadOnly", - "ReadWrite" - ], - "metadata": { - "description": "Required. Access mode for storage: \"ReadOnly\" or \"ReadWrite\"." - } - }, - "kind": { - "type": "string", - "allowedValues": [ - "NFS", - "SMB" - ], - "metadata": { - "description": "Required. Type of storage: \"SMB\" or \"NFS\"." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Storage account name." - } - }, - "name": { - "type": "string", - "metadata": { - "description": "Required. File share name." - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type of the storage." - } - }, - "appLogsConfigurationType": { - "type": "object", - "discriminator": { - "propertyName": "destination", - "mapping": { - "azure-monitor": { - "$ref": "#/definitions/appLogsConfigurationMonitorType" - }, - "log-analytics": { - "$ref": "#/definitions/appLogsConfigurationLawType" - } - } - }, - "metadata": { - "__bicep_export!": true, - "description": "The type for the App Logs Configuration." - } - }, - "appLogsConfigurationMonitorType": { - "type": "object", - "properties": { - "destination": { - "type": "string", - "allowedValues": [ - "azure-monitor" - ], - "metadata": { - "description": "Required. The destination of the logs." - } - } - }, - "metadata": { - "description": "The type for the App Logs Configuration if using azure-monitor." - } - }, - "appLogsConfigurationLawType": { - "type": "object", - "properties": { - "destination": { - "type": "string", - "allowedValues": [ - "log-analytics" - ], - "metadata": { - "description": "Required. The destination of the logs." - } - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string", - "metadata": { - "description": "Required. Existing Log Analytics Workspace resource ID." - } - } - }, - "metadata": { - "description": "The type for the App Logs Configuration if using log-analytics." - } - }, - "certificateKeyVaultPropertiesType": { - "type": "object", - "properties": { - "identityResourceId": { - "type": "string", - "metadata": { - "description": "Required. The resource ID of the identity. This is the identity that will be used to access the key vault." - } - }, - "keyVaultUrl": { - "type": "string", - "metadata": { - "description": "Required. A key vault URL referencing the wildcard certificate that will be used for the custom domain." - } - } - }, - "metadata": { - "description": "The type for the certificate's key vault properties.", - "__bicep_imported_from!": { - "sourceTemplate": "certificate/main.bicep" - } - } - }, - "lockType": { + "lockType": { "type": "object", "properties": { "name": { @@ -59999,7 +56802,7 @@ }, { "name": "APP_CONTENT_UNDERSTANDING_ENDPOINT", - "value": "[reference('avmAiServices_cu').outputs.endpoint.value]" + "value": "[reference('avmAiServices').outputs.endpoint.value]" }, { "name": "APP_COSMOS_CONTAINER_PROCESS", @@ -62348,7 +59151,6 @@ }, "dependsOn": [ "avmAiServices", - "avmAiServices_cu", "avmContainerApp", "avmContainerApp_API", "avmContainerApp_Web", @@ -66229,8 +63031,7 @@ "avmAppConfig", "avmContainerAppEnv", "avmContainerRegistryReader", - "cognitiveServicePrivateEndpoint", - "contentUnderstandingPrivateEndpoint" + "cognitiveServicePrivateEndpoint" ] }, "avmContainerApp_API_update": { @@ -69637,9 +66438,9 @@ "CONTENT_UNDERSTANDING_ACCOUNT_NAME": { "type": "string", "metadata": { - "description": "The name of the Content Understanding AI Services account." + "description": "The name of the AI Services account that hosts both Azure OpenAI and Content Understanding GA." }, - "value": "[reference('avmAiServices_cu').outputs.name.value]" + "value": "[reference('avmAiServices').outputs.name.value]" }, "AZURE_RESOURCE_GROUP": { "type": "string", diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 27461ece..44153d57 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -8,9 +8,6 @@ "location": { "value": "${AZURE_LOCATION}" }, - "contentUnderstandingLocation": { - "value": "${AZURE_ENV_CU_LOCATION}" - }, "azureAiServiceLocation": { "value": "${AZURE_ENV_AI_SERVICE_LOCATION}" }, diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index 7fdeab31..8e145a07 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -8,9 +8,6 @@ "location": { "value": "${AZURE_LOCATION}" }, - "contentUnderstandingLocation": { - "value": "${AZURE_ENV_CU_LOCATION}" - }, "azureAiServiceLocation": { "value": "${AZURE_ENV_AI_SERVICE_LOCATION}" }, diff --git a/infra/main_custom.bicep b/infra/main_custom.bicep index b63bbace..fd462dc6 100644 --- a/infra/main_custom.bicep +++ b/infra/main_custom.bicep @@ -28,26 +28,20 @@ param solutionName string = 'cps' param location string @minLength(1) -@description('Optional. Location for the Azure AI Content Understanding service deployment.') -@allowed(['WestUS', 'SwedenCentral', 'AustraliaEast']) -@metadata({ - azd: { - type: 'location' - } -}) -param contentUnderstandingLocation string = 'WestUS' - @allowed([ 'australiaeast' - 'centralus' - 'eastasia' + 'eastus' 'eastus2' 'japaneast' - 'northeurope' + 'southcentralus' 'southeastasia' + 'swedencentral' 'uksouth' + 'westeurope' + 'westus' + 'westus3' ]) -@description('Required. Location for the Azure AI Services deployment.') +@description('Required. Location for the Azure AI Services deployment. Must support both Azure OpenAI gpt-5.1 (GlobalStandard) and Azure AI Content Understanding GA. If the deploymentType param is set to Standard, override the metadata.azd.usageName below to reference OpenAI.Standard.gpt-5.1 instead.') @metadata({ azd: { type: 'location' @@ -750,6 +744,16 @@ module avmAiServices 'modules/account/aifoundry.bicep' = { roleDefinitionIdOrName: 'Azure AI Developer' principalType: 'ServicePrincipal' } + { + principalId: avmContainerApp.outputs.systemAssignedMIPrincipalId! + roleDefinitionIdOrName: 'Cognitive Services User' + principalType: 'ServicePrincipal' + } + { + principalId: avmContainerApp_Workflow.outputs.systemAssignedMIPrincipalId! + roleDefinitionIdOrName: 'Cognitive Services User' + principalType: 'ServicePrincipal' + } ] networkAcls: { bypass: 'AzureServices' @@ -819,84 +823,6 @@ module cognitiveServicePrivateEndpoint 'br/public:avm/res/network/private-endpoi } } -module avmAiServices_cu 'br/public:avm/res/cognitive-services/account:0.14.2' = { - name: take('avm.res.cognitive-services.account.content-understanding.${solutionSuffix}', 64) - - params: { - name: 'aicu-${solutionSuffix}' - location: contentUnderstandingLocation - sku: 'S0' - managedIdentities: { - systemAssigned: false - userAssignedResourceIds: [ - avmManagedIdentity.outputs.resourceId // Use the managed identity created above - ] - } - kind: 'AIServices' - tags: { - app: solutionSuffix - location: location - } - customSubDomainName: 'aicu-${solutionSuffix}' - disableLocalAuth: true - enableTelemetry: enableTelemetry - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Allow' // Always allow for AI Services - } - roleAssignments: [ - { - principalId: avmContainerApp.outputs.systemAssignedMIPrincipalId! - roleDefinitionIdOrName: 'a97b65f3-24c7-4388-baec-2e87135dc908' - principalType: 'ServicePrincipal' - } - { - principalId: avmContainerApp_Workflow.outputs.systemAssignedMIPrincipalId! - roleDefinitionIdOrName: 'a97b65f3-24c7-4388-baec-2e87135dc908' - principalType: 'ServicePrincipal' - } - ] - - publicNetworkAccess: (enablePrivateNetworking) ? 'Disabled' : 'Enabled' - } -} - -module contentUnderstandingPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.12.0' = if (enablePrivateNetworking) { - name: take('avm.res.network.private-endpoint.aicu-${solutionSuffix}', 64) - params: { - name: 'pep-aicu-${solutionSuffix}' - location: location - tags: tags - customNetworkInterfaceName: 'nic-aicu-${solutionSuffix}' - privateLinkServiceConnections: [ - { - name: 'pep-aicu-${solutionSuffix}-cognitiveservices-connection' - properties: { - privateLinkServiceId: avmAiServices_cu.outputs.resourceId - groupIds: ['account'] - } - } - ] - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { - name: 'aicu-dns-zone-cognitiveservices' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId - } - { - name: 'ai-services-dns-zone-aiservices' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.aiServices]!.outputs.resourceId - } - { - name: 'aicu-dns-zone-contentunderstanding' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.contentUnderstanding]!.outputs.resourceId - } - ] - } - subnetResourceId: virtualNetwork!.outputs.backendSubnetResourceId - } -} - // ========== Container App Environment ========== // module avmContainerAppEnv 'br/public:avm/res/app/managed-environment:0.13.2' = { name: take('avm.res.app.managed-environment.${solutionSuffix}', 64) @@ -1431,7 +1357,7 @@ module avmAppConfig 'br/public:avm/res/app-configuration/configuration-store:0.9 } { name: 'APP_CONTENT_UNDERSTANDING_ENDPOINT' - value: avmAiServices_cu.outputs.endpoint //TODO: replace with actual endpoint + value: avmAiServices.outputs.endpoint } { name: 'APP_COSMOS_CONTAINER_PROCESS' @@ -1711,7 +1637,6 @@ module avmContainerApp_update 'br/public:avm/res/app/container-app:0.22.1' = { } dependsOn: [ cognitiveServicePrivateEndpoint - contentUnderstandingPrivateEndpoint ] } @@ -1963,8 +1888,8 @@ output CONTAINER_REGISTRY_LOGIN_SERVER string = avmContainerRegistry.outputs.log @description('The Azure Container Registry endpoint for AZD custom deployment.') output AZURE_CONTAINER_REGISTRY_ENDPOINT string = avmContainerRegistry.outputs.loginServer -@description('The name of the Content Understanding AI Services account.') -output CONTENT_UNDERSTANDING_ACCOUNT_NAME string = avmAiServices_cu.outputs.name +@description('The name of the AI Services account that hosts both Azure OpenAI and Content Understanding.') +output CONTENT_UNDERSTANDING_ACCOUNT_NAME string = avmAiServices.outputs.name @description('The resource group the resources were deployed into.') output AZURE_RESOURCE_GROUP string = resourceGroup().name diff --git a/src/ContentProcessor/src/libs/azure_helper/content_understanding.py b/src/ContentProcessor/src/libs/azure_helper/content_understanding.py index 78cd0bfc..26c3a861 100644 --- a/src/ContentProcessor/src/libs/azure_helper/content_understanding.py +++ b/src/ContentProcessor/src/libs/azure_helper/content_understanding.py @@ -4,8 +4,8 @@ """Azure Content Understanding REST client. Manages analyzer lifecycle (create / list / delete) and document analysis -operations against the Azure Content Understanding preview API, used by -the extract pipeline step. +operations against the Azure Content Understanding GA API +(api-version=2025-11-01), used by the extract pipeline step. """ import json @@ -22,7 +22,7 @@ class AzureContentUnderstandingHelper: - """REST client for the Azure Content Understanding preview API. + """REST client for the Azure Content Understanding GA API. Responsibilities: 1. Manage analyzer lifecycle (create, list, get, delete). @@ -36,7 +36,7 @@ class AzureContentUnderstandingHelper: def __init__( self, endpoint: str, - api_version: str = "2024-12-01-preview", + api_version: str = "2025-11-01", x_ms_useragent: str = "cps-contentunderstanding/client", ): self.credential = get_azure_credential() @@ -63,14 +63,21 @@ def _get_analyzer_list_url(self, endpoint, api_version): def _get_analyze_url(self, endpoint, api_version, analyzer_id): return f"{endpoint}/contentunderstanding/analyzers/{analyzer_id}:analyze?api-version={api_version}" # noqa - def _get_training_data_config( + def _get_analyze_binary_url(self, endpoint, api_version, analyzer_id): + return f"{endpoint}/contentunderstanding/analyzers/{analyzer_id}:analyzeBinary?api-version={api_version}" # noqa + + def _get_knowledge_source_config( self, storage_container_sas_url, storage_container_path_prefix ): - return { - "containerUrl": storage_container_sas_url, - "kind": "blob", - "prefix": storage_container_path_prefix, - } + # GA renamed the analyzer-template field `trainingData` (object) to + # `knowledgeSources` (array of source objects). + return [ + { + "kind": "blob", + "containerUrl": storage_container_sas_url, + "prefix": storage_container_path_prefix, + } + ] def _get_headers(self, api_token, x_ms_useragent): """Build default HTTP headers for Content Understanding requests. @@ -164,7 +171,7 @@ def begin_create_analyzer( training_storage_container_sas_url and training_storage_container_path_prefix ): # noqa - analyzer_template["trainingData"] = self._get_training_data_config( + analyzer_template["knowledgeSources"] = self._get_knowledge_source_config( training_storage_container_sas_url, training_storage_container_path_prefix, ) @@ -204,7 +211,11 @@ def delete_analyzer(self, analyzer_id: str): def begin_analyze_stream(self, analyzer_id: str, file_stream: bytes): """ - Begins the analysis of a file or URL using the specified analyzer. + Begins the analysis of a binary file stream using the specified analyzer. + + In Content Understanding GA, raw byte uploads must target the + ``:analyzeBinary`` action (the ``:analyze`` action is JSON-only and + accepts a ``url`` in the request body). Args: analyzer_id (str): The ID of the analyzer to use. @@ -214,13 +225,14 @@ def begin_analyze_stream(self, analyzer_id: str, file_stream: bytes): Response: The response from the analysis request. Raises: - ValueError: If the file location is not a valid path or URL. HTTPError: If the HTTP request returned an unsuccessful status code. """ headers = {"Content-Type": "application/octet-stream"} headers.update(self._headers) response = requests.post( - url=self._get_analyze_url(self._endpoint, self._api_version, analyzer_id), + url=self._get_analyze_binary_url( + self._endpoint, self._api_version, analyzer_id + ), headers=headers, data=file_stream, ) @@ -233,6 +245,10 @@ def begin_analyze(self, analyzer_id: str, file_location: str): """ Begins the analysis of a file or URL using the specified analyzer. + For local files, byte content is uploaded via the GA ``:analyzeBinary`` + action; for HTTP/HTTPS URLs, the URL is sent as JSON to the ``:analyze`` + action. + Args: analyzer_id (str): The ID of the analyzer to use. file_location (str): The path to the file or the URL to analyze. @@ -244,34 +260,31 @@ def begin_analyze(self, analyzer_id: str, file_location: str): ValueError: If the file location is not a valid path or URL. HTTPError: If the HTTP request returned an unsuccessful status code. """ - data = None if Path(file_location).exists(): with open(file_location, "rb") as file: data = file.read() headers = {"Content-Type": "application/octet-stream"} - elif "https://" in file_location or "http://" in file_location: - data = {"url": file_location} - headers = {"Content-Type": "application/json"} - else: - raise ValueError("File location must be a valid path or URL.") - - headers.update(self._headers) - if isinstance(data, dict): + headers.update(self._headers) response = requests.post( - url=self._get_analyze_url( + url=self._get_analyze_binary_url( self._endpoint, self._api_version, analyzer_id ), headers=headers, - json=data, + data=data, ) - else: + elif "https://" in file_location or "http://" in file_location: + data = {"url": file_location} + headers = {"Content-Type": "application/json"} + headers.update(self._headers) response = requests.post( url=self._get_analyze_url( self._endpoint, self._api_version, analyzer_id ), headers=headers, - data=data, + json=data, ) + else: + raise ValueError("File location must be a valid path or URL.") response.raise_for_status() self._logger.info( @@ -282,12 +295,19 @@ def begin_analyze(self, analyzer_id: str, file_location: str): def get_image_from_analyze_operation( self, analyze_response: Response, image_id: str ): - """Retrieves an image from the analyze operation using the image ID. + """Retrieves a generated file (e.g., a rendered page image) from a + completed analyze operation by its file id / path. + + In Content Understanding GA the file-retrieval URL changed from + ``{operationLocation}/images/{imageId}`` to + ``{operationLocation}/files/{fileId}`` (where ``operationLocation`` now + ends in ``/analyzerResults/{operationId}``). + Args: analyze_response (Response): The response object from the analyze operation. - image_id (str): The ID of the image to retrieve. + image_id (str): The id (or path) of the file to retrieve. Returns: - bytes: The image content as a byte string. + bytes: The file content as a byte string. """ operation_location = analyze_response.headers.get("operation-location", "") if not operation_location: @@ -296,7 +316,7 @@ def get_image_from_analyze_operation( ) operation_location = operation_location.split("?api-version")[0] image_retrieval_url = ( - f"{operation_location}/images/{image_id}?api-version={self._api_version}" + f"{operation_location}/files/{image_id}?api-version={self._api_version}" ) try: response = requests.get(url=image_retrieval_url, headers=self._headers) diff --git a/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py b/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py index cbffaf0e..15ef812a 100644 --- a/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py +++ b/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py @@ -100,11 +100,11 @@ class Config: class Page(BaseModel): pageNumber: int - angle: float + angle: Optional[float] = None width: float height: float - spans: List[Span] - words: List[Word] + spans: Optional[List[Span]] = None + words: Optional[List[Word]] = None lines: Optional[List[Line]] = [] paragraphs: Optional[List[Paragraph]] = [] @@ -116,13 +116,30 @@ class DocumentContent(BaseModel): endPageNumber: int unit: str pages: List[Page] + paragraphs: Optional[List[Paragraph]] = None + + +class Warning(BaseModel): + """Mirrors the Azure.Core.Foundations.Error shape returned in + ``ResultData.warnings`` by the Content Understanding GA API. + + The API now emits structured warning objects (with ``code`` / ``message`` + plus optional ``target`` / ``details``) instead of plain strings, so this + model accepts arbitrary nested error payloads via ``model_config``. + """ + + code: str + message: str + target: Optional[str] = None + + model_config = {"extra": "allow"} class ResultData(BaseModel): analyzerId: str apiVersion: str createdAt: str - warnings: List[str] + warnings: List[Warning] = [] contents: List[DocumentContent] diff --git a/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py b/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py index 624f1063..11d82d0b 100644 --- a/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py +++ b/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py @@ -13,6 +13,7 @@ Paragraph, ResultData, Span, + Warning, Word, ) @@ -163,7 +164,7 @@ def test_construction(self): status="succeeded", result=ResultData( analyzerId="prebuilt", - apiVersion="2024-01-01", + apiVersion="2025-11-01", createdAt="2024-01-01T00:00:00Z", warnings=[], contents=[], @@ -172,3 +173,50 @@ def test_construction(self): assert result.id == "r-1" assert result.status == "succeeded" assert result.result.contents == [] + + def test_warnings_are_structured(self): + # In CU GA the `warnings` field switched from List[str] to a list of + # Azure.Core.Foundations.Error objects with code/message/target/details. + result = AnalyzedResult( + id="r-2", + status="succeeded", + result=ResultData( + analyzerId="prebuilt-documentAnalyzer", + apiVersion="2025-11-01", + createdAt="2025-11-01T00:00:00Z", + warnings=[ + { + "code": "PageImageNotAvailable", + "message": "Generated image is not available for page 3.", + "target": "pages[2]", + "details": [], + } + ], + contents=[], + ), + ) + assert len(result.result.warnings) == 1 + warning = result.result.warnings[0] + assert isinstance(warning, Warning) + assert warning.code == "PageImageNotAvailable" + assert warning.message.startswith("Generated image") + assert warning.target == "pages[2]" + + +class TestPageGAOptionals: + """In CU GA the `angle`, `spans`, and `words` fields on Page are optional + (the prebuilt-documentAnalyzer can omit them when the underlying source + has no OCR layer).""" + + def test_page_can_be_constructed_without_angle_spans_words(self): + page = Page( + pageNumber=1, + width=8.5, + height=11.0, + ) + assert page.pageNumber == 1 + assert page.angle is None + assert page.spans is None + assert page.words is None + assert page.lines == [] + assert page.paragraphs == [] diff --git a/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py b/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py index 624f1063..11d82d0b 100644 --- a/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py +++ b/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py @@ -13,6 +13,7 @@ Paragraph, ResultData, Span, + Warning, Word, ) @@ -163,7 +164,7 @@ def test_construction(self): status="succeeded", result=ResultData( analyzerId="prebuilt", - apiVersion="2024-01-01", + apiVersion="2025-11-01", createdAt="2024-01-01T00:00:00Z", warnings=[], contents=[], @@ -172,3 +173,50 @@ def test_construction(self): assert result.id == "r-1" assert result.status == "succeeded" assert result.result.contents == [] + + def test_warnings_are_structured(self): + # In CU GA the `warnings` field switched from List[str] to a list of + # Azure.Core.Foundations.Error objects with code/message/target/details. + result = AnalyzedResult( + id="r-2", + status="succeeded", + result=ResultData( + analyzerId="prebuilt-documentAnalyzer", + apiVersion="2025-11-01", + createdAt="2025-11-01T00:00:00Z", + warnings=[ + { + "code": "PageImageNotAvailable", + "message": "Generated image is not available for page 3.", + "target": "pages[2]", + "details": [], + } + ], + contents=[], + ), + ) + assert len(result.result.warnings) == 1 + warning = result.result.warnings[0] + assert isinstance(warning, Warning) + assert warning.code == "PageImageNotAvailable" + assert warning.message.startswith("Generated image") + assert warning.target == "pages[2]" + + +class TestPageGAOptionals: + """In CU GA the `angle`, `spans`, and `words` fields on Page are optional + (the prebuilt-documentAnalyzer can omit them when the underlying source + has no OCR layer).""" + + def test_page_can_be_constructed_without_angle_spans_words(self): + page = Page( + pageNumber=1, + width=8.5, + height=11.0, + ) + assert page.pageNumber == 1 + assert page.angle is None + assert page.spans is None + assert page.words is None + assert page.lines == [] + assert page.paragraphs == [] From cb2834f1dbd3f7eb4b649db6baf70307eea4db7a Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Wed, 6 May 2026 16:45:52 +0530 Subject: [PATCH 2/8] Fix root-relative links in CustomizingAzdParameters.md Replace /docs/re-use-*.md with relative paths so the lychee link checker resolves them. Pre-existing links flagged on this PR because the file was modified by the GA migration commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/CustomizingAzdParameters.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CustomizingAzdParameters.md b/docs/CustomizingAzdParameters.md index 398b53e0..b3fdec52 100644 --- a/docs/CustomizingAzdParameters.md +++ b/docs/CustomizingAzdParameters.md @@ -18,8 +18,8 @@ By default this template will use the environment name as the prefix to prevent | `AZURE_ENV_GPT_MODEL_CAPACITY` | integer | `300` | Sets the model capacity (minimum 1). Default: 300. Optimal: 500 for multi-document claim processing. | | `AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT` | string | `cpscontainerreg.azurecr.io` | Sets the public container image endpoint for pulling pre-built images. | | `AZURE_ENV_IMAGETAG` | string | `latest_v2` | Sets the container image tag (e.g., `latest_v2`, `dev`, `demo`, `hotfix`). | -| `AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID` | string | Guide to get your [Existing Workspace Resource ID](/docs/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of provisioning a new one. | -| `AZURE_EXISTING_AIPROJECT_RESOURCE_ID` | string | Guide to get your [Existing AI Project Resource ID](/docs/re-use-foundry-project.md) | Reuses an existing AI Foundry and AI Foundry Project instead of creating a new one. | +| `AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID` | string | Guide to get your [Existing Workspace Resource ID](re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of provisioning a new one. | +| `AZURE_EXISTING_AIPROJECT_RESOURCE_ID` | string | Guide to get your [Existing AI Project Resource ID](re-use-foundry-project.md) | Reuses an existing AI Foundry and AI Foundry Project instead of creating a new one. | | `AZURE_ENV_VM_SIZE` | string | `Standard_D2s_v5` | Overrides the jumpbox VM size (private networking only). Default: `Standard_D2s_v5`. | ## How to Set a Parameter From e5ba300d8ecd18840dd27313f28f181a8d302a13 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 7 May 2026 12:23:02 +0530 Subject: [PATCH 3/8] docs: add region requirement note for re-use Foundry project flow The existing Foundry must support both gpt-5.1 (GlobalStandard) and Content Understanding GA, otherwise deployment will fail with downstream model/analyzer errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/re-use-foundry-project.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/re-use-foundry-project.md b/docs/re-use-foundry-project.md index 8c7ff463..2e2fe666 100644 --- a/docs/re-use-foundry-project.md +++ b/docs/re-use-foundry-project.md @@ -2,6 +2,13 @@ # Reusing an Existing Azure AI Foundry Project To configure your environment to use an existing Azure AI Foundry Project, follow these steps: + +> **⚠️ Region requirement** +> +> The existing Foundry project must reside in a region that supports **both** the GPT model deployed by this accelerator (default `gpt-5.1` with `GlobalStandard` deployment type) **and** Azure AI Content Understanding (GA).
+> Supported regions: `australiaeast`, `eastus`, `eastus2`, `northcentralus`, `southcentralus`, `swedencentral`, `switzerlandnorth`, `westeurope`, `westus`, `westus2`, `westus3`.
+> If the existing project is in a different region, deployment will fail. + --- ### 1. Go to Azure Portal Go to https://portal.azure.com From 9524e7b8938789b9b73beefbb038c61b6f4e1ad1 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 7 May 2026 12:30:41 +0530 Subject: [PATCH 4/8] docs: clarify region-mismatch consequence in re-use Foundry note Deploy may fail at provisioning time for unsupported gpt-5.1 region, or appear to succeed but break at runtime when Content Understanding GA is unavailable in the existing Foundry's region. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/re-use-foundry-project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/re-use-foundry-project.md b/docs/re-use-foundry-project.md index 2e2fe666..65ace6d7 100644 --- a/docs/re-use-foundry-project.md +++ b/docs/re-use-foundry-project.md @@ -7,7 +7,7 @@ To configure your environment to use an existing Azure AI Foundry Project, follo > > The existing Foundry project must reside in a region that supports **both** the GPT model deployed by this accelerator (default `gpt-5.1` with `GlobalStandard` deployment type) **and** Azure AI Content Understanding (GA).
> Supported regions: `australiaeast`, `eastus`, `eastus2`, `northcentralus`, `southcentralus`, `swedencentral`, `switzerlandnorth`, `westeurope`, `westus`, `westus2`, `westus3`.
-> If the existing project is in a different region, deployment will fail. +> If the existing project is in a different region, deployment will fail or the application will not work correctly. --- ### 1. Go to Azure Portal From 980b4133d6b7f55c3f71f737bf44c8902bbb4254 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 7 May 2026 12:45:19 +0530 Subject: [PATCH 5/8] docs: fix root-relative DeploymentGuide.md links in re-use-foundry-project.md Lychee CI rejects root-relative paths when no base dir is configured. Switch to relative ./DeploymentGuide.md paths matching the fix already applied to CustomizingAzdParameters.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/re-use-foundry-project.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/re-use-foundry-project.md b/docs/re-use-foundry-project.md index 65ace6d7..bde9a26f 100644 --- a/docs/re-use-foundry-project.md +++ b/docs/re-use-foundry-project.md @@ -1,4 +1,4 @@ -[← Back to *DEPLOYMENT* guide](/docs/DeploymentGuide.md#deployment-options--steps) +[← Back to *DEPLOYMENT* guide](./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: @@ -48,4 +48,4 @@ azd env set AZURE_EXISTING_AIPROJECT_RESOURCE_ID '` with the value obtained from Step 5. ### 7. Continue Deployment -Proceed with the next steps in the [deployment guide](/docs/DeploymentGuide.md#deployment-options--steps). +Proceed with the next steps in the [deployment guide](./DeploymentGuide.md#deployment-options--steps). From 0f069ac52dff35119df8e40187d95c5fe0da1680 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 7 May 2026 13:21:42 +0530 Subject: [PATCH 6/8] Correct supported regions list in re-use-foundry-project.md Align the listed regions with the @allowed list enforced by infra/main.bicep and infra/main_custom.bicep for the unified AI Services account (japaneast, southeastasia, uksouth added; northcentralus, switzerlandnorth, westus2 removed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/re-use-foundry-project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/re-use-foundry-project.md b/docs/re-use-foundry-project.md index bde9a26f..b02181c0 100644 --- a/docs/re-use-foundry-project.md +++ b/docs/re-use-foundry-project.md @@ -6,7 +6,7 @@ To configure your environment to use an existing Azure AI Foundry Project, follo > **⚠️ Region requirement** > > The existing Foundry project must reside in a region that supports **both** the GPT model deployed by this accelerator (default `gpt-5.1` with `GlobalStandard` deployment type) **and** Azure AI Content Understanding (GA).
-> Supported regions: `australiaeast`, `eastus`, `eastus2`, `northcentralus`, `southcentralus`, `swedencentral`, `switzerlandnorth`, `westeurope`, `westus`, `westus2`, `westus3`.
+> Supported regions: `australiaeast`, `eastus`, `eastus2`, `japaneast`, `southcentralus`, `southeastasia`, `swedencentral`, `uksouth`, `westeurope`, `westus`, `westus3`.
> If the existing project is in a different region, deployment will fail or the application will not work correctly. --- From ab1cccb49d8c8c6d275db10096d1342c864dfc34 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 7 May 2026 13:21:57 +0530 Subject: [PATCH 7/8] Use Field(default_factory=list) for CU response collections Replace Optional[...] = None and mutable [] defaults on Page.spans, Page.words, DocumentContent.paragraphs, and ResultData.warnings with Field(default_factory=list). Removes a possible TypeError when the confidence evaluator iterates page.words and aligns with the repo's Pydantic convention. Tests updated to assert empty-list defaults. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../libs/azure_helper/model/content_understanding.py | 10 +++++----- .../azure_helper/test_content_understanding_model.py | 4 ++-- .../azure_helper/test_content_understanding_model.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py b/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py index 15ef812a..471474e2 100644 --- a/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py +++ b/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py @@ -9,7 +9,7 @@ from typing import List, Optional -from pydantic import BaseModel, ValidationInfo, field_validator +from pydantic import BaseModel, Field, ValidationInfo, field_validator class Span(BaseModel): @@ -103,8 +103,8 @@ class Page(BaseModel): angle: Optional[float] = None width: float height: float - spans: Optional[List[Span]] = None - words: Optional[List[Word]] = None + spans: List[Span] = Field(default_factory=list) + words: List[Word] = Field(default_factory=list) lines: Optional[List[Line]] = [] paragraphs: Optional[List[Paragraph]] = [] @@ -116,7 +116,7 @@ class DocumentContent(BaseModel): endPageNumber: int unit: str pages: List[Page] - paragraphs: Optional[List[Paragraph]] = None + paragraphs: List[Paragraph] = Field(default_factory=list) class Warning(BaseModel): @@ -139,7 +139,7 @@ class ResultData(BaseModel): analyzerId: str apiVersion: str createdAt: str - warnings: List[Warning] = [] + warnings: List[Warning] = Field(default_factory=list) contents: List[DocumentContent] diff --git a/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py b/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py index 11d82d0b..8e042a30 100644 --- a/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py +++ b/src/ContentProcessor/tests/unit/azure_helper/test_content_understanding_model.py @@ -216,7 +216,7 @@ def test_page_can_be_constructed_without_angle_spans_words(self): ) assert page.pageNumber == 1 assert page.angle is None - assert page.spans is None - assert page.words is None + assert page.spans == [] + assert page.words == [] assert page.lines == [] assert page.paragraphs == [] diff --git a/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py b/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py index 11d82d0b..8e042a30 100644 --- a/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py +++ b/src/tests/ContentProcessor/azure_helper/test_content_understanding_model.py @@ -216,7 +216,7 @@ def test_page_can_be_constructed_without_angle_spans_words(self): ) assert page.pageNumber == 1 assert page.angle is None - assert page.spans is None - assert page.words is None + assert page.spans == [] + assert page.words == [] assert page.lines == [] assert page.paragraphs == [] From 8aa9a614abe2f2a3c527286179e9bc89b23c8b87 Mon Sep 17 00:00:00 2001 From: Harsh-Microsoft Date: Thu, 7 May 2026 13:40:54 +0530 Subject: [PATCH 8/8] Use Field(default_factory=list) for Page.lines and Page.paragraphs Pre-existing Optional[List[...]] = [] defaults silently parsed an explicit JSON null to None, which would cause a TypeError when the confidence evaluator iterates page.lines (line 132). Switch to the same Field(default_factory=list) pattern used for the other Page collections so these fields fail loudly at parse time on a malformed response and remain safe to iterate at every call site. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/libs/azure_helper/model/content_understanding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py b/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py index 471474e2..3ca81b71 100644 --- a/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py +++ b/src/ContentProcessor/src/libs/azure_helper/model/content_understanding.py @@ -105,8 +105,8 @@ class Page(BaseModel): height: float spans: List[Span] = Field(default_factory=list) words: List[Word] = Field(default_factory=list) - lines: Optional[List[Line]] = [] - paragraphs: Optional[List[Paragraph]] = [] + lines: List[Line] = Field(default_factory=list) + paragraphs: List[Paragraph] = Field(default_factory=list) class DocumentContent(BaseModel):