diff --git a/README.md b/README.md index 014571548..7ed79d81e 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ either by deleting the resource group in the Portal or running `azd down`. | [Microsoft Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry) | Used to orchestrate and build AI workflows that combine Azure AI services. | Free Tier | [Pricing](https://azure.microsoft.com/pricing/details/ai-studio/) | | [Foundry IQ](https://learn.microsoft.com/en-us/azure/search/search-what-is-azure-search) | Powers vector-based semantic search for retrieving indexed conversation data. | Standard S1; costs scale with document count and replica/partition settings. | [Pricing](https://azure.microsoft.com/pricing/details/search/) | | [Azure Storage Account](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview) | Stores transcripts, intermediate outputs, and application assets. | Standard LRS; usage-based cost by storage/operations. | [Pricing](https://azure.microsoft.com/pricing/details/storage/blobs/) | -| [Azure AI Services (OpenAI)](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/overview) | Enables language understanding, summarization, entity extraction, and chat capabilities using GPT models. | S0 Tier; pricing depends on token volume and model used (e.g., GPT-4o-mini). | [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) | +| [Azure AI Services (OpenAI)](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/overview) | Enables language understanding, summarization, entity extraction, and chat capabilities using GPT models. | S0 Tier; pricing depends on token volume and model used (e.g., GPT-5.2). | [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/) | | [Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/overview) | Hosts the front-end web application and backend API as Linux web apps. | Basic B3 (non-WAF) or Premium P1v3 (WAF); fixed cost based on plan tier. | [Pricing](https://azure.microsoft.com/pricing/details/app-service/linux/) | | [Azure Monitor / Log Analytics](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-overview) | Collects and analyzes telemetry and logs from services and containers. | Pay-as-you-go; charges based on data ingestion volume. | [Pricing](https://azure.microsoft.com/pricing/details/monitor/) | | [Azure SQL Database](https://learn.microsoft.com/en-us/azure/azure-sql/database/sql-database-paas-overview) | Stores structured data including insights, metadata, and indexed results. | General Purpose Tier; can be provisioned or serverless. Fixed cost if provisioned. | [Pricing](https://azure.microsoft.com/pricing/details/azure-sql-database/single/) | diff --git a/azure.yaml b/azure.yaml index 04c409462..1208c9521 100644 --- a/azure.yaml +++ b/azure.yaml @@ -19,6 +19,9 @@ hooks: Write-Host "Web app URL: " Write-Host "$env:WEB_APP_URL" -ForegroundColor Cyan + Write-Host "`nRun the following command in your Bash terminal to build the backend and frontend container images, push them to the provisioned Azure Container Registry, and update the App Services to use them:" + Write-Host "bash ./infra/scripts/build_and_push_images.sh" -ForegroundColor Cyan + Write-Host "`nCreate and activate a virtual environment if not already done, then run the following command in the bash terminal to create agents:" Write-Host "bash ./infra/scripts/run_create_agents_scripts.sh" -ForegroundColor Cyan Write-Host "`nRun the following command in your Bash terminal. It will grant the necessary permissions between resources and your user account, and also process and load the sample data into the application." @@ -31,6 +34,9 @@ hooks: echo "Web app URL: " echo $WEB_APP_URL + echo "\nRun the following command in your Bash terminal to build the backend and frontend container images, push them to the provisioned Azure Container Registry, and update the App Services to use them:" + echo "bash ./infra/scripts/build_and_push_images.sh" + echo "\nCreate and activate a virtual environment if not already done, then run the following command in the bash terminal to create agents:" echo "bash ./infra/scripts/run_create_agents_scripts.sh" echo "" diff --git a/documents/AzureGPTQuotaSettings.md b/documents/AzureGPTQuotaSettings.md index a4134372b..a2cbdf0e6 100644 --- a/documents/AzureGPTQuotaSettings.md +++ b/documents/AzureGPTQuotaSettings.md @@ -5,6 +5,6 @@ 3. **Go to** the `Management Center` from the bottom-left navigation menu. 4. Select `Quota` - Click on the `GlobalStandard` dropdown. - - Select the required **GPT model** (`GPT-4, GPT-4o, GPT-4o Mini`) or **Embeddings model** (`text-embedding-3-small`). + - Select the required **GPT model** (`gpt-5.2`) or **Embeddings model** (`text-embedding-3-small`). - Choose the **region** where the deployment is hosted. 5. Request More Quota or delete any unused model deployments as needed. diff --git a/documents/CustomizingAzdParameters.md b/documents/CustomizingAzdParameters.md index d4b456387..59207e33c 100644 --- a/documents/CustomizingAzdParameters.md +++ b/documents/CustomizingAzdParameters.md @@ -12,21 +12,19 @@ By default this template will use the environment name as the prefix to prevent | `AZURE_LOCATION` | string | ` ` | Sets the Azure region for resource deployment. | | `AZURE_ENV_NAME` | string | `env_name` | Sets the environment name prefix for all Azure resources. | | `AZURE_ENV_AI_SERVICE_LOCATION` | string | `eastus2` | Specifies the Azure AI service location. | -| `AZURE_ENV_SECONDARY_LOCATION` | string | `eastus2` | Specifies a secondary Azure region. | -| `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 `aiServiceLocation` in `infra/main.bicep` (currently `OpenAI.GlobalStandard.gpt-4o-mini,150`). If you set this to `Standard`, also edit that metadata to `OpenAI.Standard.gpt-4o-mini,150` so the picker shows the correct subset of regions, since `gpt-4o-mini` Standard (regional) availability differs from Global Standard. | -| `AZURE_ENV_GPT_MODEL_NAME` | string | `gpt-4o-mini` | Specifies the GPT model name (e.g., `gpt-4o-mini`, `gpt-4.1`, etc.). | -| `AZURE_ENV_GPT_MODEL_VERSION` | string | `2024-07-18` | Sets the Azure model version (e.g., `2024-07-18`, etc.). | +| `AZURE_ENV_SECONDARY_LOCATION` | string | `australiaeast` | Specifies a secondary Azure region. | +| `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 `aiServiceLocation` in `infra/main.bicep` (currently `OpenAI.GlobalStandard.gpt-5.2,150`). If you set this to `Standard`, also edit that metadata to `OpenAI.Standard.gpt-5.2,150` so the picker shows the correct subset of regions, since `gpt-5.2` Standard (regional) availability differs from Global Standard. | +| `AZURE_ENV_GPT_MODEL_NAME` | string | `gpt-5.2` | Specifies the GPT model name (e.g., `gpt-5.2`, `gpt-4.1`, etc.). | +| `AZURE_ENV_GPT_MODEL_VERSION` | string | `2025-12-11` | Sets the Azure model version (e.g., `2025-12-11`, etc.). | | `AZURE_ENV_GPT_MODEL_CAPACITY` | integer | `30` | Sets the GPT model capacity. | | `AZURE_ENV_EMBEDDING_MODEL_NAME` | string | `text-embedding-3-small` | Sets the name of the embedding model to use. | -| `AZURE_ENV_IMAGE_TAG` | string | `latest_afv2` | Sets the image tag (`latest_afv2`, `dev`, `hotfix`, etc.). | +| `AZURE_ENV_IMAGE_TAG` | string | `latest` | Sets the image tag (`latest`, `dev`, `hotfix`, etc.). | | `AZURE_ENV_EMBEDDING_DEPLOYMENT_CAPACITY` | integer | `80` | Sets the capacity for the embedding model deployment. | | `AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID` | string | Guide to get your [Existing Workspace ID](/documents/re-use-log-analytics.md) | Reuses an existing Log Analytics Workspace instead of creating a new one. | -| `USE_LOCAL_BUILD` | string | `false` | Indicates whether to use a local container build for deployment. | | `AZURE_EXISTING_AIPROJECT_RESOURCE_ID` | string | `` | Reuses an existing AIFoundry and AIFoundryProject instead of creating a new one. | | `AZURE_ENV_VM_ADMIN_USERNAME` | string | `take(newGuid(), 20)` | The administrator username for the virtual machine. | | `AZURE_ENV_VM_ADMIN_PASSWORD` | string | `newGuid()` | The administrator password for the virtual machine. | | `AZURE_ENV_VM_SIZE` | string | `Standard_D2s_v5` | The size/SKU of the Jumpbox Virtual Machine (e.g., `Standard_D2s_v5`, `Standard_DS2_v2`). | -| `AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT` | string | `` | The hostname/endpoint for the container registry used by both backend and frontend containers. | ## How to Set a Parameter diff --git a/documents/DeploymentGuide.md b/documents/DeploymentGuide.md index 7bb0a03ef..7baecd380 100644 --- a/documents/DeploymentGuide.md +++ b/documents/DeploymentGuide.md @@ -289,7 +289,7 @@ azd up **During deployment, you'll be prompted for:** 1. **Environment name** (e.g., "ckmapp") - Must be 3-16 characters long, alphanumeric only 2. **Azure subscription** selection -3. **Microsoft Foundry deployment region** - Select a region with available gpt-4o model quota for AI operations +3. **Microsoft Foundry deployment region** - Select a region with available gpt-5.2 model quota for AI operations 4. **Primary location** - Select the region where your infrastructure resources will be deployed 5. **Resource group** selection (create new or use existing) 6. **Use case** selection: @@ -312,9 +312,39 @@ After successful deployment: 4. Click on the App Service to open its overview page 5. Copy the **Default domain** URL (e.g., `app-abc123def.azurewebsites.net`) -⚠️ **Important:** Complete the following steps to process sample data and configure authentication before accessing the application. +⚠️ **Important:** Complete the following steps to build the application container images, process sample data, and configure authentication before accessing the application. -### 4.4 Process Sample Data +### 4.4 Build and Push Container Images + +This solution provisions a dedicated **Azure Container Registry (ACR)** in your resource group. During provisioning, the backend and frontend App Services start with a temporary *hello-world* placeholder image. Run the following script to build the backend (`km-api`) and frontend (`km-app`) images, push them to your ACR, and update the App Services to run them. + +**Run the build and push script:** + +The `azd up` deployment output includes a ready-to-use bash script command. Look for the script in the deployment output and run it: + +```bash +bash ./infra/scripts/build_and_push_images.sh +``` + +The images are **built remotely in ACR** using `az acr build`, so no local Docker installation is required. + +**If you don't have `azd env` configured**, pass the resource group name so the script can resolve values from the deployment outputs: + +```bash +bash ./infra/scripts/build_and_push_images.sh +``` + +**What the script does:** +- Builds the backend image from [src/api/ApiApp.Dockerfile](../src/api/ApiApp.Dockerfile) +- Builds the frontend image from [src/App/WebApp.Dockerfile](../src/App/WebApp.Dockerfile) +- Pushes both images to the provisioned Azure Container Registry +- Updates the backend and frontend App Services to run the new images (pulled via managed identity) and restarts them + +> **Note:** For Production (WAF) deployments, ACR public network access is disabled. The script temporarily enables it to build/push the images and restores the original setting when it finishes. + +**Expected Processing Time:** 5-10 minutes depending on network speed. + +### 4.5 Process Sample Data After the infrastructure deployment completes, follow these steps to process and load the sample data: diff --git a/documents/QuotaCheck.md b/documents/QuotaCheck.md index 5cfa305cc..703b33793 100644 --- a/documents/QuotaCheck.md +++ b/documents/QuotaCheck.md @@ -14,7 +14,7 @@ az login --use-device-code ### 📌 Default Models & Capacities: ``` -gpt-4o:150, gpt-4o-mini:150, gpt-4:150, text-embedding-3-small:80 +gpt-5.2:150, text-embedding-3-small:80 ``` ### 📌 Default Regions: ``` @@ -40,7 +40,7 @@ eastus, eastus2, australiaeast, uksouth, swedencentral, westus, westus3, japanea ``` ✔️ Check specific model(s) in default regions: ``` - ./quota_check_params.sh --models gpt-4o:150,text-embedding-3-small:80 + ./quota_check_params.sh --models gpt-5.2:150,text-embedding-3-small:80 ``` ✔️ Check default models in specific region(s): ``` @@ -48,11 +48,11 @@ eastus, eastus2, australiaeast, uksouth, swedencentral, westus, westus3, japanea ``` ✔️ Passing Both models and regions: ``` - ./quota_check_params.sh --models gpt-4o:150 --regions eastus,westus2 + ./quota_check_params.sh --models gpt-5.2:150 --regions eastus,westus2 ``` ✔️ All parameters combined: ``` - ./quota_check_params.sh --models gpt-4:150,text-embedding-3-small:80 --regions eastus,westus --verbose + ./quota_check_params.sh --models gpt-5.2:150,text-embedding-3-small:80 --regions eastus,westus --verbose ``` ### **Sample Output** diff --git a/documents/re-use-foundry-project.md b/documents/re-use-foundry-project.md index a85fb91a0..e321af570 100644 --- a/documents/re-use-foundry-project.md +++ b/documents/re-use-foundry-project.md @@ -5,7 +5,7 @@ To configure your environment to use an existing Microsoft Foundry Project, foll > **⚠️ Region requirement** > -> The existing Foundry project must reside in a region that supports **both** the GPT model deployed by this accelerator (default `gpt-4o-mini` with `GlobalStandard` deployment type) **and** Azure AI Content Understanding (GA).
+> The existing Foundry project must reside in a region that supports **both** the GPT model deployed by this accelerator (default `gpt-5.2` with `GlobalStandard` deployment type) **and** Azure AI Content Understanding (GA).
> Supported regions: `australiaeast`, `eastus`, `eastus2`, `japaneast`, `southcentralus`, `swedencentral`, `uksouth`, `westeurope`, `westus`, `westus3`.
> If the existing project is in a different region, deployment will fail or the application will not work correctly. diff --git a/infra/main.bicep b/infra/main.bicep index 6915b6bdd..9d45f50c1 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -36,7 +36,7 @@ param location string azd: { type: 'location' usageName: [ - 'OpenAI.GlobalStandard.gpt-4o-mini,150' + 'OpenAI.GlobalStandard.gpt-5.2,150' 'OpenAI.GlobalStandard.text-embedding-3-small,80' ] } @@ -53,8 +53,8 @@ param aiServiceLocation string param usecase string @minLength(1) -@description('Optional. Secondary location for databases creation (example: eastus2).') -param secondaryLocation string = 'eastus2' +@description('Optional. Secondary location for databases creation (example: australiaeast).') +param secondaryLocation string = 'australiaeast' @minLength(1) @description('Optional. GPT model deployment type.') @@ -65,10 +65,10 @@ param secondaryLocation string = 'eastus2' param deploymentType string = 'GlobalStandard' @description('Optional. Name of the GPT model to deploy.') -param gptModelName string = 'gpt-4o-mini' +param gptModelName string = 'gpt-5.2' @description('Optional. Version of the GPT model to deploy.') -param gptModelVersion string = '2024-07-18' +param gptModelVersion string = '2025-12-11' @description('Optional. Version of AI Agent API.') param azureAiAgentApiVersion string = '2025-05-01' @@ -93,23 +93,17 @@ param embeddingModel string = 'text-embedding-3-small' @description('Optional. Capacity of the Embedding Model deployment.') param embeddingDeploymentCapacity int = 80 -@description('Optional. The Container Registry hostname where the docker images for the backend are located.') -param backendContainerRegistryHostname string = 'kmcontainerreg.azurecr.io' - @description('Optional. The Container Image Name to deploy on the backend.') param backendContainerImageName string = 'km-api' @description('Optional. The Container Image Tag to deploy on the backend.') -param backendContainerImageTag string = 'latest_afv2_2026-03-10_1326' - -@description('Optional. The Container Registry hostname where the docker images for the frontend are located.') -param frontendContainerRegistryHostname string = 'kmcontainerreg.azurecr.io' +param backendContainerImageTag string = 'latest' @description('Optional. The Container Image Name to deploy on the frontend.') param frontendContainerImageName string = 'km-app' @description('Optional. The Container Image Tag to deploy on the frontend.') -param frontendContainerImageTag string = 'latest_afv2_2026-03-10_1326' +param frontendContainerImageTag string = 'latest' @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} @@ -163,7 +157,6 @@ var solutionSuffix = toLower(trim(replace( '' ))) -var acrName = 'kmcontainerreg' // Replica regions list based on article in [Azure regions list](https://learn.microsoft.com/azure/reliability/regions-list) and [Enhance resilience by replicating your Log Analytics workspace across regions](https://learn.microsoft.com/azure/azure-monitor/logs/workspace-replication#supported-regions) for supported regions for Log Analytics Workspace. var replicaRegionPairs = { australiaeast: 'australiasoutheast' @@ -579,6 +572,7 @@ var privateDnsZones = [ 'privatelink${environment().suffixes.sqlServerHostname}' 'privatelink.search.windows.net' 'privatelink.azurewebsites.net' + 'privatelink.azurecr.io' ] // DNS Zone Index Constants @@ -594,6 +588,7 @@ var dnsZoneIndex = { sqlServer: 8 search: 9 webApp: 10 + containerRegistry: 11 } // =================================================== @@ -1375,11 +1370,14 @@ module webSiteBackend 'modules/web-sites.bicep' = { managedIdentities: { systemAssigned: true userAssignedResourceIds: [ + userAssignedIdentity.outputs.resourceId backendUserAssignedIdentity.outputs.resourceId ] } siteConfig: { - linuxFxVersion: 'DOCKER|${backendContainerRegistryHostname}/${backendContainerImageName}:${backendContainerImageTag}' + linuxFxVersion: 'DOCKER|mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + acrUseManagedIdentityCreds: true + acrUserManagedIdentityID: userAssignedIdentity.outputs.clientId minTlsVersion: '1.2' } configs: [ @@ -1459,9 +1457,14 @@ module webSiteFrontend 'modules/web-sites.bicep' = { serverFarmResourceId: webServerFarm.outputs.resourceId managedIdentities: { systemAssigned: true + userAssignedResourceIds: [ + userAssignedIdentity.outputs.resourceId + ] } siteConfig: { - linuxFxVersion: 'DOCKER|${frontendContainerRegistryHostname}/${frontendContainerImageName}:${frontendContainerImageTag}' + linuxFxVersion: 'DOCKER|mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + acrUseManagedIdentityCreds: true + acrUserManagedIdentityID: userAssignedIdentity.outputs.clientId minTlsVersion: '1.2' } configs: [ @@ -1483,6 +1486,57 @@ module webSiteFrontend 'modules/web-sites.bicep' = { } } +// ========== Azure Container Registry ========== // +var acrName = 'cr${solutionSuffix}' +var acrPullRoleDefinitionId = '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull +var acrPushRoleDefinitionId = '8311e382-0749-4cb8-b61a-304f252e45ec' // AcrPush +var deployerObjectId = deployer().objectId +var deployerPrincipalType = contains(deployer(), 'userPrincipalName') ? 'User' : 'ServicePrincipal' + +module containerRegistry 'br/public:avm/res/container-registry/registry:0.9.1' = { + name: take('avm.res.container-registry.registry.${acrName}', 64) + params: { + name: acrName + location: location + tags: tags + enableTelemetry: enableTelemetry + acrSku: enablePrivateNetworking ? 'Premium' : 'Standard' + acrAdminUserEnabled: false + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + networkRuleSetDefaultAction: enablePrivateNetworking ? 'Deny' : 'Allow' + exportPolicyStatus: 'enabled' + managedIdentities: { systemAssigned: true } + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + roleDefinitionIdOrName: acrPullRoleDefinitionId + principalType: 'ServicePrincipal' + } + { + principalId: deployerObjectId + roleDefinitionIdOrName: acrPushRoleDefinitionId + principalType: deployerPrincipalType + } + ] + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-${acrName}' + customNetworkInterfaceName: 'nic-${acrName}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.containerRegistry]!.outputs.resourceId } + ] + } + service: 'registry' + subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId + } + ] + : [] + } +} + // ========== Outputs ========== // @description('Contains Solution Name.') output SOLUTION_NAME string = solutionSuffix @@ -1590,7 +1644,25 @@ output AZURE_AI_AGENT_ENDPOINT string = !empty(existingProjEndpoint) ? existingP output AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME string = gptModelName @description('Contains Azure Container Registry name.') -output ACR_NAME string = acrName +output ACR_NAME string = containerRegistry.outputs.name + +@description('Contains Azure Container Registry login server URL.') +output ACR_LOGIN_SERVER string = containerRegistry.outputs.loginServer + +@description('Contains the backend container image name (repository) to build and push to ACR.') +output BACKEND_CONTAINER_IMAGE_NAME string = backendContainerImageName + +@description('Contains the backend container image tag to build and push to ACR.') +output BACKEND_CONTAINER_IMAGE_TAG string = backendContainerImageTag + +@description('Contains the frontend container image name (repository) to build and push to ACR.') +output FRONTEND_CONTAINER_IMAGE_NAME string = frontendContainerImageName + +@description('Contains the frontend container image tag to build and push to ACR.') +output FRONTEND_CONTAINER_IMAGE_TAG string = frontendContainerImageTag + +@description('Contains web (frontend) application name.') +output FRONTEND_APP_NAME string = webSiteResourceName @description('Contains Azure environment image tag.') output AZURE_ENV_IMAGE_TAG string = backendContainerImageTag diff --git a/infra/main.json b/infra/main.json index 79ee03b1e..5efcb66bc 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.44.1.10279", - "templateHash": "5914264828980253617" + "templateHash": "10819681913377495137" } }, "parameters": { @@ -56,7 +56,7 @@ "azd": { "type": "location", "usageName": [ - "OpenAI.GlobalStandard.gpt-4o-mini,150", + "OpenAI.GlobalStandard.gpt-5.2,150", "OpenAI.GlobalStandard.text-embedding-3-small,80" ] }, @@ -76,10 +76,10 @@ }, "secondaryLocation": { "type": "string", - "defaultValue": "eastus2", + "defaultValue": "australiaeast", "minLength": 1, "metadata": { - "description": "Optional. Secondary location for databases creation (example: eastus2)." + "description": "Optional. Secondary location for databases creation (example: australiaeastiaeast)." } }, "deploymentType": { @@ -96,14 +96,14 @@ }, "gptModelName": { "type": "string", - "defaultValue": "gpt-4o-mini", + "defaultValue": "gpt-5.2", "metadata": { "description": "Optional. Name of the GPT model to deploy." } }, "gptModelVersion": { "type": "string", - "defaultValue": "2024-07-18", + "defaultValue": "2025-12-11", "metadata": { "description": "Optional. Version of the GPT model to deploy." } @@ -149,13 +149,6 @@ "description": "Optional. Capacity of the Embedding Model deployment." } }, - "backendContainerRegistryHostname": { - "type": "string", - "defaultValue": "kmcontainerreg.azurecr.io", - "metadata": { - "description": "Optional. The Container Registry hostname where the docker images for the backend are located." - } - }, "backendContainerImageName": { "type": "string", "defaultValue": "km-api", @@ -165,18 +158,11 @@ }, "backendContainerImageTag": { "type": "string", - "defaultValue": "latest_afv2_2026-03-10_1326", + "defaultValue": "latest", "metadata": { "description": "Optional. The Container Image Tag to deploy on the backend." } }, - "frontendContainerRegistryHostname": { - "type": "string", - "defaultValue": "kmcontainerreg.azurecr.io", - "metadata": { - "description": "Optional. The Container Registry hostname where the docker images for the frontend are located." - } - }, "frontendContainerImageName": { "type": "string", "defaultValue": "km-app", @@ -186,7 +172,7 @@ }, "frontendContainerImageTag": { "type": "string", - "defaultValue": "latest_afv2_2026-03-10_1326", + "defaultValue": "latest", "metadata": { "description": "Optional. The Container Image Tag to deploy on the frontend." } @@ -289,7 +275,6 @@ }, "variables": { "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueText')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", - "acrName": "kmcontainerreg", "replicaRegionPairs": { "australiaeast": "australiasoutheast", "centralus": "westus", @@ -338,7 +323,8 @@ "privatelink.documents.azure.com", "[format('privatelink{0}', environment().suffixes.sqlServerHostname)]", "privatelink.search.windows.net", - "privatelink.azurewebsites.net" + "privatelink.azurewebsites.net", + "privatelink.azurecr.io" ], "dnsZoneIndex": { "cognitiveServices": 0, @@ -351,7 +337,8 @@ "cosmosDB": 7, "sqlServer": 8, "search": 9, - "webApp": 10 + "webApp": 10, + "containerRegistry": 11 }, "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]", "backendUserAssignedIdentityResourceName": "[format('id-backend-{0}', variables('solutionSuffix'))]", @@ -399,7 +386,12 @@ "webServerFarmResourceName": "[format('asp-{0}', variables('solutionSuffix'))]", "reactAppLayoutConfig": "{\r\n \"appConfig\": {\r\n \"THREE_COLUMN\": {\r\n \"DASHBOARD\": 50,\r\n \"CHAT\": 33,\r\n \"CHATHISTORY\": 17\r\n },\r\n \"TWO_COLUMN\": {\r\n \"DASHBOARD_CHAT\": {\r\n \"DASHBOARD\": 65,\r\n \"CHAT\": 35\r\n },\r\n \"CHAT_CHATHISTORY\": {\r\n \"CHAT\": 80,\r\n \"CHATHISTORY\": 20\r\n }\r\n }\r\n },\r\n \"charts\": [\r\n {\r\n \"id\": \"SATISFIED\",\r\n \"name\": \"Satisfied\",\r\n \"type\": \"card\",\r\n \"layout\": { \"row\": 1, \"column\": 1, \"height\": 11 }\r\n },\r\n {\r\n \"id\": \"TOTAL_CALLS\",\r\n \"name\": \"Total Calls\",\r\n \"type\": \"card\",\r\n \"layout\": { \"row\": 1, \"column\": 2, \"span\": 1 }\r\n },\r\n {\r\n \"id\": \"AVG_HANDLING_TIME\",\r\n \"name\": \"Average Handling Time\",\r\n \"type\": \"card\",\r\n \"layout\": { \"row\": 1, \"column\": 3, \"span\": 1 }\r\n },\r\n {\r\n \"id\": \"SENTIMENT\",\r\n \"name\": \"Topics Overview\",\r\n \"type\": \"donutchart\",\r\n \"layout\": { \"row\": 2, \"column\": 1, \"width\": 40, \"height\": 44.5 }\r\n },\r\n {\r\n \"id\": \"AVG_HANDLING_TIME_BY_TOPIC\",\r\n \"name\": \"Average Handling Time By Topic\",\r\n \"type\": \"bar\",\r\n \"layout\": { \"row\": 2, \"column\": 2, \"row-span\": 2, \"width\": 60 }\r\n },\r\n {\r\n \"id\": \"TOPICS\",\r\n \"name\": \"Trending Topics\",\r\n \"type\": \"table\",\r\n \"layout\": { \"row\": 3, \"column\": 1, \"span\": 2 }\r\n },\r\n {\r\n \"id\": \"KEY_PHRASES\",\r\n \"name\": \"Key Phrases\",\r\n \"type\": \"wordcloud\",\r\n \"layout\": { \"row\": 3, \"column\": 2, \"height\": 44.5 }\r\n }\r\n ]\r\n}", "backendWebSiteResourceName": "[format('api-{0}', variables('solutionSuffix'))]", - "webSiteResourceName": "[format('app-{0}', variables('solutionSuffix'))]" + "webSiteResourceName": "[format('app-{0}', variables('solutionSuffix'))]", + "acrName": "[format('cr{0}', variables('solutionSuffix'))]", + "acrPullRoleDefinitionId": "7f951dda-4ed3-4680-a7ca-43fe172d538d", + "acrPushRoleDefinitionId": "8311e382-0749-4cb8-b61a-304f252e45ec", + "deployerObjectId": "[deployer().objectId]", + "deployerPrincipalType": "[if(contains(deployer(), 'userPrincipalName'), 'User', 'ServicePrincipal')]" }, "resources": { "existingLogAnalyticsWorkspace": { @@ -29628,9 +29620,7 @@ }, "dependsOn": [ "aiFoundryAiServices", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "avmPrivateDnsZones", "virtualNetwork" ] }, @@ -39944,10 +39934,10 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageDfs)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", "userAssignedIdentity", "virtualNetwork" ] @@ -53700,13 +53690,16 @@ "value": { "systemAssigned": true, "userAssignedResourceIds": [ + "[reference('userAssignedIdentity').outputs.resourceId.value]", "[reference('backendUserAssignedIdentity').outputs.resourceId.value]" ] } }, "siteConfig": { "value": { - "linuxFxVersion": "[format('DOCKER|{0}/{1}:{2}', parameters('backendContainerRegistryHostname'), parameters('backendContainerImageName'), parameters('backendContainerImageTag'))]", + "linuxFxVersion": "DOCKER|mcr.microsoft.com/azuredocs/containerapps-helloworld:latest", + "acrUseManagedIdentityCreds": true, + "acrUserManagedIdentityID": "[reference('userAssignedIdentity').outputs.clientId.value]", "minTlsVersion": "1.2" } }, @@ -55649,6 +55642,7 @@ "cosmosDb", "logAnalyticsWorkspace", "sqlDBModule", + "userAssignedIdentity", "virtualNetwork", "webServerFarm" ] @@ -55680,12 +55674,17 @@ }, "managedIdentities": { "value": { - "systemAssigned": true + "systemAssigned": true, + "userAssignedResourceIds": [ + "[reference('userAssignedIdentity').outputs.resourceId.value]" + ] } }, "siteConfig": { "value": { - "linuxFxVersion": "[format('DOCKER|{0}/{1}:{2}', parameters('frontendContainerRegistryHostname'), parameters('frontendContainerImageName'), parameters('frontendContainerImageTag'))]", + "linuxFxVersion": "DOCKER|mcr.microsoft.com/azuredocs/containerapps-helloworld:latest", + "acrUseManagedIdentityCreds": true, + "acrUserManagedIdentityID": "[reference('userAssignedIdentity').outputs.clientId.value]", "minTlsVersion": "1.2" } }, @@ -56962,8 +56961,3068 @@ } }, "metadata": { - "__bicep_export!": true, - "description": "The type of a private dns zone group." + "__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": [ + "app" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the site." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the site." + }, + "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the site was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('app', '2024-04-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('app', '2024-04-01', 'full').location]" + }, + "defaultHostname": { + "type": "string", + "metadata": { + "description": "Default hostname of the app." + }, + "value": "[reference('app').defaultHostName]" + }, + "customDomainVerificationId": { + "type": "string", + "metadata": { + "description": "Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification." + }, + "value": "[reference('app').customDomainVerificationId]" + }, + "outboundIpAddresses": { + "type": "string", + "metadata": { + "description": "The outbound IP addresses of the app." + }, + "value": "[reference('app').outboundIpAddresses]" + } + } + } + }, + "dependsOn": [ + "applicationInsights", + "logAnalyticsWorkspace", + "userAssignedIdentity", + "virtualNetwork", + "webServerFarm" + ] + }, + "containerRegistry": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.container-registry.registry.{0}', variables('acrName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('acrName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "acrSku": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Premium'), createObject('value', 'Standard'))]", + "acrAdminUserEnabled": { + "value": false + }, + "publicNetworkAccess": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", + "networkRuleSetDefaultAction": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Deny'), createObject('value', 'Allow'))]", + "exportPolicyStatus": { + "value": "enabled" + }, + "managedIdentities": { + "value": { + "systemAssigned": true + } + }, + "roleAssignments": { + "value": [ + { + "principalId": "[reference('userAssignedIdentity').outputs.principalId.value]", + "roleDefinitionIdOrName": "[variables('acrPullRoleDefinitionId')]", + "principalType": "ServicePrincipal" + }, + { + "principalId": "[variables('deployerObjectId')]", + "roleDefinitionIdOrName": "[variables('acrPushRoleDefinitionId')]", + "principalType": "[variables('deployerPrincipalType')]" + } + ] + }, + "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), reference('logAnalyticsWorkspace').outputs.resourceId.value)))), createObject('value', null()))]", + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', variables('acrName')), 'customNetworkInterfaceName', format('nic-{0}', variables('acrName')), 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', createArray(createObject('privateDnsZoneResourceId', reference(format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').containerRegistry)).outputs.resourceId.value))), 'service', 'registry', 'subnetResourceId', reference('virtualNetwork').outputs.pepsSubnetResourceId.value))), createObject('value', createArray()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "12422547988165106386" + }, + "name": "Azure Container Registries (ACR)", + "description": "This module deploys an Azure Container Registry (ACR)." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "scopeMapsType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the scope map." + } + }, + "actions": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of scoped permissions for registry artifacts." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The user friendly description of the scope map." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a scope map." + } + }, + "cacheRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the cache rule. Will be derived from the source repository name if not defined." + } + }, + "sourceRepository": { + "type": "string", + "metadata": { + "description": "Required. Source repository pulled from upstream." + } + }, + "targetRepository": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Target repository specified in docker pull command. E.g.: docker pull myregistry.azurecr.io/{targetRepository}:{tag}." + } + }, + "credentialSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the credential store which is associated with the cache rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cache rule." + } + }, + "credentialSetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the credential set." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityOnlySysAssignedType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "authCredentials": { + "type": "array", + "items": { + "$ref": "#/definitions/authCredentialsType" + }, + "metadata": { + "description": "Required. List of authentication credentials stored for an upstream. Usually consists of a primary and an optional secondary credential." + } + }, + "loginServer": { + "type": "string", + "metadata": { + "description": "Required. The credentials are stored for this upstream or login server." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a credential set." + } + }, + "replicationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the replication." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "regionEndpointEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether the replication regional endpoint is enabled. Requests will not be routed to a replication whose regional endpoint is disabled, however its data will continue to be synced with other replications." + } + }, + "zoneRedundancy": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Whether or not zone redundancy is enabled for this container registry." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a replication." + } + }, + "webhookType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "minLength": 5, + "maxLength": 50, + "metadata": { + "description": "Optional. The name of the registry webhook." + } + }, + "serviceUri": { + "type": "string", + "metadata": { + "description": "Required. The service URI for the webhook to post notifications." + } + }, + "status": { + "type": "string", + "allowedValues": [ + "disabled", + "enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. The status of the webhook at the time the operation was called." + } + }, + "action": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of actions that trigger the webhook to post notifications." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "customHeaders": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Custom headers that will be added to the webhook notifications." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The scope of repositories where the event can be triggered. For example, 'foo:*' means events for all tags under repository 'foo'. 'foo:bar' means events for 'foo:bar' only. 'foo' is equivalent to 'foo:latest'. Empty means all events." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a webhook." + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "authCredentialsType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the credential." + } + }, + "usernameSecretIdentifier": { + "type": "string", + "metadata": { + "description": "Required. KeyVault Secret URI for accessing the username." + } + }, + "passwordSecretIdentifier": { + "type": "string", + "metadata": { + "description": "Required. KeyVault Secret URI for accessing the password." + } + } + }, + "metadata": { + "description": "The type for auth credentials.", + "__bicep_imported_from!": { + "sourceTemplate": "credential-set/main.bicep" + } + } + }, + "customerManagedKeyWithAutoRotateType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using version as per 'autoRotationEnabled' setting." + } + }, + "autoRotationEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable auto-rotating to the latest key version. Default is `true`. If set to `false`, the latest key version at the time of the deployment is used." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type supports auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityAllType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "managedIdentityOnlySysAssignedType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if only system-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "privateEndpointSingleServiceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private Endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the Private Endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the Private Endpoint. This will be used to map to the first-party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the Private Endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the Private Endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags to be applied on all resources/Resource Groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 5, + "maxLength": 50, + "metadata": { + "description": "Required. Name of your Azure Container Registry." + } + }, + "acrAdminUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable admin user that have push / pull permission to the registry." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "acrSku": { + "type": "string", + "defaultValue": "Premium", + "allowedValues": [ + "Basic", + "Premium", + "Standard" + ], + "metadata": { + "description": "Optional. Tier of your Azure container registry." + } + }, + "exportPolicyStatus": { + "type": "string", + "defaultValue": "disabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. The value that indicates whether the export policy is enabled or not." + } + }, + "quarantinePolicyStatus": { + "type": "string", + "defaultValue": "disabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. The value that indicates whether the quarantine policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'." + } + }, + "trustPolicyStatus": { + "type": "string", + "defaultValue": "disabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. The value that indicates whether the trust policy is enabled or not. Note, requires the 'acrSku' to be 'Premium'." + } + }, + "retentionPolicyStatus": { + "type": "string", + "defaultValue": "enabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. The value that indicates whether the retention policy is enabled or not." + } + }, + "retentionPolicyDays": { + "type": "int", + "defaultValue": 15, + "metadata": { + "description": "Optional. The number of days to retain an untagged manifest after which it gets purged." + } + }, + "azureADAuthenticationAsArmPolicyStatus": { + "type": "string", + "defaultValue": "enabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. The value that indicates whether the policy for using ARM audience token for a container registry is enabled or not. Default is enabled." + } + }, + "softDeletePolicyStatus": { + "type": "string", + "defaultValue": "disabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. Soft Delete policy status. Default is disabled." + } + }, + "softDeletePolicyDays": { + "type": "int", + "defaultValue": 7, + "metadata": { + "description": "Optional. The number of days after which a soft-deleted item is permanently deleted." + } + }, + "dataEndpointEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable a single data endpoint per region for serving data. Not relevant in case of disabled public access. Note, requires the 'acrSku' to be 'Premium'." + } + }, + "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 networkRuleSetIpRules are not set. Note, requires the 'acrSku' to be 'Premium'." + } + }, + "networkRuleBypassOptions": { + "type": "string", + "defaultValue": "AzureServices", + "allowedValues": [ + "AzureServices", + "None" + ], + "metadata": { + "description": "Optional. Whether to allow trusted Azure services to access a network restricted registry." + } + }, + "networkRuleSetDefaultAction": { + "type": "string", + "defaultValue": "Deny", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Optional. The default action of allow or deny when no other rules match." + } + }, + "networkRuleSetIpRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The IP ACL rules. Note, requires the 'acrSku' to be 'Premium'." + } + }, + "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. Note, requires the 'acrSku' to be 'Premium'." + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Whether or not zone redundancy is enabled for this container registry." + } + }, + "replications": { + "type": "array", + "items": { + "$ref": "#/definitions/replicationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All replications to create." + } + }, + "webhooks": { + "type": "array", + "items": { + "$ref": "#/definitions/webhookType" + }, + "nullable": true, + "metadata": { + "description": "Optional. All webhooks to create." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "anonymousPullEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables registry-wide pull from unauthenticated clients. It's in preview and available in the Standard and Premium service tiers." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyWithAutoRotateType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "cacheRules": { + "type": "array", + "items": { + "$ref": "#/definitions/cacheRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Cache Rules." + } + }, + "credentialSets": { + "type": "array", + "items": { + "$ref": "#/definitions/credentialSetType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Credential Sets." + } + }, + "scopeMaps": { + "type": "array", + "items": { + "$ref": "#/definitions/scopeMapsType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Scope maps setting." + } + } + }, + "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', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "AcrDelete": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c2f4ef07-c644-48eb-af81-4b1b4947fb11')]", + "AcrImageSigner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6cef56e8-d556-48e5-a04f-b8e64114680f')]", + "AcrPull": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", + "AcrPush": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8311e382-0749-4cb8-b61a-304f252e45ec')]", + "AcrQuarantineReader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'cdda3590-29a3-44f6-95f2-9f980659eb04')]", + "AcrQuarantineWriter": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c8d4ff99-41c3-41a8-9f60-21dfdad59608')]", + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(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.containerregistry-registry.{0}.{1}', replace('0.9.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "subscriptionId": "[split(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": "2023-01-31", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]" + }, + "registry": { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-06-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('acrSku')]" + }, + "properties": { + "anonymousPullEnabled": "[parameters('anonymousPullEnabled')]", + "adminUserEnabled": "[parameters('acrAdminUserEnabled')]", + "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('status', 'enabled', 'keyVaultProperties', createObject('identity', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), ''))), reference('cMKUserAssignedIdentity').clientId, null()), 'keyIdentifier', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), format('{0}/{1}', reference('cMKKeyVault::cMKKey').keyUri, tryGet(parameters('customerManagedKey'), 'keyVersion')), if(coalesce(tryGet(parameters('customerManagedKey'), 'autoRotationEnabled'), true()), reference('cMKKeyVault::cMKKey').keyUri, reference('cMKKeyVault::cMKKey').keyUriWithVersion)))), null())]", + "policies": { + "azureADAuthenticationAsArmPolicy": { + "status": "[parameters('azureADAuthenticationAsArmPolicyStatus')]" + }, + "exportPolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('status', parameters('exportPolicyStatus')), null())]", + "quarantinePolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('status', parameters('quarantinePolicyStatus')), null())]", + "trustPolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('type', 'Notary', 'status', parameters('trustPolicyStatus')), null())]", + "retentionPolicy": "[if(equals(parameters('acrSku'), 'Premium'), createObject('days', parameters('retentionPolicyDays'), 'status', parameters('retentionPolicyStatus')), null())]", + "softDeletePolicy": { + "retentionDays": "[parameters('softDeletePolicyDays')]", + "status": "[parameters('softDeletePolicyStatus')]" + } + }, + "dataEndpointEnabled": "[parameters('dataEndpointEnabled')]", + "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkRuleSetIpRules'))), 'Disabled', null()))]", + "networkRuleBypassOptions": "[parameters('networkRuleBypassOptions')]", + "networkRuleSet": "[if(not(empty(parameters('networkRuleSetIpRules'))), createObject('defaultAction', parameters('networkRuleSetDefaultAction'), 'ipRules', parameters('networkRuleSetIpRules')), null())]", + "zoneRedundancy": "[if(equals(parameters('acrSku'), 'Premium'), parameters('zoneRedundancy'), null())]" + }, + "dependsOn": [ + "cMKKeyVault::cMKKey", + "cMKUserAssignedIdentity" + ] + }, + "registry_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.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "registry" + ] + }, + "registry_diagnosticSettings": { + "copy": { + "name": "registry_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "registry" + ] + }, + "registry_roleAssignments": { + "copy": { + "name": "registry_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.ContainerRegistry/registries', 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": [ + "registry" + ] + }, + "registry_scopeMaps": { + "copy": { + "name": "registry_scopeMaps", + "count": "[length(coalesce(parameters('scopeMaps'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Registry-Scope-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[tryGet(coalesce(parameters('scopeMaps'), createArray())[copyIndex()], 'name')]" + }, + "actions": { + "value": "[coalesce(parameters('scopeMaps'), createArray())[copyIndex()].actions]" + }, + "description": { + "value": "[tryGet(coalesce(parameters('scopeMaps'), createArray())[copyIndex()], 'description')]" + }, + "registryName": { + "value": "[parameters('name')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "11112300500664950599" + }, + "name": "Container Registries scopeMaps", + "description": "This module deploys an Azure Container Registry (ACR) scopeMap." + }, + "parameters": { + "registryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent registry. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('{0}-scopemaps', parameters('registryName'))]", + "metadata": { + "description": "Optional. The name of the scope map." + } + }, + "actions": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of scoped permissions for registry artifacts." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The user friendly description of the scope map." + } + } + }, + "resources": { + "registry": { + "existing": true, + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-06-01-preview", + "name": "[parameters('registryName')]" + }, + "scopeMap": { + "type": "Microsoft.ContainerRegistry/registries/scopeMaps", + "apiVersion": "2023-06-01-preview", + "name": "[format('{0}/{1}', parameters('registryName'), parameters('name'))]", + "properties": { + "actions": "[parameters('actions')]", + "description": "[parameters('description')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the scope map." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the scope map was created in." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the scope map." + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries/scopeMaps', parameters('registryName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "registry" + ] + }, + "registry_replications": { + "copy": { + "name": "registry_replications", + "count": "[length(coalesce(parameters('replications'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Registry-Replication-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('replications'), createArray())[copyIndex()].name]" + }, + "registryName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[coalesce(parameters('replications'), createArray())[copyIndex()].location]" + }, + "regionEndpointEnabled": { + "value": "[tryGet(coalesce(parameters('replications'), createArray())[copyIndex()], 'regionEndpointEnabled')]" + }, + "zoneRedundancy": { + "value": "[tryGet(coalesce(parameters('replications'), createArray())[copyIndex()], 'zoneRedundancy')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('replications'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "6036875058945996178" + }, + "name": "Azure Container Registry (ACR) Replications", + "description": "This module deploys an Azure Container Registry (ACR) Replication." + }, + "parameters": { + "registryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent registry. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the replication." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "regionEndpointEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Specifies whether the replication regional endpoint is enabled. Requests will not be routed to a replication whose regional endpoint is disabled, however its data will continue to be synced with other replications." + } + }, + "zoneRedundancy": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Whether or not zone redundancy is enabled for this container registry." + } + } + }, + "resources": { + "registry": { + "existing": true, + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-06-01-preview", + "name": "[parameters('registryName')]" + }, + "replication": { + "type": "Microsoft.ContainerRegistry/registries/replications", + "apiVersion": "2023-06-01-preview", + "name": "[format('{0}/{1}', parameters('registryName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "regionEndpointEnabled": "[parameters('regionEndpointEnabled')]", + "zoneRedundancy": "[parameters('zoneRedundancy')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the replication." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the replication." + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries/replications', parameters('registryName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the replication was created in." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('replication', '2023-06-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "registry" + ] + }, + "registry_credentialSets": { + "copy": { + "name": "registry_credentialSets", + "count": "[length(coalesce(parameters('credentialSets'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Registry-CredentialSet-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('credentialSets'), createArray())[copyIndex()].name]" + }, + "registryName": { + "value": "[parameters('name')]" + }, + "managedIdentities": { + "value": "[coalesce(parameters('credentialSets'), createArray())[copyIndex()].managedIdentities]" + }, + "authCredentials": { + "value": "[coalesce(parameters('credentialSets'), createArray())[copyIndex()].authCredentials]" + }, + "loginServer": { + "value": "[coalesce(parameters('credentialSets'), createArray())[copyIndex()].loginServer]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "15848218260506856293" + }, + "name": "Container Registries Credential Sets", + "description": "This module deploys an ACR Credential Set." + }, + "definitions": { + "authCredentialsType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the credential." + } + }, + "usernameSecretIdentifier": { + "type": "string", + "metadata": { + "description": "Required. KeyVault Secret URI for accessing the username." + } + }, + "passwordSecretIdentifier": { + "type": "string", + "metadata": { + "description": "Required. KeyVault Secret URI for accessing the password." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for auth credentials." + } + }, + "managedIdentityOnlySysAssignedType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if only system-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "registryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent registry. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the credential set." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityOnlySysAssignedType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "authCredentials": { + "type": "array", + "items": { + "$ref": "#/definitions/authCredentialsType" + }, + "metadata": { + "description": "Required. List of authentication credentials stored for an upstream. Usually consists of a primary and an optional secondary credential." + } + }, + "loginServer": { + "type": "string", + "metadata": { + "description": "Required. The credentials are stored for this upstream or login server." + } + } + }, + "variables": { + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), 'SystemAssigned', null())), null())]" + }, + "resources": { + "registry": { + "existing": true, + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-06-01-preview", + "name": "[parameters('registryName')]" + }, + "credentialSet": { + "type": "Microsoft.ContainerRegistry/registries/credentialSets", + "apiVersion": "2023-11-01-preview", + "name": "[format('{0}/{1}', parameters('registryName'), parameters('name'))]", + "identity": "[variables('identity')]", + "properties": { + "authCredentials": "[parameters('authCredentials')]", + "loginServer": "[parameters('loginServer')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The Name of the Credential Set." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Credential Set." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Credential Set." + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries/credentialSets', parameters('registryName'), parameters('name'))]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('credentialSet', '2023-11-01-preview', 'full'), 'identity'), 'principalId')]" + } + } + } + }, + "dependsOn": [ + "registry" + ] + }, + "registry_cacheRules": { + "copy": { + "name": "registry_cacheRules", + "count": "[length(coalesce(parameters('cacheRules'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Registry-Cache-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "registryName": { + "value": "[parameters('name')]" + }, + "sourceRepository": { + "value": "[coalesce(parameters('cacheRules'), createArray())[copyIndex()].sourceRepository]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('cacheRules'), createArray())[copyIndex()], 'name')]" + }, + "targetRepository": { + "value": "[coalesce(tryGet(coalesce(parameters('cacheRules'), createArray())[copyIndex()], 'targetRepository'), coalesce(parameters('cacheRules'), createArray())[copyIndex()].sourceRepository)]" + }, + "credentialSetResourceId": { + "value": "[tryGet(coalesce(parameters('cacheRules'), createArray())[copyIndex()], 'credentialSetResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "3783697279882479947" + }, + "name": "Container Registries Cache", + "description": "Cache for Azure Container Registry (Preview) feature allows users to cache container images in a private container registry. Cache for ACR, is a preview feature available in Basic, Standard, and Premium service tiers ([ref](https://learn.microsoft.com/en-us/azure/container-registry/tutorial-registry-cache))." + }, + "parameters": { + "registryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent registry. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[replace(replace(replace(parameters('sourceRepository'), '/', '-'), '.', '-'), '*', '')]", + "metadata": { + "description": "Optional. The name of the cache rule. Will be derived from the source repository name if not defined." + } + }, + "sourceRepository": { + "type": "string", + "metadata": { + "description": "Required. Source repository pulled from upstream." + } + }, + "targetRepository": { + "type": "string", + "defaultValue": "[parameters('sourceRepository')]", + "metadata": { + "description": "Optional. Target repository specified in docker pull command. E.g.: docker pull myregistry.azurecr.io/{targetRepository}:{tag}." + } + }, + "credentialSetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the credential store which is associated with the cache rule." + } + } + }, + "resources": { + "registry": { + "existing": true, + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-06-01-preview", + "name": "[parameters('registryName')]" + }, + "cacheRule": { + "type": "Microsoft.ContainerRegistry/registries/cacheRules", + "apiVersion": "2023-06-01-preview", + "name": "[format('{0}/{1}', parameters('registryName'), parameters('name'))]", + "properties": { + "sourceRepository": "[parameters('sourceRepository')]", + "targetRepository": "[parameters('targetRepository')]", + "credentialSetResourceId": "[parameters('credentialSetResourceId')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The Name of the Cache Rule." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Cache Rule." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Cache Rule." + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries/cacheRules', parameters('registryName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "registry", + "registry_credentialSets" + ] + }, + "registry_webhooks": { + "copy": { + "name": "registry_webhooks", + "count": "[length(coalesce(parameters('webhooks'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-Registry-Webhook-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('webhooks'), createArray())[copyIndex()].name]" + }, + "registryName": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('webhooks'), createArray())[copyIndex()], 'location'), parameters('location'))]" + }, + "action": { + "value": "[tryGet(coalesce(parameters('webhooks'), createArray())[copyIndex()], 'action')]" + }, + "customHeaders": { + "value": "[tryGet(coalesce(parameters('webhooks'), createArray())[copyIndex()], 'customHeaders')]" + }, + "scope": { + "value": "[tryGet(coalesce(parameters('webhooks'), createArray())[copyIndex()], 'scope')]" + }, + "status": { + "value": "[tryGet(coalesce(parameters('webhooks'), createArray())[copyIndex()], 'status')]" + }, + "serviceUri": { + "value": "[coalesce(parameters('webhooks'), createArray())[copyIndex()].serviceUri]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('webhooks'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.93.31351", + "templateHash": "10084997815751263562" + }, + "name": "Azure Container Registry (ACR) Webhooks", + "description": "This module deploys an Azure Container Registry (ACR) Webhook." + }, + "parameters": { + "registryName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent registry. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('{0}webhook', parameters('registryName'))]", + "minLength": 5, + "maxLength": 50, + "metadata": { + "description": "Optional. The name of the registry webhook." + } + }, + "serviceUri": { + "type": "string", + "metadata": { + "description": "Required. The service URI for the webhook to post notifications." + } + }, + "status": { + "type": "string", + "defaultValue": "enabled", + "allowedValues": [ + "disabled", + "enabled" + ], + "metadata": { + "description": "Optional. The status of the webhook at the time the operation was called." + } + }, + "action": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [ + "chart_delete", + "chart_push", + "delete", + "push", + "quarantine" + ], + "metadata": { + "description": "Optional. The list of actions that trigger the webhook to post notifications." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "customHeaders": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Custom headers that will be added to the webhook notifications." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The scope of repositories where the event can be triggered. For example, 'foo:*' means events for all tags under repository 'foo'. 'foo:bar' means events for 'foo:bar' only. 'foo' is equivalent to 'foo:latest'. Empty means all events." + } + } + }, + "resources": { + "registry": { + "existing": true, + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-06-01-preview", + "name": "[parameters('registryName')]" + }, + "webhook": { + "type": "Microsoft.ContainerRegistry/registries/webhooks", + "apiVersion": "2023-06-01-preview", + "name": "[format('{0}/{1}', parameters('registryName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "actions": "[parameters('action')]", + "customHeaders": "[parameters('customHeaders')]", + "scope": "[parameters('scope')]", + "serviceUri": "[parameters('serviceUri')]", + "status": "[parameters('status')]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the webhook." + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries/webhooks', parameters('registryName'), parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the webhook." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the Azure container registry." + }, + "value": "[resourceGroup().name]" + }, + "actions": { + "type": "array", + "metadata": { + "description": "The actions of the webhook." + }, + "value": "[reference('webhook').actions]" + }, + "status": { + "type": "string", + "metadata": { + "description": "The status of the webhook." + }, + "value": "[reference('webhook').status]" + }, + "provistioningState": { + "type": "string", + "metadata": { + "description": "The provisioning state of the webhook." + }, + "value": "[reference('webhook').provisioningState]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('webhook', '2023-06-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "registry" + ] + }, + "registry_privateEndpoints": { + "copy": { + "name": "registry_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-registry-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.ContainerRegistry/registries', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry'), 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.ContainerRegistry/registries', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry')))))), 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.ContainerRegistry/registries', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'registry')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.13.18514", + "templateHash": "15954548978129725136" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "ipConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "privateLinkServiceConnectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the private link service connection." + } + }, + "properties": { + "type": "object", + "properties": { + "groupIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." + } + }, + "privateLinkServiceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of private link service." + } + }, + "requestMessage": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." + } + } + }, + "metadata": { + "description": "Required. Properties of private link service connection." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "customDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true } }, "lockType": { @@ -56987,19 +60046,12 @@ "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" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" } } }, @@ -57021,7 +60073,6 @@ } }, "metadata": { - "description": "The type of a private DNS zone group configuration.", "__bicep_imported_from!": { "sourceTemplate": "private-dns-zone-group/main.bicep" } @@ -57098,7 +60149,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" } } } @@ -57135,13 +60186,13 @@ }, "ipConfigurations": { "type": "array", + "items": { + "$ref": "#/definitions/ipConfigurationType" + }, + "nullable": true, "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", @@ -57176,43 +60227,40 @@ }, "tags": { "type": "object", + "nullable": true, "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", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, + "nullable": true, "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", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, "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", + "items": { + "$ref": "#/definitions/privateLinkServiceConnectionType" + }, + "nullable": true, "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", @@ -57247,8 +60295,8 @@ "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))]", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.10.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -57266,7 +60314,7 @@ }, "privateEndpoint": { "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-10-01", + "apiVersion": "2023-11-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -57298,7 +60346,7 @@ "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.'))]" + "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" @@ -57329,7 +60377,7 @@ "privateEndpoint_privateDnsZoneGroup": { "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", "properties": { "expressionEvaluationOptions": { @@ -57354,8 +60402,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.38.5.1644", - "templateHash": "24141742673128945" + "version": "0.33.13.18514", + "templateHash": "5440815542537978381" }, "name": "Private Endpoint Private DNS Zone Groups", "description": "This module deploys a Private Endpoint Private DNS Zone Group." @@ -57379,8 +60427,7 @@ } }, "metadata": { - "__bicep_export!": true, - "description": "The type of a private DNS zone group configuration." + "__bicep_export!": true } } }, @@ -57410,30 +60457,33 @@ } } }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigsVar", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" + } + } + } + ] + }, "resources": { "privateEndpoint": { "existing": true, "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-10-01", + "apiVersion": "2023-11-01", "name": "[parameters('privateEndpointName')]" }, "privateDnsZoneGroup": { "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-10-01", + "apiVersion": "2023-11-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]" - } - } - } - ] + "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" } } }, @@ -57494,15 +60544,14 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" + "value": "[reference('privateEndpoint', '2023-11-01', 'full').location]" }, "customDnsConfigs": { "type": "array", + "items": { + "$ref": "#/definitions/customDnsConfigType" + }, "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]" @@ -57529,7 +60578,8 @@ } }, "dependsOn": [ - "app" + "registry", + "registry_replications" ] } }, @@ -57537,68 +60587,93 @@ "name": { "type": "string", "metadata": { - "description": "The name of the site." + "description": "The Name of the Azure container registry." }, "value": "[parameters('name')]" }, - "resourceId": { + "loginServer": { "type": "string", "metadata": { - "description": "The resource ID of the site." + "description": "The reference to the Azure container registry." }, - "value": "[resourceId('Microsoft.Web/sites', parameters('name'))]" + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('name')), '2019-05-01').loginServer]" }, "resourceGroupName": { "type": "string", "metadata": { - "description": "The resource group the site was deployed into." + "description": "The name of the Azure container registry." }, "value": "[resourceGroup().name]" }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Azure container registry." + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + }, "systemAssignedMIPrincipalId": { "type": "string", "nullable": true, "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[tryGet(tryGet(reference('app', '2024-04-01', 'full'), 'identity'), 'principalId')]" + "value": "[tryGet(tryGet(reference('registry', '2023-06-01-preview', 'full'), 'identity'), 'principalId')]" }, "location": { "type": "string", "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('app', '2024-04-01', 'full').location]" + "value": "[reference('registry', '2023-06-01-preview', 'full').location]" }, - "defaultHostname": { - "type": "string", + "credentialSetsSystemAssignedMIPrincipalIds": { + "type": "array", "metadata": { - "description": "Default hostname of the app." + "description": "The Principal IDs of the ACR Credential Sets system-assigned identities." }, - "value": "[reference('app').defaultHostName]" + "copy": { + "count": "[length(range(0, length(coalesce(parameters('credentialSets'), createArray()))))]", + "input": "[tryGet(tryGet(reference(format('registry_credentialSets[{0}]', range(0, length(coalesce(parameters('credentialSets'), createArray())))[copyIndex()])).outputs, 'systemAssignedMIPrincipalId'), 'value')]" + } }, - "customDomainVerificationId": { - "type": "string", + "credentialSetsResourceIds": { + "type": "array", "metadata": { - "description": "Unique identifier that verifies the custom domains assigned to the app. Customer will add this ID to a txt record for verification." + "description": "The Resource IDs of the ACR Credential Sets." }, - "value": "[reference('app').customDomainVerificationId]" + "copy": { + "count": "[length(range(0, length(coalesce(parameters('credentialSets'), createArray()))))]", + "input": "[reference(format('registry_credentialSets[{0}]', range(0, length(coalesce(parameters('credentialSets'), createArray())))[copyIndex()])).outputs.resourceId.value]" + } }, - "outboundIpAddresses": { - "type": "string", + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, "metadata": { - "description": "The outbound IP addresses of the app." + "description": "The private endpoints of the Azure container registry." }, - "value": "[reference('app').outboundIpAddresses]" + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('registry_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('registry_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('registry_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('registry_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('registry_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } } } } }, "dependsOn": [ - "applicationInsights", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').containerRegistry)]", "logAnalyticsWorkspace", - "virtualNetwork", - "webServerFarm" + "userAssignedIdentity", + "virtualNetwork" ] } }, @@ -57846,7 +60921,49 @@ "metadata": { "description": "Contains Azure Container Registry name." }, - "value": "[variables('acrName')]" + "value": "[reference('containerRegistry').outputs.name.value]" + }, + "ACR_LOGIN_SERVER": { + "type": "string", + "metadata": { + "description": "Contains Azure Container Registry login server URL." + }, + "value": "[reference('containerRegistry').outputs.loginServer.value]" + }, + "BACKEND_CONTAINER_IMAGE_NAME": { + "type": "string", + "metadata": { + "description": "Contains the backend container image name (repository) to build and push to ACR." + }, + "value": "[parameters('backendContainerImageName')]" + }, + "BACKEND_CONTAINER_IMAGE_TAG": { + "type": "string", + "metadata": { + "description": "Contains the backend container image tag to build and push to ACR." + }, + "value": "[parameters('backendContainerImageTag')]" + }, + "FRONTEND_CONTAINER_IMAGE_NAME": { + "type": "string", + "metadata": { + "description": "Contains the frontend container image name (repository) to build and push to ACR." + }, + "value": "[parameters('frontendContainerImageName')]" + }, + "FRONTEND_CONTAINER_IMAGE_TAG": { + "type": "string", + "metadata": { + "description": "Contains the frontend container image tag to build and push to ACR." + }, + "value": "[parameters('frontendContainerImageTag')]" + }, + "FRONTEND_APP_NAME": { + "type": "string", + "metadata": { + "description": "Contains web (frontend) application name." + }, + "value": "[variables('webSiteResourceName')]" }, "AZURE_ENV_IMAGE_TAG": { "type": "string", diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 3ed53c369..2fde1c4d5 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -33,10 +33,10 @@ "value": "${AZURE_ENV_EMBEDDING_DEPLOYMENT_CAPACITY}" }, "backendContainerImageTag": { - "value": "${AZURE_ENV_IMAGE_TAG=latest_afv2}" + "value": "${AZURE_ENV_IMAGE_TAG=latest}" }, "frontendContainerImageTag": { - "value": "${AZURE_ENV_IMAGE_TAG=latest_afv2}" + "value": "${AZURE_ENV_IMAGE_TAG=latest}" }, "enableTelemetry": { "value": "${AZURE_ENV_ENABLE_TELEMETRY}" @@ -47,12 +47,6 @@ "existingAiFoundryAiProjectResourceId": { "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" }, - "backendContainerRegistryHostname": { - "value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}" - }, - "frontendContainerRegistryHostname": { - "value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}" - }, "usecase":{ "value": "${USE_CASE}" } diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index 574122e12..9481419b0 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -33,10 +33,10 @@ "value": "${AZURE_ENV_EMBEDDING_DEPLOYMENT_CAPACITY}" }, "backendContainerImageTag": { - "value": "${AZURE_ENV_IMAGE_TAG=latest_afv2}" + "value": "${AZURE_ENV_IMAGE_TAG=latest}" }, "frontendContainerImageTag": { - "value": "${AZURE_ENV_IMAGE_TAG=latest_afv2}" + "value": "${AZURE_ENV_IMAGE_TAG=latest}" }, "enableTelemetry": { "value": "${AZURE_ENV_ENABLE_TELEMETRY}" @@ -47,12 +47,6 @@ "existingAiFoundryAiProjectResourceId": { "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" }, - "backendContainerRegistryHostname": { - "value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}" - }, - "frontendContainerRegistryHostname": { - "value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}" - }, "enableMonitoring": { "value": true }, diff --git a/infra/scripts/build_and_push_images.sh b/infra/scripts/build_and_push_images.sh new file mode 100644 index 000000000..575c816dc --- /dev/null +++ b/infra/scripts/build_and_push_images.sh @@ -0,0 +1,330 @@ +#!/bin/bash +# ============================================================================= +# build_and_push_images.sh +# +# Post-deployment script that builds the backend (km-api) and frontend (km-app) +# container images remotely in the Azure Container Registry (ACR) that was +# provisioned during `azd up` (using `az acr build`, so no local Docker is +# required). It then updates the backend and frontend App Services to run the +# newly pushed images (pulled via managed identity). +# +# Usage: +# bash ./infra/scripts/build_and_push_images.sh [ResourceGroupName] +# +# When no arguments are provided, values are resolved from the azd environment. +# When a resource group name is provided, values are resolved from the +# deployment outputs of that resource group. +# ============================================================================= + +set -e + +# Get the directory where this script is located and the repository root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# --------------------------------------------------------------------------- +# Parse arguments +# --------------------------------------------------------------------------- +resourceGroupName="" + +while [[ $# -gt 0 ]]; do + case "$1" in + *) + if [ -z "$resourceGroupName" ]; then + resourceGroupName="$1" + fi + shift + ;; + esac +done + +# Backend (km-api) and frontend (km-app) build contexts and Dockerfiles +backendContext="$REPO_ROOT/src/api" +backendDockerfile="$REPO_ROOT/src/api/ApiApp.Dockerfile" +frontendContext="$REPO_ROOT/src/App" +frontendDockerfile="$REPO_ROOT/src/App/WebApp.Dockerfile" + +# Variables resolved below +acrName="" +acrLoginServer="" +backendImageName="" +backendImageTag="" +frontendImageName="" +frontendImageTag="" +backendAppName="" +frontendAppName="" + +# Track original ACR public access state to restore on exit +original_acr_public_access="" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +check_azd_installed() { + command -v azd >/dev/null 2>&1 +} + +get_values_from_azd_env() { + echo "Getting values from azd environment..." + resourceGroupName=$(azd env get-value RESOURCE_GROUP_NAME 2>&1 | grep -E '^[a-zA-Z0-9._-]+$') + acrName=$(azd env get-value ACR_NAME 2>&1 | grep -E '^[a-zA-Z0-9]+$') + acrLoginServer=$(azd env get-value ACR_LOGIN_SERVER 2>&1 | grep -E '^[a-zA-Z0-9._-]+$') + backendImageName=$(azd env get-value BACKEND_CONTAINER_IMAGE_NAME 2>&1 | grep -E '^[a-zA-Z0-9._/-]+$') + backendImageTag=$(azd env get-value BACKEND_CONTAINER_IMAGE_TAG 2>&1 | grep -E '^[a-zA-Z0-9._-]+$') + frontendImageName=$(azd env get-value FRONTEND_CONTAINER_IMAGE_NAME 2>&1 | grep -E '^[a-zA-Z0-9._/-]+$') + frontendImageTag=$(azd env get-value FRONTEND_CONTAINER_IMAGE_TAG 2>&1 | grep -E '^[a-zA-Z0-9._-]+$') + backendAppName=$(azd env get-value API_APP_NAME 2>&1 | grep -E '^[a-zA-Z0-9._-]+$') + frontendAppName=$(azd env get-value FRONTEND_APP_NAME 2>&1 | grep -E '^[a-zA-Z0-9._-]+$') + + if [ -z "$resourceGroupName" ] || [ -z "$acrName" ] || [ -z "$backendAppName" ] || [ -z "$frontendAppName" ]; then + echo "Error: One or more required values could not be retrieved from azd environment." + return 1 + fi + return 0 +} + +get_values_from_az_deployment() { + echo "Getting values from Azure deployment outputs..." + + deploymentName=$(az group show --name "$resourceGroupName" --query "tags.DeploymentName" -o tsv) + echo "Deployment Name (from tag): $deploymentName" + + echo "Fetching deployment outputs..." + deploymentOutputs=$(az deployment group show \ + --name "$deploymentName" \ + --resource-group "$resourceGroupName" \ + --query "properties.outputs" -o json) + + # Helper function to extract value from deployment outputs + # Usage: extract_value "primaryKey" "fallbackKey" + extract_value() { + local primary_key="$1" + local fallback_key="$2" + local value + + value=$(echo "$deploymentOutputs" | grep -i -A 3 "\"$primary_key\"" | grep '"value"' | sed 's/.*"value": *"\([^"]*\)".*/\1/') + + if [ -z "$value" ] && [ -n "$fallback_key" ]; then + value=$(echo "$deploymentOutputs" | grep -i -A 3 "\"$fallback_key\"" | grep '"value"' | sed 's/.*"value": *"\([^"]*\)".*/\1/') + fi + + echo "$value" + } + + # Extract deployment outputs + acrName=$(extract_value "acR_NAME" "ACR_NAME") + acrLoginServer=$(extract_value "acR_LOGIN_SERVER" "ACR_LOGIN_SERVER") + backendImageName=$(extract_value "backenD_CONTAINER_IMAGE_NAME" "BACKEND_CONTAINER_IMAGE_NAME") + backendImageTag=$(extract_value "backenD_CONTAINER_IMAGE_TAG" "BACKEND_CONTAINER_IMAGE_TAG") + frontendImageName=$(extract_value "frontenD_CONTAINER_IMAGE_NAME" "FRONTEND_CONTAINER_IMAGE_NAME") + frontendImageTag=$(extract_value "frontenD_CONTAINER_IMAGE_TAG" "FRONTEND_CONTAINER_IMAGE_TAG") + backendAppName=$(extract_value "apI_APP_NAME" "API_APP_NAME") + frontendAppName=$(extract_value "frontenD_APP_NAME" "FRONTEND_APP_NAME") + + # Define required values with their display names + declare -A required_values=( + ["acrName"]="ACR_NAME" + ["acrLoginServer"]="ACR_LOGIN_SERVER" + ["backendImageName"]="BACKEND_CONTAINER_IMAGE_NAME" + ["backendImageTag"]="BACKEND_CONTAINER_IMAGE_TAG" + ["frontendImageName"]="FRONTEND_CONTAINER_IMAGE_NAME" + ["frontendImageTag"]="FRONTEND_CONTAINER_IMAGE_TAG" + ["backendAppName"]="API_APP_NAME" + ["frontendAppName"]="FRONTEND_APP_NAME" + ) + + # Validate required values + missing_values=() + for var_name in "${!required_values[@]}"; do + if [ -z "${!var_name}" ]; then + missing_values+=("${required_values[$var_name]}") + fi + done + if [ ${#missing_values[@]} -gt 0 ]; then + echo "Error: The following required values could not be retrieved from Azure deployment outputs:" + printf ' - %s\n' "${missing_values[@]}" | sort + return 1 + fi + return 0 +} + +# Enable public network access on ACR temporarily (needed for remote builds on WAF deployments) +enable_acr_public_access() { + original_acr_public_access=$(az acr show \ + --name "$acrName" \ + --resource-group "$resourceGroupName" \ + --query "publicNetworkAccess" -o tsv 2>/dev/null) + + if [ -z "$original_acr_public_access" ]; then + echo "[WARN] Could not retrieve ACR network access status" + fi + + if [ "$original_acr_public_access" != "Enabled" ]; then + echo "[OK] Temporarily enabling ACR public network access" + az acr update \ + --name "$acrName" \ + --resource-group "$resourceGroupName" \ + --public-network-enabled true \ + --default-action Allow \ + --output none + # Wait a bit for the change to take effect + sleep 20 + fi + return 0 +} + +# Restore original ACR public network access state +restore_acr_public_access() { + if [ -n "$original_acr_public_access" ] && [ "$original_acr_public_access" != "Enabled" ]; then + echo "[OK] Restoring ACR public network access to '$original_acr_public_access'" + case "$original_acr_public_access" in + "enabled"|"Enabled") restore_value=true ;; + "disabled"|"Disabled") restore_value=false ;; + *) restore_value="$original_acr_public_access" ;; + esac + az acr update \ + --name "$acrName" \ + --resource-group "$resourceGroupName" \ + --public-network-enabled $restore_value \ + --default-action Deny \ + --output none 2>/dev/null || echo "[WARN] Failed to restore ACR public network access - please check the Azure portal" + fi +} + +cleanup_on_exit() { + exit_code=$? + restore_acr_public_access + echo "" + if [ $exit_code -ne 0 ]; then + echo "[FAILED] Script failed" + else + echo "[SUCCESS] Script completed successfully" + fi + exit $exit_code +} +trap cleanup_on_exit EXIT + +# Build and push a single image +# Args: +build_and_push_image() { + local context="$1" + local dockerfile="$2" + local imageName="$3" + local imageTag="$4" + local imageRef="${imageName}:${imageTag}" + + if [ ! -f "$dockerfile" ]; then + echo "[ERROR] Dockerfile not found: $dockerfile" + return 1 + fi + + echo "Building '$imageRef' remotely in ACR '$acrName'..." + az acr build \ + --registry "$acrName" \ + --image "$imageRef" \ + --file "$dockerfile" \ + --platform linux \ + "$context" +} + +# Update an App Service to run an ACR image using managed identity credentials +# Args: +update_web_app_image() { + local appName="$1" + local imageName="$2" + local imageTag="$3" + local fullImage="${acrLoginServer}/${imageName}:${imageTag}" + + echo "Updating App Service '$appName' to use image '$fullImage'..." + az webapp config container set \ + --name "$appName" \ + --resource-group "$resourceGroupName" \ + --container-image-name "$fullImage" \ + --container-registry-url "https://${acrLoginServer}" \ + --only-show-errors \ + --output none + + # Ensure the app pulls the image using its managed identity (no admin credentials) + az resource update \ + --resource-group "$resourceGroupName" \ + --namespace Microsoft.Web \ + --resource-type sites \ + --name "$appName" \ + --set properties.siteConfig.acrUseManagedIdentityCreds=true \ + --output none 2>/dev/null || true + + echo "Restarting App Service '$appName'..." + az webapp restart --name "$appName" --resource-group "$resourceGroupName" --output none +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +echo "===============================================" +echo "Build & Push Container Images" +echo "===============================================" + +# Check Azure authentication +echo "Checking Azure authentication..." +if az account show &>/dev/null; then + echo "Already authenticated with Azure." +else + echo "Authenticating with Azure CLI..." + if ! az login --use-device-code; then + echo "[ERROR] Failed to authenticate with Azure" + exit 1 + fi +fi + +# Resolve configuration values +if [ -z "$resourceGroupName" ]; then + if ! get_values_from_azd_env; then + echo "" + echo "Failed to get values from azd environment." + echo "Provide a resource group name to resolve values from deployment outputs instead:" + echo " Usage: $0 [ResourceGroupName]" + exit 1 + fi +else + if ! get_values_from_az_deployment; then + echo "Failed to get values from deployment outputs." + exit 1 + fi +fi + +# Fallbacks / defaults +acrLoginServer="${acrLoginServer:-${acrName}.azurecr.io}" +backendImageName="${backendImageName:-km-api}" +backendImageTag="${backendImageTag:-latest}" +frontendImageName="${frontendImageName:-km-app}" +frontendImageTag="${frontendImageTag:-latest}" + +echo "" +echo "===============================================" +echo "Values to be used:" +echo "===============================================" +echo "Resource Group: $resourceGroupName" +echo "ACR Name: $acrName" +echo "ACR Login Server: $acrLoginServer" +echo "Backend Image: $backendImageName:$backendImageTag" +echo "Frontend Image: $frontendImageName:$frontendImageTag" +echo "Backend App: $backendAppName" +echo "Frontend App: $frontendAppName" +echo "===============================================" +echo "" + +# Ensure ACR is reachable for build (temporarily enable public access if needed) +enable_acr_public_access + +# Build & push backend +build_and_push_image "$backendContext" "$backendDockerfile" "$backendImageName" "$backendImageTag" + +# Build & push frontend +build_and_push_image "$frontendContext" "$frontendDockerfile" "$frontendImageName" "$frontendImageTag" + +# Update the web apps to use the freshly pushed images +update_web_app_image "$backendAppName" "$backendImageName" "$backendImageTag" +update_web_app_image "$frontendAppName" "$frontendImageName" "$frontendImageTag" + +echo "" +echo "Images built and pushed, and App Services updated." diff --git a/infra/scripts/checkquota_km.sh b/infra/scripts/checkquota_km.sh index 9ec2ef416..77075987c 100644 --- a/infra/scripts/checkquota_km.sh +++ b/infra/scripts/checkquota_km.sh @@ -42,7 +42,7 @@ fi # Define models and their minimum required capacities declare -A MIN_CAPACITY=( - ["OpenAI.GlobalStandard.gpt-4o-mini"]=$GPT_MIN_CAPACITY #km generic + ["OpenAI.GlobalStandard.gpt-5.2"]=$GPT_MIN_CAPACITY #km generic ["OpenAI.GlobalStandard.text-embedding-3-small"]=$TEXT_EMBEDDING_MIN_CAPACITY #km generic ) diff --git a/infra/scripts/quota_check_params.sh b/infra/scripts/quota_check_params.sh index 26d8a22f9..d5e9fad18 100644 --- a/infra/scripts/quota_check_params.sh +++ b/infra/scripts/quota_check_params.sh @@ -47,7 +47,7 @@ log_verbose() { } # Default Models and Capacities (Comma-separated in "model:capacity" format) -DEFAULT_MODEL_CAPACITY="gpt-4o:150,gpt-4o-mini:150,gpt-4:150,text-embedding-3-small:80" +DEFAULT_MODEL_CAPACITY="gpt-5.2:150,text-embedding-3-small:80" # Convert the comma-separated string into an array IFS=',' read -r -a MODEL_CAPACITY_PAIRS <<< "$DEFAULT_MODEL_CAPACITY" diff --git a/src/App/.dockerignore b/src/App/.dockerignore index 27c81ea86..0cfff2f19 100644 --- a/src/App/.dockerignore +++ b/src/App/.dockerignore @@ -25,7 +25,10 @@ **/compose* **/Dockerfile* **/*.Dockerfile +node_modules +node_modules/** **/node_modules +**/node_modules/** **/npm-debug.log **/obj **/secrets.dev.yaml diff --git a/src/App/WebApp.Dockerfile b/src/App/WebApp.Dockerfile index b9cc974e6..498713649 100644 --- a/src/App/WebApp.Dockerfile +++ b/src/App/WebApp.Dockerfile @@ -1,18 +1,14 @@ -FROM node:24-alpine AS build -WORKDIR /home/node/app - -COPY ./package*.json ./ - +# Stage 1: Build +FROM node:24-alpine AS build +WORKDIR /app +COPY package*.json ./ RUN npm ci --include=dev - -COPY . . - -RUN npm run build - -FROM nginx:alpine - -COPY --from=build /home/node/app/dist /usr/share/nginx/html +COPY . . +RUN npm run build +# Stage 2: Runtime (Nginx only - no Node) +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html COPY env.sh /docker-entrypoint.d/env.sh RUN chmod +x /docker-entrypoint.d/env.sh RUN sed -i 's/\r$//' /docker-entrypoint.d/env.sh diff --git a/src/api/ApiApp.Dockerfile b/src/api/ApiApp.Dockerfile index 4c336bd47..75f7da36d 100644 --- a/src/api/ApiApp.Dockerfile +++ b/src/api/ApiApp.Dockerfile @@ -1,24 +1,12 @@ -FROM python:3.11-alpine - -# Install system dependencies required for building and running the application -RUN apk add --no-cache \ - ca-certificates \ - && apk add --no-cache --virtual .build-deps \ - build-base \ - libffi-dev \ - openssl-dev \ - curl \ - unixodbc-dev \ - libpq \ - opus-dev \ - libvpx-dev - -# Download and install Microsoft ODBC Driver 18 and MSSQL tools (latest release) -RUN curl -O https://download.microsoft.com/download/fae28b9a-d880-42fd-9b98-d779f0fdd77f/msodbcsql18_18.5.1.1-1_amd64.apk \ - && curl -O https://download.microsoft.com/download/7/6/d/76de322a-d860-4894-9945-f0cc5d6a45f8/mssql-tools18_18.4.1.1-1_amd64.apk \ - && apk add --allow-untrusted msodbcsql18_18.5.1.1-1_amd64.apk \ - && apk add --allow-untrusted mssql-tools18_18.4.1.1-1_amd64.apk \ - && rm msodbcsql18_18.5.1.1-1_amd64.apk mssql-tools18_18.4.1.1-1_amd64.apk +FROM python:3.13-slim + +# Install ODBC driver for SQL Server +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl gnupg2 unixodbc-dev && \ + curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg && \ + curl -fsSL https://packages.microsoft.com/config/debian/12/prod.list | tee /etc/apt/sources.list.d/mssql-release.list && \ + apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql18 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* # Set the working directory inside the container WORKDIR /app