diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 8270ff53..8952ed34 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -95,7 +95,7 @@ jobs:
--template-file infra/main.bicep \
--parameters \
Prefix="${{ env.SOLUTION_PREFIX }}" \
- AzureAiServiceLocation="eastus" \
+ aiDeploymentsLocation="eastus" \
capacity=${{ env.GPT_MIN_CAPACITY }} \
imageVersion="${IMAGE_TAG}"\
--debug
diff --git a/README.md b/README.md
index 099ab80c..07ea2dd0 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ The Modernize your code solution accelerator allows users to specify a group of
-
+
[**SOLUTION OVERVIEW**](#solution-overview) \| [**QUICK DEPLOY**](#quick-deploy) \| [**BUSINESS SCENARIO**](#business-scenario) \| [**SUPPORTING DOCUMENTATION**](#supporting-documentation)
@@ -24,7 +24,10 @@ The solution leverages Azure AI Foundry, Azure OpenAI Service, Azure Container A
||
|---|
+This architecture will be deployed with the 'sandbox' setting of our deployment process. Optionally you can deploy [Well-Architected Framework (WAF) aligned](https://learn.microsoft.com/en-us/azure/well-architected/) architecture, described in [WAF-Aligned Solution Architecture](./docs/ArchitectureWAF.md), with the WAF-Aligned deployment option described in [Deployment Guide](./docs/DeploymentGuide.md).
+
### Agentic architecture
+
||
|---|
@@ -51,16 +54,16 @@ If you'd like to customize the solution accelerator, here are some common areas
Click to learn more about the key features this solution enables
- **Code language modernization**
- Modernizing outdated code ensures compatibility with current technologies, reduces reliance on legacy expertise, and keeps businesses competitive.
+ Modernizing outdated code ensures compatibility with current technologies, reduces reliance on legacy expertise, and keeps businesses competitive.
- **Summary and review of new code**
- Generating summaries and translating code files keeps humans in the loop, enhances their understanding, and facilitates timely interventions, ensuring the files are ready to export.
+ Generating summaries and translating code files keeps humans in the loop, enhances their understanding, and facilitates timely interventions, ensuring the files are ready to export.
- **Business logic analysis**
- Leveraging AI to decipher business logic from legacy code helps minimizes the risk of human error.
+ Leveraging AI to decipher business logic from legacy code helps minimizes the risk of human error.
- **Efficient code transformation**
- Streamlining the process of analyzing, converting, and iterative error testing reduces time and effort required to modernize the systems.
+ Streamlining the process of analyzing, converting, and iterative error testing reduces time and effort required to modernize the systems.
@@ -77,7 +80,7 @@ Follow the quick deploy steps on the deployment guide to deploy this solution to
| [](https://codespaces.new/microsoft/Modernize-your-Code-Solution-Accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/Modernize-your-Code-Solution-Accelerator) |
|---|---|
-
+
> ⚠️ **Important: Check Azure OpenAI Quota Availability**
@@ -141,19 +144,19 @@ The sample data used in this repository is synthetic and generated using Azure O
Click to learn more about what value this solution provides
- **Accelerated Migration**
- Automate the translation of SQL queries, significantly reducing migration time and effort.
+ Automate the translation of SQL queries, significantly reducing migration time and effort.
- **Error Reduction**
- Multi-agent validation ensures accurate translations and maintains data integrity.
+ Multi-agent validation ensures accurate translations and maintains data integrity.
- **Knowledge Preservation**
- Captures and preserves business logic during the modernization process.
+ Captures and preserves business logic during the modernization process.
- **Cost Efficiency**
- Reduces reliance on specialized legacy system expertise and manual translation efforts.
+ Reduces reliance on specialized legacy system expertise and manual translation efforts.
- **Standardization**
- Ensures consistent query translation across the organization.
+ Ensures consistent query translation across the organization.
diff --git a/azure.yaml b/azure.yaml
index 11715364..f3a12172 100644
--- a/azure.yaml
+++ b/azure.yaml
@@ -1,6 +1,3 @@
-environment:
- name: modernize-your-code-solution-accelerator
- location: eastus
name: modernize-your-code-solution-accelerator
metadata:
template: modernize-your-code-solution-accelerator@1.0
@@ -21,19 +18,3 @@ deployment:
AzureAiServiceLocation: ${{ parameters.AzureAiServiceLocation }}
Prefix: ${{ parameters.Prefix }}
baseUrl: ${{ parameters.baseUrl }}
-hooks:
- preprovision:
- posix:
- shell: sh
- run: >
- chmod u+r+x ./scripts/validate_model_deployment_quota.sh; chmod u+r+x ./scripts/validate_model_quota.sh; ./scripts/validate_model_deployment_quota.sh --SubscriptionId "$AZURE_SUBSCRIPTION_ID" --Location "${AZURE_AISERVICE_LOCATION:-japaneast}" --ModelsParameter "aiModelDeployments"
- interactive: false
- continueOnError: false
-
- windows:
- shell: pwsh
- run: >
- $location = if ($env:AZURE_AISERVICE_LOCATION) { $env:AZURE_AISERVICE_LOCATION } else { "japaneast" };
- ./scripts/validate_model_deployment_quota.ps1 -SubscriptionId $env:AZURE_SUBSCRIPTION_ID -Location $location -ModelsParameter "aiModelDeployments"
- interactive: false
- continueOnError: false
\ No newline at end of file
diff --git a/docs/ArchitectureWAF.md b/docs/ArchitectureWAF.md
new file mode 100644
index 00000000..ca5e8a10
--- /dev/null
+++ b/docs/ArchitectureWAF.md
@@ -0,0 +1,59 @@
+# Azure WAF-Aligned Architecture
+
+This architecture implements [Azure Well-Architected Framework (WAF)](https://learn.microsoft.com/en-us/azure/well-architected/) principles for enterprise-grade deployments, deployed with the WAF-Aligned deployment option:
+
+
+
+## WAF Pillars Implementation
+
+### Security
+- **Zero Trust Network:** Private VNet with private endpoints for all PaaS services
+- **Identity & Access:** Managed identities with RBAC and least-privilege access
+- **Secure Admin Access:** Azure Bastion + Jumpbox for internal administration
+- **Secrets Management:** Azure Key Vault integration
+
+### Operational Excellence
+- **Observability:** Centralized logging via Log Analytics Workspace
+- **Application Monitoring:** Application Insights for telemetry and diagnostics
+- **Infrastructure as Code:** Bicep templates with parameterized configurations
+
+### Performance Efficiency
+- **Auto-scaling:** Container Apps with configurable scaling policies
+- **Regional Proximity:** Resources deployed in optimal Azure regions
+
+### Cost Optimization
+- **Right-sizing:** Parameterized SKUs and capacity settings
+- **Resource Sharing:** Shared networking and monitoring infrastructure
+
+### Reliability
+- **High Availability:** Multi-zone deployment options
+- **Data Redundancy:** Configurable geo-replication for critical data stores
+- **Private Connectivity:** Eliminates internet dependencies
+
+## Core Architecture Components
+
+| Component | Purpose | WAF Alignment |
+|-----------|---------|---------------|
+| **Virtual Network** | Network isolation boundary | Security, Reliability |
+| **Private Endpoints** | Secure PaaS connectivity (AI Services, Storage, Cosmos DB, Key Vault) | Security |
+| **Private DNS Zones** | Internal name resolution | Security, Reliability |
+| **Azure Bastion + Jumpbox** | Secure administrative access | Security |
+| **Container Apps** | Application hosting with VNet integration | Performance, Reliability |
+| **Log Analytics + App Insights** | Centralized monitoring and diagnostics | Operational Excellence |
+
+## Deployment Configuration
+- **Configurable Parameters:** If user selects to deploy as WAF Aligned, Parameters like Monitoring, Scaling, VPN will get enabled.
+- **Network-first Design:** All components deployed within private network boundaries
+- **Enterprise-ready:** Production-grade security and monitoring enabled
+
+## Application Information Flow
+
+The application information flow remains the same for both 'sandbox' and 'waf-aligned' configuration.
+
+The solution is composed of several services:
+
+- The web app front end and the backend app logic are containerized and run from Azure Container service instances.
+- When a request for conversion is created in the web app admin console, the user specifies what files should be converted and the target SQL dialect for conversion.
+- These files are then uploaded to blob storage and initial data about the request is stored in Cosmos DB.
+- The conversion takes place using appropriate LLM models using multiple agents, with each agent having a dedicated purpose in the conversion process. As files are converted, they are placed into blob storage, with metadata collected into Cosmos detailing the conversion process and the current state of the batch.
+- Cosmos also stores the logs from the individual agents so the results can be fully reviewed before any of the converted files are put into production.
diff --git a/docs/CmsaArchitectureSource.pptx b/docs/CmsaArchitectureSource.pptx
new file mode 100644
index 00000000..92707ff7
Binary files /dev/null and b/docs/CmsaArchitectureSource.pptx differ
diff --git a/docs/DeploymentGuide.md b/docs/DeploymentGuide.md
index 472fa065..88c28c06 100644
--- a/docs/DeploymentGuide.md
+++ b/docs/DeploymentGuide.md
@@ -31,7 +31,7 @@ Check the [Azure Products by Region](https://azure.microsoft.com/en-us/explore/g
| [](https://codespaces.new/microsoft/Modernize-your-Code-Solution-Accelerator) | [](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/Modernize-your-Code-Solution-Accelerator) |
|---|---|
-
+
### **Configurable Deployment Settings**
When you start the deployment, most parameters will have **default values**, but you can update the following settings by following the steps [here](../docs/CustomizingAzdParameters.md):
@@ -61,7 +61,33 @@ By default, the **GPT model capacity** in deployment is set to **5k tokens**.
To adjust quota settings, follow these [steps](../docs/AzureGPTQuotaSettings.md)
-### Deployment Options
+### Deployment Options & Steps
+### Sandbox or WAF Aligned Deployment Options
+
+The [`infra`](../infra) folder contains the [`main.bicep`](../infra/main.bicep) Bicep script, which defines all Azure infrastructure components for this solution.
+
+When running `azd up`, you’ll now be prompted to choose between a **WAF-aligned configuration** and a **sandbox configuration** using a simple selection:
+
+- A **sandbox environment** — ideal for development and proof-of-concept scenarios, with minimal security and cost controls for rapid iteration.
+
+- A **production deployments environment**, which applies a [Well-Architected Framework (WAF) aligned](https://learn.microsoft.com/en-us/azure/well-architected/) configuration. This option enables additional Azure best practices for reliability, security, cost optimization, operational excellence, and performance efficiency, such as:
+ - Enhanced network security (e.g., Network protection with private endpoints)
+ - Stricter access controls and managed identities
+ - Logging, monitoring, and diagnostics enabled by default
+ - Resource tagging and cost management recommendations
+
+**How to choose your deployment configuration:**
+
+When prompted during `azd up`:
+
+
+
+- Select **`true`** to deploy a **WAF-aligned, production-ready environment**
+- Select **`false`** to deploy a **lightweight sandbox/dev environment**
+-
+> [!TIP]
+> Always review and adjust parameter values (such as region, capacity, security settings and log analytics workspace configuration) to match your organization’s requirements before deploying. For production, ensure you have sufficient quota and follow the principle of least privilege for all identities and role assignments.
+
Pick from the options below to see step-by-step instructions for: GitHub Codespaces, VS Code Dev Containers, Local Environments, and Bicep deployments.
@@ -133,23 +159,28 @@ To change the azd parameters from the default values, follow the steps [here](..
1. Login to Azure:
- ```shell
- azd auth login
- ```
+ ```shell
+ azd auth login
+ ```
+
+ #### Note: To authenticate with Azure Developer CLI (`azd`) to a specific tenant, use the previous command with your **Tenant ID**:
+
+ ```sh
+ azd auth login --tenant-id
+ ```
- #### Note: To authenticate with Azure Developer CLI (`azd`) to a specific tenant, use the previous command with your **Tenant ID**:
+2. Provide an `azd` environment name (like "cmsaapp")
- ```sh
- azd auth login --tenant-id
+ ```sh
+ azd env new
```
-2. Provision and deploy all the resources:
+3. Provision and deploy all the resources:
```shell
azd up
```
-3. Provide an `azd` environment name (like "cmsaapp")
4. Select a subscription from your Azure account, and select a location which has quota for all the resources.
* This deployment will take *6-9 minutes* to provision the resources in your account and set up the solution with sample data.
* If you get an error or timeout with deployment, changing the location can help, as there may be availability constraints for the resources.
diff --git a/docs/images/macae_waf_prompt.png b/docs/images/macae_waf_prompt.png
new file mode 100644
index 00000000..b3f8f6ca
Binary files /dev/null and b/docs/images/macae_waf_prompt.png differ
diff --git a/docs/images/read_me/solArchitectureWAF.png b/docs/images/read_me/solArchitectureWAF.png
new file mode 100644
index 00000000..673657a3
Binary files /dev/null and b/docs/images/read_me/solArchitectureWAF.png differ
diff --git a/infra/deploy_ai_foundry.bicep b/infra/deploy_ai_foundry.bicep
deleted file mode 100644
index 297dd2b7..00000000
--- a/infra/deploy_ai_foundry.bicep
+++ /dev/null
@@ -1,209 +0,0 @@
-// Creates Azure dependent resources for Azure AI studio
-@minLength(3)
-@maxLength(15)
-@description('Solution Name')
-param solutionName string
-param solutionLocation string
-param keyVaultName string
-param gptModelName string
-param gptModelVersion string
-param managedIdentityObjectId string
-param aiServicesEndpoint string
-param aiServicesKey string
-param aiServicesId string
-param aureaiFoundryEndpoint string
-
-param aiFoundryName string
-param existingLogAnalyticsWorkspaceId string = ''
-
-var useExisting = !empty(existingLogAnalyticsWorkspaceId)
-var existingLawSubscription = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[2] : ''
-var existingLawResourceGroup = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[4] : ''
-var existingLawName = useExisting ? split(existingLogAnalyticsWorkspaceId, '/')[8] : ''
-
-var abbrs = loadJsonContent('./abbreviations.json')
-
-var storageName = '${abbrs.storage.storageAccount}${solutionName}'
-
-var storageSkuName = 'Standard_LRS'
-var workspaceName = '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionName}'
-var keyvaultName = '${abbrs.security.keyVault}${solutionName}'
-var location = solutionLocation
-
-var aiSearchName = '${solutionName}-search'
-var applicationInsightsName = '${solutionName}-appi'
-
-
-
-resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
- name: keyVaultName
-}
-
-resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = if (useExisting) {
- name: existingLawName
- scope: resourceGroup(existingLawSubscription, existingLawResourceGroup)
-}
-
-resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = if (!useExisting) {
- name: workspaceName
- location: location
- tags: {}
- properties: {
- retentionInDays: 30
- sku: {
- name: 'PerGB2018'
- }
- }
-}
-
-resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
- name: applicationInsightsName
- location: location
- kind: 'web'
- properties: {
- Application_Type: 'web'
- publicNetworkAccessForIngestion: 'Enabled'
- publicNetworkAccessForQuery: 'Enabled'
- WorkspaceResourceId: useExisting ? existingLogAnalyticsWorkspaceId : logAnalytics.id
- }
-}
-
-var storageNameCleaned = replace(replace(replace(replace('${storageName}cast', '-', ''), '_', ''), '.', ''),'/', '')
-
-resource tenantIdEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'TENANT-ID'
- properties: {
- value: subscription().tenantId
- }
-}
-
-resource azureOpenAIInferenceEndpoint 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPENAI-INFERENCE-ENDPOINT'
- properties: {
- value:''
- }
-}
-
-resource azureOpenAIInferenceKey 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPENAI-INFERENCE-KEY'
- properties: {
- value:''
- }
-}
-
-resource azureOpenAIApiKeyEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPENAI-KEY'
- properties: {
- value: aiServicesKey //aiServices_m.listKeys().key1
- }
-}
-
-resource azureOpenAIDeploymentModel 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPEN-AI-DEPLOYMENT-MODEL'
- properties: {
- value: gptModelName
- }
-}
-
-resource azureOpenAIApiVersionEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPENAI-PREVIEW-API-VERSION'
- properties: {
- value: gptModelVersion //'2024-02-15-preview'
- }
-}
-
-resource azureOpenAIEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPENAI-ENDPOINT'
- properties: {
- value: aiServicesEndpoint//aiServices_m.properties.endpoint
- }
-}
-
-resource azureAIProjectEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AI-PROJECT-ENDPOINT'
- properties: {
- value: aureaiFoundryEndpoint
- }
-}
-
-resource azureOpenAICUApiVersionEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-OPENAI-CU-VERSION'
- properties: {
- value: '?api-version=2024-12-01-preview'
- }
-}
-
-resource azureSearchIndexEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-SEARCH-INDEX'
- properties: {
- value: 'transcripts_index'
- }
-}
-
-resource cogServiceEndpointEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'COG-SERVICES-ENDPOINT'
- properties: {
- value: aiServicesEndpoint
- }
-}
-
-resource cogServiceKeyEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'COG-SERVICES-KEY'
- properties: {
- value: aiServicesKey
- }
-}
-
-resource cogServiceNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'COG-SERVICES-NAME'
- properties: {
- value: aiFoundryName
- }
-}
-
-resource azureSubscriptionIdEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-SUBSCRIPTION-ID'
- properties: {
- value: subscription().subscriptionId
- }
-}
-
-resource resourceGroupNameEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-RESOURCE-GROUP'
- properties: {
- value: resourceGroup().name
- }
-}
-
-resource azureLocatioEntry 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
- parent: keyVault
- name: 'AZURE-LOCATION'
- properties: {
- value: solutionLocation
- }
-}
-
-output keyvaultName string = keyvaultName
-output keyvaultId string = keyVault.id
-
-output aiSearchName string = aiSearchName
-
-output storageAccountName string = storageNameCleaned
-
-output logAnalyticsId string = useExisting ? existingLogAnalyticsWorkspace.id : logAnalytics.id
-output applicationInsightsConnectionString string = applicationInsights.properties.ConnectionString
diff --git a/infra/deploy_keyvault.bicep b/infra/deploy_keyvault.bicep
deleted file mode 100644
index a10a9af6..00000000
--- a/infra/deploy_keyvault.bicep
+++ /dev/null
@@ -1,65 +0,0 @@
-@minLength(3)
-@maxLength(15)
-@description('Solution Name')
-param solutionName string
-param solutionLocation string
-param managedIdentityObjectId string
-
-param keyvaultName string
-
-resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = {
- name: keyvaultName
- location: solutionLocation
- properties: {
- createMode: 'default'
- accessPolicies: [
- {
- objectId: managedIdentityObjectId
- permissions: {
- certificates: [
- 'all'
- ]
- keys: [
- 'all'
- ]
- secrets: [
- 'all'
- ]
- storage: [
- 'all'
- ]
- }
- tenantId: subscription().tenantId
- }
- ]
- enabledForDeployment: true
- enabledForDiskEncryption: true
- enabledForTemplateDeployment: true
- enableRbacAuthorization: true
- publicNetworkAccess: 'enabled'
- sku: {
- family: 'A'
- name: 'standard'
- }
- softDeleteRetentionInDays: 7
- tenantId: subscription().tenantId
- }
-}
-
-@description('This is the built-in Key Vault Administrator role.')
-resource kvAdminRole 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
- scope: resourceGroup()
- name: '00482a5a-887f-4fb3-b363-3b7fe8e74483'
-}
-
-resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(resourceGroup().id, managedIdentityObjectId, kvAdminRole.id)
- properties: {
- principalId: managedIdentityObjectId
- roleDefinitionId:kvAdminRole.id
- principalType: 'ServicePrincipal'
- }
-}
-
-output keyvaultName string = keyvaultName
-output keyvaultId string = keyVault.id
diff --git a/infra/deploy_managed_identity.bicep b/infra/deploy_managed_identity.bicep
deleted file mode 100644
index 6e0b9dc2..00000000
--- a/infra/deploy_managed_identity.bicep
+++ /dev/null
@@ -1,49 +0,0 @@
-// ========== Managed Identity ========== //
-targetScope = 'resourceGroup'
-
-@minLength(3)
-@maxLength(15)
-@description('Solution Name')
-param solutionName string
-
-@description('Solution Location')
-param solutionLocation string
-
-@description('Name')
-
-param miName string
-
-
-resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
- name: miName
- location: solutionLocation
- tags: {
- app: solutionName
- location: solutionLocation
- }
-}
-
-@description('This is the built-in owner role. See https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#owner')
-resource ownerRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
- scope: resourceGroup()
- name: '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
-}
-
-resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(resourceGroup().id, managedIdentity.id, ownerRoleDefinition.id)
- properties: {
- principalId: managedIdentity.properties.principalId
- roleDefinitionId: ownerRoleDefinition.id
- principalType: 'ServicePrincipal'
- }
-}
-
-output managedIdentityOutput object = {
- id: managedIdentity.id
- objectId: managedIdentity.properties.principalId
- resourceId: managedIdentity.id
- location: managedIdentity.location
- name: miName
-}
-
-output managedIdentityId string = managedIdentity.id
diff --git a/infra/main.bicep b/infra/main.bicep
index cfcfa6ad..548d49be 100644
--- a/infra/main.bicep
+++ b/infra/main.bicep
@@ -1,11 +1,23 @@
+metadata name = 'Modernize Your Code Solution Accelerator'
+metadata description = '''CSA CTO Gold Standard Solution Accelerator for Modernize Your Code.
+'''
+
+@description('Set to true if you want to deploy WAF-aligned infrastructure.')
+param useWafAlignedArchitecture bool
+
@minLength(3)
-@description('Prefix for all resources created by this template. This should be 3-20 characters long. If your provide a prefix longer than 20 characters, it will be truncated to 20 characters.')
-param Prefix string
-var abbrs = loadJsonContent('./abbreviations.json')
-var safePrefix = length(Prefix) > 20 ? substring(Prefix, 0, 20) : Prefix
+@maxLength(16)
+@description('Required. A unique application/solution name for all resources in this deployment. This should be 3-16 characters long.')
+param solutionName string
+
+@maxLength(5)
+@description('Optional. A unique token for the solution. This is used to ensure resource names are unique for global resources. Defaults to a 5-character substring of the unique string generated from the subscription ID, resource group name, and solution name.')
+param solutionUniqueToken string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5)
-@description('Required. Location for all Resources except AI Foundry.')
-param solutionLocation string = resourceGroup().location
+@minLength(3)
+@metadata({ azd: { type: 'location' } })
+@description('Optional. Azure region for all services. Defaults to the resource group location.')
+param location string = resourceGroup().location
@allowed([
'australiaeast'
@@ -20,14 +32,50 @@ param solutionLocation string = resourceGroup().location
'westus'
'westus3'
])
-@description('Location for all Ai services resources. This location can be different from the resource group location.')
-param AzureAiServiceLocation string // The location used for all deployed resources. This location must be in the same region as the resource group.
+@metadata({
+ azd : {
+ type: 'location'
+ usageName : [
+ 'OpenAI.GlobalStandard.gpt-4o, 150'
+ ]
+ }
+})
+@description('Optional. Location for all AI service resources. This location can be different from the resource group location.')
+param aiDeploymentsLocation string
-@minValue(5)
-@description('Capacity of the GPT deployment:')
-param capacity int = 5
+@description('Optional. AI model deployment token capacity. Defaults to 150K tokens per minute.')
+param capacity int = 150
-param existingLogAnalyticsWorkspaceId string = ''
+@description('Optional. Enable monitoring for the resources. This will enable Application Insights and Log Analytics. Defaults to false.')
+param enableMonitoring bool = useWafAlignedArchitecture? true : false
+
+@description('Optional. Enable scaling for the container apps. Defaults to false.')
+param enableScaling bool = useWafAlignedArchitecture? true : false
+
+@description('Optional. Enable redundancy for applicable resources. Defaults to false.')
+param enableRedundancy bool = false
+
+@description('Optional. The secondary location for the Cosmos DB account if redundancy is enabled.')
+param secondaryLocation string?
+
+@description('Optional. Enable private networking for the resources. Set to true to enable private networking. Defaults to false.')
+param enablePrivateNetworking bool = useWafAlignedArchitecture? true : false
+
+@description('Optional. Admin username for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.')
+@secure()
+//param vmAdminUsername string = take(newGuid(), 20)
+param vmAdminUsername string?
+
+@description('Optional. Admin password for the Jumpbox Virtual Machine. Set to custom value if enablePrivateNetworking is true.')
+@secure()
+//param vmAdminPassword string = newGuid()
+param vmAdminPassword string?
+
+@description('Optional. Specifies the resource tags for all the resources. Tag "azd-env-name" is automatically added to all resources.')
+param tags object = {}
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
@minLength(1)
@description('GPT model deployment type:')
@@ -45,349 +93,388 @@ param imageVersion string = 'latest'
@description('Version of the GPT model to deploy:')
param gptModelVersion string = '2024-08-06'
+param existingLogAnalyticsWorkspaceId string = ''
-
-var uniqueId = toLower(uniqueString(subscription().id, safePrefix, resourceGroup().location))
-var UniquePrefix = 'cm${padLeft(take(uniqueId, 12), 12, '0')}'
-var ResourcePrefix = take('cm${safePrefix}${UniquePrefix}', 15)
-var cosmosdbDatabase = 'cmsadb'
-var cosmosdbBatchContainer = 'cmsabatch'
-var cosmosdbFileContainer = 'cmsafile'
-var cosmosdbLogContainer = 'cmsalog'
-var containerName = 'appstorage'
-var storageSkuName = 'Standard_LRS'
-var storageContainerName = replace(replace(replace(replace('${ResourcePrefix}cast', '-', ''), '_', ''), '.', ''),'/', '')
-
-var aiFoundryName = '${abbrs.ai.aiFoundry}${ResourcePrefix}'
-var aiProjectDescription = 'AI foundary project for CPS template'
-var aiProjectName = '${abbrs.ai.aiFoundryProject}${ResourcePrefix}'
-var aiProjectFriendlyName = aiProjectName
-
-var aiModelDeployments = [
+var allTags = union(
{
+ 'azd-env-name': solutionName
+ },
+ tags
+)
+
+var resourcesName = toLower(trim(replace(
+ replace(
+ replace(replace(replace(replace('${solutionName}${solutionUniqueToken}', '-', ''), '_', ''), '.', ''), '/', ''),
+ ' ',
+ ''
+ ),
+ '*',
+ ''
+)))
+
+var modelDeployment = {
+ name: llmModel
+ model: {
name: llmModel
- model: llmModel
+ format: 'OpenAI'
version: gptModelVersion
- sku: {
- name: deploymentType
- capacity: capacity
- }
- raiPolicyName: 'Microsoft.Default'
}
-]
-
-resource azureAiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
- name: aiFoundryName
- location: AzureAiServiceLocation
sku: {
- name: 'S0'
- }
- kind: 'AIServices'
- identity: {
- type: 'SystemAssigned'
- }
- properties: {
- allowProjectManagement: true
- customSubDomainName: aiFoundryName
- networkAcls: {
- defaultAction: 'Allow'
- virtualNetworkRules: []
- ipRules: []
- }
- publicNetworkAccess: 'Enabled'
- disableLocalAuth: false
+ name: deploymentType
+ capacity: capacity
}
+ raiPolicyName: 'Microsoft.Default'
}
-resource aiFoundryProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
- parent: azureAiServices
- name: aiProjectName
- location: AzureAiServiceLocation
- identity: {
- type: 'SystemAssigned'
- }
+#disable-next-line no-deployments-resources
+resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) {
+ name: take(
+ '46d3xbcp.ptn.sa-modernizeyourcode.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}',
+ 64
+ )
properties: {
- description: aiProjectDescription
- displayName: aiProjectFriendlyName
+ 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'
+ }
+ }
+ }
}
}
-@batchSize(1)
-resource azureAiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for aiModeldeployment in aiModelDeployments: {
- parent: azureAiServices //aiServices_m
- name: aiModeldeployment.name
- properties: {
- model: {
- format: 'OpenAI'
- name: aiModeldeployment.model
- version: aiModeldeployment.version
- }
- raiPolicyName: aiModeldeployment.raiPolicyName
- }
- sku:{
- name: aiModeldeployment.sku.name
- capacity: aiModeldeployment.sku.capacity
+module appIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = {
+ name: take('identity-app-${resourcesName}-deployment', 64)
+ params: {
+ name: 'id-app-${resourcesName}'
+ location: location
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
-}]
+}
+// Extracts subscription, resource group, and workspace name from the resource ID when using an existing Log Analytics workspace
+var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId)
+var existingLawSubscription = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[2] : ''
+var existingLawResourceGroup = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[4] : ''
+var existingLawName = useExistingLogAnalytics ? split(existingLogAnalyticsWorkspaceId, '/')[8] : ''
+resource existingLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = if (useExistingLogAnalytics) {
+ name: existingLawName
+ scope: resourceGroup(existingLawSubscription, existingLawResourceGroup)
+}
-//param storageAccountId string = 'storageAccountId'
-module managedIdentityModule 'deploy_managed_identity.bicep' = {
- name: 'deploy_managed_identity'
+// Deploy new Log Analytics workspace only if required and not using existing
+module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.11.2' = if ((enableMonitoring || enablePrivateNetworking) && !useExistingLogAnalytics) {
+ name: take('log-analytics-${resourcesName}-deployment', 64)
params: {
- miName:'${abbrs.security.managedIdentity}${ResourcePrefix}'
- solutionName: ResourcePrefix
- solutionLocation: solutionLocation
+ name: 'log-${resourcesName}'
+ location: location
+ skuName: 'PerGB2018'
+ dataRetention: 30
+ diagnosticSettings: [{ useThisWorkspace: true }]
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
- scope: resourceGroup(resourceGroup().name)
}
+// Log Analytics workspace ID, customer ID, and shared key (existing or new)
+var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics ? existingLogAnalyticsWorkspaceId : logAnalyticsWorkspace.outputs.resourceId
+var LogAnalyticsPrimarySharedKey string = useExistingLogAnalytics? existingLogAnalyticsWorkspace.listKeys().primarySharedKey : logAnalyticsWorkspace.outputs.primarySharedKey
+var LogAnalyticsWorkspaceId = useExistingLogAnalytics? existingLogAnalyticsWorkspace.properties.customerId : logAnalyticsWorkspace.outputs.logAnalyticsWorkspaceId
-// ==========Key Vault Module ========== //
-module kvault 'deploy_keyvault.bicep' = {
- name: 'deploy_keyvault'
+module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (enableMonitoring) {
+ name: take('app-insights-${resourcesName}-deployment', 64)
params: {
- keyvaultName: '${abbrs.security.keyVault}${ResourcePrefix}'
- solutionName: ResourcePrefix
- solutionLocation: solutionLocation
- managedIdentityObjectId:managedIdentityModule.outputs.managedIdentityOutput.objectId
+ name: 'appi-${resourcesName}'
+ location: location
+ workspaceResourceId: logAnalyticsWorkspaceResourceId
+ diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }]
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
- scope: resourceGroup(resourceGroup().name)
}
-
-// ==========AI Foundry and related resources ========== //
-module azureAifoundry 'deploy_ai_foundry.bicep' = {
- name: 'deploy_ai_foundry'
+module network 'modules/network.bicep' = if (enablePrivateNetworking) {
+ name: take('network-${resourcesName}-deployment', 64)
params: {
- solutionName: ResourcePrefix
- solutionLocation: AzureAiServiceLocation
- aiFoundryName: aiFoundryName
- keyVaultName: kvault.outputs.keyvaultName
- gptModelName: llmModel
- gptModelVersion: gptModelVersion
- managedIdentityObjectId:managedIdentityModule.outputs.managedIdentityOutput.objectId
- aiServicesEndpoint: azureAiServices.properties.endpoint
- aiServicesKey: azureAiServices.listKeys().key1
- aiServicesId: azureAiServices.id
- existingLogAnalyticsWorkspaceId: existingLogAnalyticsWorkspaceId
- aureaiFoundryEndpoint: aiFoundryProject.properties.endpoints['AI Foundry API']
+ resourcesName: resourcesName
+ logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspaceResourceId
+ vmAdminUsername: vmAdminUsername ?? 'JumpboxAdminUser'
+ vmAdminPassword: vmAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!'
+ location: location
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
- scope: resourceGroup(resourceGroup().name)
}
-module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.9.1' = {
- name: toLower('${ResourcePrefix}conAppsEnv')
+module aiServices 'modules/ai-foundry/main.bicep' = {
+ name: take('aiservices-${resourcesName}-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [logAnalyticsWorkspace, network] // required due to optional flags that could change dependency
params: {
- logAnalyticsWorkspaceResourceId: azureAifoundry.outputs.logAnalyticsId
- name: toLower('${ResourcePrefix}manenv')
- location: solutionLocation
- zoneRedundant: false
- managedIdentities: managedIdentityModule
+ name: 'ais-${resourcesName}'
+ location: aiDeploymentsLocation
+ sku: 'S0'
+ kind: 'AIServices'
+ deployments: [modelDeployment]
+ projectName: 'proj-${resourcesName}'
+ logAnalyticsWorkspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : ''
+ privateNetworking: enablePrivateNetworking
+ ? {
+ virtualNetworkResourceId: network.outputs.vnetResourceId
+ subnetResourceId: network.outputs.subnetPrivateEndpointsResourceId
+ }
+ : null
+ roleAssignments: [
+ {
+ principalId: appIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ roleDefinitionIdOrName: 'Cognitive Services OpenAI Contributor'
+ }
+ {
+ principalId: appIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer
+ }
+ {
+ principalId: appIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User
+ }
+ ]
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
}
-module databaseAccount 'br/public:avm/res/document-db/database-account:0.9.0' = {
- name: toLower('${abbrs.databases.cosmosDBDatabase}${ResourcePrefix}databaseAccount')
+var appStorageContainerName = 'appstorage'
+
+module storageAccount 'modules/storageAccount.bicep' = {
+ name: take('storage-account-${resourcesName}-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [logAnalyticsWorkspace, network] // required due to optional flags that could change dependency
params: {
- // Required parameters
- name: toLower('${abbrs.databases.cosmosDBDatabase}${ResourcePrefix}databaseAccount')
- // Non-required parameters
- enableAnalyticalStorage: true
- location: solutionLocation
- managedIdentities: {
- systemAssigned: true
- userAssignedResourceIds: [
- managedIdentityModule.outputs.managedIdentityOutput.resourceId
- ]
- }
- networkRestrictions: {
- networkAclBypass: 'AzureServices'
- publicNetworkAccess: 'Enabled'
- ipRules: [] // Adding ipRules as an empty array
- virtualNetworkRules: [] // Adding virtualNetworkRules as an empty array
- }
- disableKeyBasedMetadataWriteAccess: false
- locations: [
+ name: take('st${resourcesName}', 24)
+ location: location
+ tags: allTags
+ skuName: enableRedundancy ? 'Standard_GZRS' : 'Standard_LRS'
+ logAnalyticsWorkspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : ''
+ privateNetworking: enablePrivateNetworking
+ ? {
+ virtualNetworkResourceId: network.outputs.vnetResourceId
+ subnetResourceId: network.outputs.subnetPrivateEndpointsResourceId
+ }
+ : null
+ containers: [
{
- failoverPriority: 0
- isZoneRedundant: false
- locationName: solutionLocation
+ name: appStorageContainerName
+ properties: {
+ publicAccess: 'None'
+ }
}
]
- sqlDatabases: [
+ roleAssignments: [
{
- containers: [
- {
- indexingPolicy: {
- automatic: true
- }
- name: cosmosdbBatchContainer
- paths:[
- '/batch_id'
- ]
- }
- {
- indexingPolicy: {
- automatic: true
- }
- name: cosmosdbFileContainer
- paths:[
- '/file_id'
- ]
- }
- {
- indexingPolicy: {
- automatic: true
- }
- name: cosmosdbLogContainer
- paths:[
- '/log_id'
- ]
+ principalId: appIdentity.outputs.principalId
+ principalType: 'ServicePrincipal'
+ roleDefinitionIdOrName: 'Storage Blob Data Contributor'
+ }
+ ]
+ enableTelemetry: enableTelemetry
+ }
+}
+
+module keyVault 'modules/keyVault.bicep' = {
+ name: take('keyvault-${resourcesName}-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [logAnalyticsWorkspace, network] // required due to optional flags that could change dependency
+ params: {
+ name: take('kv-${resourcesName}', 24)
+ location: location
+ sku: 'standard'
+ logAnalyticsWorkspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : ''
+ privateNetworking: enablePrivateNetworking
+ ? {
+ virtualNetworkResourceId: network.outputs.vnetResourceId
+ subnetResourceId: network.outputs.subnetPrivateEndpointsResourceId
}
- ]
- name: cosmosdbDatabase
+ : null
+ roleAssignments: [
+ {
+ principalId: aiServices.outputs.?systemAssignedMIPrincipalId ?? ''
+ principalType: 'ServicePrincipal'
+ roleDefinitionIdOrName: 'Key Vault Reader'
}
]
-
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
+}
+module cosmosDb 'modules/cosmosDb.bicep' = {
+ name: take('cosmos-${resourcesName}-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [logAnalyticsWorkspace, network] // required due to optional flags that could change dependency
+ params: {
+ name: take('cosmos-${resourcesName}', 44)
+ location: location
+ dataAccessIdentityPrincipalId: appIdentity.outputs.principalId
+ logAnalyticsWorkspaceResourceId: enableMonitoring ? logAnalyticsWorkspaceResourceId : ''
+ zoneRedundant: enableRedundancy
+ secondaryLocation: enableRedundancy && !empty(secondaryLocation) ? secondaryLocation : ''
+ privateNetworking: enablePrivateNetworking
+ ? {
+ virtualNetworkResourceId: network.outputs.vnetResourceId
+ subnetResourceId: network.outputs.subnetPrivateEndpointsResourceId
+ }
+ : null
+ tags: allTags
+ enableTelemetry: enableTelemetry
+ }
}
-module containerAppFrontend 'br/public:avm/res/app/container-app:0.13.0' = {
- name: toLower('${abbrs.containers.containerApp}${ResourcePrefix}containerAppFrontend')
+var containerAppsEnvironmentName = 'cae-${resourcesName}'
+
+module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.11.2' = {
+ name: take('container-env-${resourcesName}-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [applicationInsights, logAnalyticsWorkspace, network] // required due to optional flags that could change dependency
params: {
+ name: containerAppsEnvironmentName
+ infrastructureResourceGroupName: '${resourceGroup().name}-ME-${containerAppsEnvironmentName}'
+ location: location
+ zoneRedundant: enableRedundancy && enablePrivateNetworking
+ publicNetworkAccess: 'Enabled' // public access required for frontend
+ infrastructureSubnetResourceId: enablePrivateNetworking ? network.outputs.subnetWebResourceId : null
managedIdentities: {
- systemAssigned: true
userAssignedResourceIds: [
- managedIdentityModule.outputs.managedIdentityOutput.resourceId
+ appIdentity.outputs.resourceId
]
}
- // Required parameters
- containers: [
- {
- env: [
+ appInsightsConnectionString: enableMonitoring ? applicationInsights.outputs.connectionString : null
+ appLogsConfiguration: enableMonitoring
+ ? {
+ destination: 'log-analytics'
+ logAnalyticsConfiguration: {
+ customerId: LogAnalyticsWorkspaceId
+ sharedKey: LogAnalyticsPrimarySharedKey
+ }
+ }
+ : {}
+ workloadProfiles: enablePrivateNetworking
+ ? [
+ // NOTE: workload profiles are required for private networking
{
- name: 'API_URL'
- value: 'https://${containerAppBackend.properties.configuration.ingress.fqdn}'
+ name: 'Consumption'
+ workloadProfileType: 'Consumption'
}
]
- image: 'cmsacontainerreg.azurecr.io/cmsafrontend:${imageVersion}'
- name: 'cmsafrontend'
- resources: {
- cpu: '1'
- memory: '2.0Gi'
- }
- }
- ]
- ingressTargetPort: 3000
- ingressExternal: true
- scaleMinReplicas: 1
- scaleMaxReplicas: 1
- environmentResourceId: containerAppsEnvironment.outputs.resourceId
- name: toLower('${abbrs.containers.containerApp}${ResourcePrefix}Frontend')
- // Non-required parameters
- location: solutionLocation
+ : []
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
}
-
-resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = {
- name: toLower('${abbrs.containers.containerApp}${ResourcePrefix}Backend')
- location: solutionLocation
- identity: {
- type: 'SystemAssigned'
- }
- properties: {
- managedEnvironmentId: containerAppsEnvironment.outputs.resourceId
- configuration: {
- ingress: {
- external: true
- targetPort: 8000
- }
+module containerAppBackend 'br/public:avm/res/app/container-app:0.17.0' = {
+ name: take('container-app-backend-${resourcesName}-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [applicationInsights] // required due to optional flags that could change dependency
+ params: {
+ name: take('ca-${resourcesName}backend', 32)
+ location: location
+ environmentResourceId: containerAppsEnvironment.outputs.resourceId
+ managedIdentities: {
+ userAssignedResourceIds: [
+ appIdentity.outputs.resourceId
+ ]
}
- template: {
- scale: {
- minReplicas: 1
- maxReplicas: 1
- }
- containers: [
- {
- name: 'cmsabackend'
- image: 'cmsacontainerreg.azurecr.io/cmsabackend:${imageVersion}'
- env: [
+ containers: [
+ {
+ name: 'cmsabackend'
+ image: 'cmsacontainerreg.azurecr.io/cmsabackend:${imageVersion}'
+ env: concat(
+ [
{
name: 'COSMOSDB_ENDPOINT'
- value: databaseAccount.outputs.endpoint
- }
- {
- name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
- value: azureAifoundry.outputs.applicationInsightsConnectionString
+ value: cosmosDb.outputs.endpoint
}
{
name: 'COSMOSDB_DATABASE'
- value: cosmosdbDatabase
+ value: cosmosDb.outputs.databaseName
}
{
name: 'COSMOSDB_BATCH_CONTAINER'
- value: cosmosdbBatchContainer
+ value: cosmosDb.outputs.containerNames.batch
}
{
name: 'COSMOSDB_FILE_CONTAINER'
- value: cosmosdbFileContainer
+ value: cosmosDb.outputs.containerNames.file
}
{
name: 'COSMOSDB_LOG_CONTAINER'
- value: cosmosdbLogContainer
+ value: cosmosDb.outputs.containerNames.log
}
{
name: 'AZURE_BLOB_ACCOUNT_NAME'
- value: storageContianerApp.name
+ value: storageAccount.outputs.name
}
{
name: 'AZURE_BLOB_CONTAINER_NAME'
- value: containerName
+ value: appStorageContainerName
}
{
name: 'AZURE_OPENAI_ENDPOINT'
- value: 'https://${aiFoundryName}.openai.azure.com/'
+ value: 'https://${aiServices.outputs.name}.openai.azure.com/'
}
{
name: 'MIGRATOR_AGENT_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'PICKER_AGENT_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'FIXER_AGENT_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'SEMANTIC_VERIFIER_AGENT_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'SYNTAX_CHECKER_AGENT_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'SELECTION_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'TERMINATION_MODEL_DEPLOY'
- value: llmModel
+ value: modelDeployment.name
}
{
name: 'AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME'
- value: llmModel
+ value: modelDeployment.name
+ }
+ {
+ name: 'AI_PROJECT_ENDPOINT'
+ value: aiServices.outputs.project.apiEndpoint // or equivalent
+ }
+ {
+ name: 'AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' // This was not really used in code.
+ value: aiServices.outputs.project.apiEndpoint
}
{
name: 'AZURE_AI_AGENT_PROJECT_NAME'
- value: aiProjectName
+ value: aiServices.outputs.project.name
}
{
name: 'AZURE_AI_AGENT_RESOURCE_GROUP_NAME'
@@ -398,169 +485,118 @@ resource containerAppBackend 'Microsoft.App/containerApps@2023-05-01' = {
value: subscription().subscriptionId
}
{
- name: 'AI_PROJECT_ENDPOINT'
- value: aiFoundryProject.properties.endpoints['AI Foundry API']
+ name: 'AZURE_AI_AGENT_ENDPOINT'
+ value: aiServices.outputs.project.apiEndpoint
}
- ]
- resources: {
- cpu: 1
- memory: '2.0Gi'
- }
- }
- ]
- }
- }
-}
-resource storageContianerApp 'Microsoft.Storage/storageAccounts@2022-09-01' = {
- name: storageContainerName
- location: solutionLocation
- sku: {
- name: storageSkuName
- }
- kind: 'StorageV2'
- identity: {
- type: 'SystemAssigned' // Enables Managed Identity
- }
- properties: {
- accessTier: 'Hot'
- allowBlobPublicAccess: false
- allowCrossTenantReplication: false
- allowSharedKeyAccess: false
- encryption: {
- keySource: 'Microsoft.Storage'
- requireInfrastructureEncryption: false
- services: {
- blob: {
- enabled: true
- keyType: 'Account'
- }
- file: {
- enabled: true
- keyType: 'Account'
- }
- queue: {
- enabled: true
- keyType: 'Service'
- }
- table: {
- enabled: true
- keyType: 'Service'
+ {
+ name: 'AZURE_CLIENT_ID'
+ value: appIdentity.outputs.clientId // NOTE: This is the client ID of the managed identity, not the Entra application, and is needed for the App Service to access the Cosmos DB account.
+ }
+ ],
+ enableMonitoring
+ ? [
+ {
+ name: 'APPLICATIONINSIGHTS_INSTRUMENTATION_KEY'
+ value: applicationInsights.outputs.instrumentationKey
+ }
+ {
+ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
+ value: applicationInsights.outputs.connectionString
+ }
+ ]
+ : []
+ )
+ resources: {
+ cpu: 1
+ memory: '2.0Gi'
}
+ probes: enableMonitoring
+ ? [
+ {
+ httpGet: {
+ path: '/health'
+ port: 8000
+ }
+ initialDelaySeconds: 3
+ periodSeconds: 3
+ type: 'Liveness'
+ }
+ ]
+ : []
}
+ ]
+ ingressTargetPort: 8000
+ ingressExternal: true
+ scaleSettings: {
+ maxReplicas: enableScaling ? 3 : 1
+ minReplicas: 1
+ rules: enableScaling
+ ? [
+ {
+ name: 'http-scaler'
+ http: {
+ metadata: {
+ concurrentRequests: 100
+ }
+ }
+ }
+ ]
+ : []
}
- isHnsEnabled: false
- isNfsV3Enabled: false
- keyPolicy: {
- keyExpirationPeriodInDays: 7
- }
- largeFileSharesState: 'Disabled'
- minimumTlsVersion: 'TLS1_2'
- networkAcls: {
- bypass: 'AzureServices'
- defaultAction: 'Allow'
- }
- supportsHttpsTrafficOnly: true
- }
-}
-resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(containerAppBackend.id, 'Storage Blob Data Contributor')
- scope: storageContianerApp
- properties: {
- roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') // Storage Blob Data Contributor
- principalId: containerAppBackend.identity.principalId
- principalType: 'ServicePrincipal'
- }
-}
-var openAiContributorRoleId = 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Fixed Role ID for OpenAI Contributor
-
-resource openAiRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(containerAppBackend.id, openAiContributorRoleId)
- scope: azureAiServices
- properties: {
- roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', openAiContributorRoleId) // OpenAI Service Contributor
- principalId: containerAppBackend.identity.principalId
- principalType: 'ServicePrincipal'
- }
-}
-
-var containerNames = [
- containerName
-]
-
-// Create a blob container resource for each container name.
-resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-08-01' = [for containerName in containerNames: {
- name: '${storageContainerName}/default/${containerName}'
- properties: {
- publicAccess: 'None'
- }
- dependsOn: [azureAifoundry]
-}]
-
-resource aiDeveloper 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
- name: '64702f94-c441-49e6-a78b-ef80e0188fee'
-}
-
-resource aiDeveloperAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(containerAppBackend.name, aiDeveloper.id)
- scope: resourceGroup()
- properties: {
- roleDefinitionId: aiDeveloper.id
- principalId: containerAppBackend.identity.principalId
- principalType: 'ServicePrincipal'
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
}
-
-resource aiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
- name: '53ca6127-db72-4b80-b1b0-d745d6d5456d'
-}
-
-resource aiUserAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(containerAppBackend.name, aiUser.id)
- scope: resourceGroup()
- properties: {
- roleDefinitionId: aiUser.id
- principalId: containerAppBackend.identity.principalId
- principalType: 'ServicePrincipal'
- }
-}
-
-resource aiUserAccessFoundry 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
- name: guid(containerAppBackend.name, aiFoundryProject.id)
- scope: resourceGroup()
- properties: {
- roleDefinitionId: aiUser.id
- principalId: containerAppBackend.identity.principalId
- principalType: 'ServicePrincipal'
- }
-}
-
-resource contributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2021-06-15' existing = {
- name: '${databaseAccount.name}/00000000-0000-0000-0000-000000000002'
-}
-
-var cosmosAssignCli = 'az cosmosdb sql role assignment create --resource-group "${resourceGroup().name}" --account-name "${databaseAccount.outputs.name}" --role-definition-id "${contributorRoleDefinition.id}" --scope "${databaseAccount.outputs.resourceId}" --principal-id "${containerAppBackend.identity.principalId}"'
-
-module deploymentScriptCLI 'br/public:avm/res/resources/deployment-script:0.5.1' = {
- name: 'deploymentScriptCLI'
+module containerAppFrontend 'br/public:avm/res/app/container-app:0.17.0' = {
+ name: take('container-app-frontend-${resourcesName}-deployment', 64)
params: {
- // Required parameters
- kind: 'AzureCLI'
- name: 'rdsmin001'
- // Non-required parameters
- azCliVersion: '2.69.0'
- location: resourceGroup().location
+ name: take('ca-${resourcesName}frontend', 32)
+ location: location
+ environmentResourceId: containerAppsEnvironment.outputs.resourceId
managedIdentities: {
userAssignedResourceIds: [
- managedIdentityModule.outputs.managedIdentityId
+ appIdentity.outputs.resourceId
]
}
- scriptContent: cosmosAssignCli
+ containers: [
+ {
+ env: [
+ {
+ name: 'API_URL'
+ value: 'https://${containerAppBackend.outputs.fqdn}'
+ }
+ ]
+ image: 'cmsacontainerreg.azurecr.io/cmsafrontend:${imageVersion}'
+ name: 'cmsafrontend'
+ resources: {
+ cpu: '1'
+ memory: '2.0Gi'
+ }
+ }
+ ]
+ ingressTargetPort: 3000
+ ingressExternal: true
+ scaleSettings: {
+ maxReplicas: enableScaling ? 3 : 1
+ minReplicas: 1
+ rules: enableScaling
+ ? [
+ {
+ name: 'http-scaler'
+ http: {
+ metadata: {
+ concurrentRequests: 100
+ }
+ }
+ }
+ ]
+ : []
+ }
+ tags: allTags
+ enableTelemetry: enableTelemetry
}
}
-output AZURE_AIFOUNDRY_NAME string = azureAiServices.name
-output WEB_APP_URL string = 'https://${containerAppFrontend.outputs.fqdn}'
-output aiFoundryName string = aiFoundryName
-output aiProjectName string = aiFoundryProject.name
-output projectEndpointString string = aiFoundryProject.properties.endpoints['AI Foundry API']
+@description('The resource group the resources were deployed into.')
+output resourceGroupName string = resourceGroup().name
diff --git a/infra/main.bicepparam b/infra/main.bicepparam
deleted file mode 100644
index f3febd92..00000000
--- a/infra/main.bicepparam
+++ /dev/null
@@ -1,11 +0,0 @@
-using './main.bicep'
-
-param Prefix = readEnvironmentVariable('AZURE_ENV_NAME','azdtemp')
-param solutionLocation = readEnvironmentVariable('AZURE_LOCATION', 'eastus2')
-param AzureAiServiceLocation = readEnvironmentVariable('AZURE_AISERVICE_LOCATION','japaneast')
-param capacity = int(readEnvironmentVariable('AZURE_ENV_MODEL_CAPACITY', '50'))
-param deploymentType = readEnvironmentVariable('AZURE_ENV_MODEL_DEPLOYMENT_TYPE', 'GlobalStandard')
-param llmModel = readEnvironmentVariable('AZURE_ENV_MODEL_NAME', 'gpt-4o')
-param gptModelVersion = readEnvironmentVariable('AZURE_ENV_MODEL_VERSION', '2024-08-06')
-param imageVersion = readEnvironmentVariable('AZURE_ENV_IMAGETAG', 'latest')
-param existingLogAnalyticsWorkspaceId = readEnvironmentVariable('AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID', '')
diff --git a/infra/main.json b/infra/main.json
deleted file mode 100644
index 9f8f21ca..00000000
--- a/infra/main.json
+++ /dev/null
@@ -1,7695 +0,0 @@
-{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.35.1.17967",
- "templateHash": "17669563175804664564"
- }
- },
- "parameters": {
- "Prefix": {
- "type": "string",
- "minLength": 3,
- "metadata": {
- "description": "Prefix for all resources created by this template. This should be 3-20 characters long. If your provide a prefix longer than 20 characters, it will be truncated to 20 characters."
- }
- },
- "solutionLocation": {
- "type": "string",
- "defaultValue": "[resourceGroup().location]",
- "metadata": {
- "description": "Required. Location for all Resources except AI Foundry."
- }
- },
- "AzureAiServiceLocation": {
- "type": "string",
- "allowedValues": [
- "australiaeast",
- "eastus",
- "eastus2",
- "francecentral",
- "japaneast",
- "norwayeast",
- "southindia",
- "swedencentral",
- "uksouth",
- "westus",
- "westus3"
- ],
- "metadata": {
- "description": "Location for all Ai services resources. This location can be different from the resource group location."
- }
- },
- "capacity": {
- "type": "int",
- "defaultValue": 5,
- "minValue": 5,
- "metadata": {
- "description": "Capacity of the GPT deployment:"
- }
- },
- "existingLogAnalyticsWorkspaceId": {
- "type": "string",
- "defaultValue": ""
- },
- "deploymentType": {
- "type": "string",
- "defaultValue": "GlobalStandard",
- "minLength": 1,
- "metadata": {
- "description": "GPT model deployment type:"
- }
- },
- "llmModel": {
- "type": "string",
- "defaultValue": "gpt-4o",
- "minLength": 1,
- "metadata": {
- "description": "Name of the GPT model to deploy:"
- }
- },
- "imageVersion": {
- "type": "string",
- "defaultValue": "latest",
- "minLength": 1,
- "metadata": {
- "description": "Set the Image tag:"
- }
- },
- "gptModelVersion": {
- "type": "string",
- "defaultValue": "2024-08-06",
- "minLength": 1,
- "metadata": {
- "description": "Version of the GPT model to deploy:"
- }
- }
- },
- "variables": {
- "$fxv#0": {
- "ai": {
- "aiSearch": "srch-",
- "aiServices": "aisa-",
- "aiFoundry": "aif-",
- "aiFoundryProject": "aifp-",
- "aiVideoIndexer": "avi-",
- "machineLearningWorkspace": "mlw-",
- "openAIService": "oai-",
- "botService": "bot-",
- "computerVision": "cv-",
- "contentModerator": "cm-",
- "contentSafety": "cs-",
- "customVisionPrediction": "cstv-",
- "customVisionTraining": "cstvt-",
- "documentIntelligence": "di-",
- "faceApi": "face-",
- "healthInsights": "hi-",
- "immersiveReader": "ir-",
- "languageService": "lang-",
- "speechService": "spch-",
- "translator": "trsl-",
- "aiHub": "aih-",
- "aiHubProject": "aihp-"
- },
- "analytics": {
- "analysisServicesServer": "as",
- "databricksWorkspace": "dbw-",
- "dataExplorerCluster": "dec",
- "dataExplorerClusterDatabase": "dedb",
- "dataFactory": "adf-",
- "digitalTwin": "dt-",
- "streamAnalytics": "asa-",
- "synapseAnalyticsPrivateLinkHub": "synplh-",
- "synapseAnalyticsSQLDedicatedPool": "syndp",
- "synapseAnalyticsSparkPool": "synsp",
- "synapseAnalyticsWorkspaces": "synw",
- "dataLakeStoreAccount": "dls",
- "dataLakeAnalyticsAccount": "dla",
- "eventHubsNamespace": "evhns-",
- "eventHub": "evh-",
- "eventGridDomain": "evgd-",
- "eventGridSubscriptions": "evgs-",
- "eventGridTopic": "evgt-",
- "eventGridSystemTopic": "egst-",
- "hdInsightHadoopCluster": "hadoop-",
- "hdInsightHBaseCluster": "hbase-",
- "hdInsightKafkaCluster": "kafka-",
- "hdInsightSparkCluster": "spark-",
- "hdInsightStormCluster": "storm-",
- "hdInsightMLServicesCluster": "mls-",
- "iotHub": "iot-",
- "provisioningServices": "provs-",
- "provisioningServicesCertificate": "pcert-",
- "powerBIEmbedded": "pbi-",
- "timeSeriesInsightsEnvironment": "tsi-"
- },
- "compute": {
- "appServiceEnvironment": "ase-",
- "appServicePlan": "asp-",
- "loadTesting": "lt-",
- "availabilitySet": "avail-",
- "arcEnabledServer": "arcs-",
- "arcEnabledKubernetesCluster": "arck",
- "batchAccounts": "ba-",
- "cloudService": "cld-",
- "communicationServices": "acs-",
- "diskEncryptionSet": "des",
- "functionApp": "func-",
- "gallery": "gal",
- "hostingEnvironment": "host-",
- "imageTemplate": "it-",
- "managedDiskOS": "osdisk",
- "managedDiskData": "disk",
- "notificationHubs": "ntf-",
- "notificationHubsNamespace": "ntfns-",
- "proximityPlacementGroup": "ppg-",
- "restorePointCollection": "rpc-",
- "snapshot": "snap-",
- "staticWebApp": "stapp-",
- "virtualMachine": "vm",
- "virtualMachineScaleSet": "vmss-",
- "virtualMachineMaintenanceConfiguration": "mc-",
- "virtualMachineStorageAccount": "stvm",
- "webApp": "app-"
- },
- "containers": {
- "aksCluster": "aks-",
- "aksSystemNodePool": "npsystem-",
- "aksUserNodePool": "np-",
- "containerApp": "ca-",
- "containerAppsEnvironment": "cae-",
- "containerRegistry": "cr",
- "containerInstance": "ci",
- "serviceFabricCluster": "sf-",
- "serviceFabricManagedCluster": "sfmc-"
- },
- "databases": {
- "cosmosDBDatabase": "cosmos-",
- "cosmosDBApacheCassandra": "coscas-",
- "cosmosDBMongoDB": "cosmon-",
- "cosmosDBNoSQL": "cosno-",
- "cosmosDBTable": "costab-",
- "cosmosDBGremlin": "cosgrm-",
- "cosmosDBPostgreSQL": "cospos-",
- "cacheForRedis": "redis-",
- "sqlDatabaseServer": "sql-",
- "sqlDatabase": "sqldb-",
- "sqlElasticJobAgent": "sqlja-",
- "sqlElasticPool": "sqlep-",
- "mariaDBServer": "maria-",
- "mariaDBDatabase": "mariadb-",
- "mySQLDatabase": "mysql-",
- "postgreSQLDatabase": "psql-",
- "sqlServerStretchDatabase": "sqlstrdb-",
- "sqlManagedInstance": "sqlmi-"
- },
- "developerTools": {
- "appConfigurationStore": "appcs-",
- "mapsAccount": "map-",
- "signalR": "sigr",
- "webPubSub": "wps-"
- },
- "devOps": {
- "managedGrafana": "amg-"
- },
- "integration": {
- "apiManagementService": "apim-",
- "integrationAccount": "ia-",
- "logicApp": "logic-",
- "serviceBusNamespace": "sbns-",
- "serviceBusQueue": "sbq-",
- "serviceBusTopic": "sbt-",
- "serviceBusTopicSubscription": "sbts-"
- },
- "managementGovernance": {
- "automationAccount": "aa-",
- "applicationInsights": "appi-",
- "monitorActionGroup": "ag-",
- "monitorDataCollectionRules": "dcr-",
- "monitorAlertProcessingRule": "apr-",
- "blueprint": "bp-",
- "blueprintAssignment": "bpa-",
- "dataCollectionEndpoint": "dce-",
- "logAnalyticsWorkspace": "log-",
- "logAnalyticsQueryPacks": "pack-",
- "managementGroup": "mg-",
- "purviewInstance": "pview-",
- "resourceGroup": "rg-",
- "templateSpecsName": "ts-"
- },
- "migration": {
- "migrateProject": "migr-",
- "databaseMigrationService": "dms-",
- "recoveryServicesVault": "rsv-"
- },
- "networking": {
- "applicationGateway": "agw-",
- "applicationSecurityGroup": "asg-",
- "cdnProfile": "cdnp-",
- "cdnEndpoint": "cdne-",
- "connections": "con-",
- "dnsForwardingRuleset": "dnsfrs-",
- "dnsPrivateResolver": "dnspr-",
- "dnsPrivateResolverInboundEndpoint": "in-",
- "dnsPrivateResolverOutboundEndpoint": "out-",
- "firewall": "afw-",
- "firewallPolicy": "afwp-",
- "expressRouteCircuit": "erc-",
- "expressRouteGateway": "ergw-",
- "frontDoorProfile": "afd-",
- "frontDoorEndpoint": "fde-",
- "frontDoorFirewallPolicy": "fdfp-",
- "ipGroups": "ipg-",
- "loadBalancerInternal": "lbi-",
- "loadBalancerExternal": "lbe-",
- "loadBalancerRule": "rule-",
- "localNetworkGateway": "lgw-",
- "natGateway": "ng-",
- "networkInterface": "nic-",
- "networkSecurityGroup": "nsg-",
- "networkSecurityGroupSecurityRules": "nsgsr-",
- "networkWatcher": "nw-",
- "privateLink": "pl-",
- "privateEndpoint": "pep-",
- "publicIPAddress": "pip-",
- "publicIPAddressPrefix": "ippre-",
- "routeFilter": "rf-",
- "routeServer": "rtserv-",
- "routeTable": "rt-",
- "serviceEndpointPolicy": "se-",
- "trafficManagerProfile": "traf-",
- "userDefinedRoute": "udr-",
- "virtualNetwork": "vnet-",
- "virtualNetworkGateway": "vgw-",
- "virtualNetworkManager": "vnm-",
- "virtualNetworkPeering": "peer-",
- "virtualNetworkSubnet": "snet-",
- "virtualWAN": "vwan-",
- "virtualWANHub": "vhub-"
- },
- "security": {
- "bastion": "bas-",
- "keyVault": "kv-",
- "keyVaultManagedHSM": "kvmhsm-",
- "managedIdentity": "id-",
- "sshKey": "sshkey-",
- "vpnGateway": "vpng-",
- "vpnConnection": "vcn-",
- "vpnSite": "vst-",
- "webApplicationFirewallPolicy": "waf",
- "webApplicationFirewallPolicyRuleGroup": "wafrg"
- },
- "storage": {
- "storSimple": "ssimp",
- "backupVault": "bvault-",
- "backupVaultPolicy": "bkpol-",
- "fileShare": "share-",
- "storageAccount": "st",
- "storageSyncService": "sss-"
- },
- "virtualDesktop": {
- "labServicesPlan": "lp-",
- "virtualDesktopHostPool": "vdpool-",
- "virtualDesktopApplicationGroup": "vdag-",
- "virtualDesktopWorkspace": "vdws-",
- "virtualDesktopScalingPlan": "vdscaling-"
- }
- },
- "abbrs": "[variables('$fxv#0')]",
- "safePrefix": "[if(greater(length(parameters('Prefix')), 20), substring(parameters('Prefix'), 0, 20), parameters('Prefix'))]",
- "uniqueId": "[toLower(uniqueString(subscription().id, variables('safePrefix'), resourceGroup().location))]",
- "UniquePrefix": "[format('cm{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]",
- "ResourcePrefix": "[take(format('cm{0}{1}', variables('safePrefix'), variables('UniquePrefix')), 15)]",
- "cosmosdbDatabase": "cmsadb",
- "cosmosdbBatchContainer": "cmsabatch",
- "cosmosdbFileContainer": "cmsafile",
- "cosmosdbLogContainer": "cmsalog",
- "containerName": "appstorage",
- "storageSkuName": "Standard_LRS",
- "storageContainerName": "[replace(replace(replace(replace(format('{0}cast', variables('ResourcePrefix')), '-', ''), '_', ''), '.', ''), '/', '')]",
- "aiFoundryName": "[format('{0}{1}', variables('abbrs').ai.aiFoundry, variables('ResourcePrefix'))]",
- "aiProjectDescription": "AI foundary project for CPS template",
- "aiProjectName": "[format('{0}{1}', variables('abbrs').ai.aiFoundryProject, variables('ResourcePrefix'))]",
- "aiProjectFriendlyName": "[variables('aiProjectName')]",
- "aiModelDeployments": [
- {
- "name": "[parameters('llmModel')]",
- "model": "[parameters('llmModel')]",
- "version": "[parameters('gptModelVersion')]",
- "sku": {
- "name": "[parameters('deploymentType')]",
- "capacity": "[parameters('capacity')]"
- },
- "raiPolicyName": "Microsoft.Default"
- }
- ],
- "openAiContributorRoleId": "a001fd3d-188f-4b5d-821b-7da978bf7442",
- "containerNames": [
- "[variables('containerName')]"
- ]
- },
- "resources": [
- {
- "type": "Microsoft.CognitiveServices/accounts",
- "apiVersion": "2025-04-01-preview",
- "name": "[variables('aiFoundryName')]",
- "location": "[parameters('AzureAiServiceLocation')]",
- "sku": {
- "name": "S0"
- },
- "kind": "AIServices",
- "identity": {
- "type": "SystemAssigned"
- },
- "properties": {
- "allowProjectManagement": true,
- "customSubDomainName": "[variables('aiFoundryName')]",
- "networkAcls": {
- "defaultAction": "Allow",
- "virtualNetworkRules": [],
- "ipRules": []
- },
- "publicNetworkAccess": "Enabled",
- "disableLocalAuth": false
- }
- },
- {
- "type": "Microsoft.CognitiveServices/accounts/projects",
- "apiVersion": "2025-04-01-preview",
- "name": "[format('{0}/{1}', variables('aiFoundryName'), variables('aiProjectName'))]",
- "location": "[parameters('AzureAiServiceLocation')]",
- "identity": {
- "type": "SystemAssigned"
- },
- "properties": {
- "description": "[variables('aiProjectDescription')]",
- "displayName": "[variables('aiProjectFriendlyName')]"
- },
- "dependsOn": [
- "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]"
- ]
- },
- {
- "copy": {
- "name": "azureAiServicesDeployments",
- "count": "[length(variables('aiModelDeployments'))]",
- "mode": "serial",
- "batchSize": 1
- },
- "type": "Microsoft.CognitiveServices/accounts/deployments",
- "apiVersion": "2023-05-01",
- "name": "[format('{0}/{1}', variables('aiFoundryName'), variables('aiModelDeployments')[copyIndex()].name)]",
- "properties": {
- "model": {
- "format": "OpenAI",
- "name": "[variables('aiModelDeployments')[copyIndex()].model]",
- "version": "[variables('aiModelDeployments')[copyIndex()].version]"
- },
- "raiPolicyName": "[variables('aiModelDeployments')[copyIndex()].raiPolicyName]"
- },
- "sku": {
- "name": "[variables('aiModelDeployments')[copyIndex()].sku.name]",
- "capacity": "[variables('aiModelDeployments')[copyIndex()].sku.capacity]"
- },
- "dependsOn": [
- "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]"
- ]
- },
- {
- "type": "Microsoft.App/containerApps",
- "apiVersion": "2023-05-01",
- "name": "[toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))]",
- "location": "[parameters('solutionLocation')]",
- "identity": {
- "type": "SystemAssigned"
- },
- "properties": {
- "managedEnvironmentId": "[reference(resourceId('Microsoft.Resources/deployments', toLower(format('{0}conAppsEnv', variables('ResourcePrefix')))), '2022-09-01').outputs.resourceId.value]",
- "configuration": {
- "ingress": {
- "external": true,
- "targetPort": 8000
- }
- },
- "template": {
- "scale": {
- "minReplicas": 1,
- "maxReplicas": 1
- },
- "containers": [
- {
- "name": "cmsabackend",
- "image": "[format('cmsacontainerreg.azurecr.io/cmsabackend:{0}', parameters('imageVersion'))]",
- "env": [
- {
- "name": "COSMOSDB_ENDPOINT",
- "value": "[reference(resourceId('Microsoft.Resources/deployments', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))), '2022-09-01').outputs.endpoint.value]"
- },
- {
- "name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
- "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.applicationInsightsConnectionString.value]"
- },
- {
- "name": "COSMOSDB_DATABASE",
- "value": "[variables('cosmosdbDatabase')]"
- },
- {
- "name": "COSMOSDB_BATCH_CONTAINER",
- "value": "[variables('cosmosdbBatchContainer')]"
- },
- {
- "name": "COSMOSDB_FILE_CONTAINER",
- "value": "[variables('cosmosdbFileContainer')]"
- },
- {
- "name": "COSMOSDB_LOG_CONTAINER",
- "value": "[variables('cosmosdbLogContainer')]"
- },
- {
- "name": "AZURE_BLOB_ACCOUNT_NAME",
- "value": "[variables('storageContainerName')]"
- },
- {
- "name": "AZURE_BLOB_CONTAINER_NAME",
- "value": "[variables('containerName')]"
- },
- {
- "name": "AZURE_OPENAI_ENDPOINT",
- "value": "[format('https://{0}.openai.azure.com/', variables('aiFoundryName'))]"
- },
- {
- "name": "MIGRATOR_AGENT_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "PICKER_AGENT_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "FIXER_AGENT_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "SEMANTIC_VERIFIER_AGENT_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "SYNTAX_CHECKER_AGENT_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "SELECTION_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "TERMINATION_MODEL_DEPLOY",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME",
- "value": "[parameters('llmModel')]"
- },
- {
- "name": "AZURE_AI_AGENT_PROJECT_NAME",
- "value": "[variables('aiProjectName')]"
- },
- {
- "name": "AZURE_AI_AGENT_RESOURCE_GROUP_NAME",
- "value": "[resourceGroup().name]"
- },
- {
- "name": "AZURE_AI_AGENT_SUBSCRIPTION_ID",
- "value": "[subscription().subscriptionId]"
- },
- {
- "name": "AI_PROJECT_ENDPOINT",
- "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview').endpoints['AI Foundry API']]"
- }
- ],
- "resources": {
- "cpu": 1,
- "memory": "2.0Gi"
- }
- }
- ]
- }
- },
- "dependsOn": [
- "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName'))]",
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry')]",
- "[resourceId('Microsoft.Resources/deployments', toLower(format('{0}conAppsEnv', variables('ResourcePrefix'))))]",
- "[resourceId('Microsoft.Resources/deployments', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix'))))]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('storageContainerName'))]"
- ]
- },
- {
- "type": "Microsoft.Storage/storageAccounts",
- "apiVersion": "2022-09-01",
- "name": "[variables('storageContainerName')]",
- "location": "[parameters('solutionLocation')]",
- "sku": {
- "name": "[variables('storageSkuName')]"
- },
- "kind": "StorageV2",
- "identity": {
- "type": "SystemAssigned"
- },
- "properties": {
- "accessTier": "Hot",
- "allowBlobPublicAccess": false,
- "allowCrossTenantReplication": false,
- "allowSharedKeyAccess": false,
- "encryption": {
- "keySource": "Microsoft.Storage",
- "requireInfrastructureEncryption": false,
- "services": {
- "blob": {
- "enabled": true,
- "keyType": "Account"
- },
- "file": {
- "enabled": true,
- "keyType": "Account"
- },
- "queue": {
- "enabled": true,
- "keyType": "Service"
- },
- "table": {
- "enabled": true,
- "keyType": "Service"
- }
- }
- },
- "isHnsEnabled": false,
- "isNfsV3Enabled": false,
- "keyPolicy": {
- "keyExpirationPeriodInDays": 7
- },
- "largeFileSharesState": "Disabled",
- "minimumTlsVersion": "TLS1_2",
- "networkAcls": {
- "bypass": "AzureServices",
- "defaultAction": "Allow"
- },
- "supportsHttpsTrafficOnly": true
- }
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.Storage/storageAccounts/{0}', variables('storageContainerName'))]",
- "name": "[guid(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), 'Storage Blob Data Contributor')]",
- "properties": {
- "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
- "principalId": "[reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01', 'full').identity.principalId]",
- "principalType": "ServicePrincipal"
- },
- "dependsOn": [
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]",
- "[resourceId('Microsoft.Storage/storageAccounts', variables('storageContainerName'))]"
- ]
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('aiFoundryName'))]",
- "name": "[guid(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), variables('openAiContributorRoleId'))]",
- "properties": {
- "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('openAiContributorRoleId'))]",
- "principalId": "[reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01', 'full').identity.principalId]",
- "principalType": "ServicePrincipal"
- },
- "dependsOn": [
- "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]",
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]"
- ]
- },
- {
- "copy": {
- "name": "containers",
- "count": "[length(variables('containerNames'))]"
- },
- "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
- "apiVersion": "2021-08-01",
- "name": "[format('{0}/default/{1}', variables('storageContainerName'), variables('containerNames')[copyIndex()])]",
- "properties": {
- "publicAccess": "None"
- },
- "dependsOn": [
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry')]"
- ]
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "name": "[guid(toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))), resourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee'))]",
- "properties": {
- "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]",
- "principalId": "[reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01', 'full').identity.principalId]",
- "principalType": "ServicePrincipal"
- },
- "dependsOn": [
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]"
- ]
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "name": "[guid(toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))), resourceId('Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d'))]",
- "properties": {
- "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]",
- "principalId": "[reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01', 'full').identity.principalId]",
- "principalType": "ServicePrincipal"
- },
- "dependsOn": [
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]"
- ]
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "name": "[guid(toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))), resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')))]",
- "properties": {
- "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '53ca6127-db72-4b80-b1b0-d745d6d5456d')]",
- "principalId": "[reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01', 'full').identity.principalId]",
- "principalType": "ServicePrincipal"
- },
- "dependsOn": [
- "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName'))]",
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]"
- ]
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "deploy_managed_identity",
- "resourceGroup": "[resourceGroup().name]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "miName": {
- "value": "[format('{0}{1}', variables('abbrs').security.managedIdentity, variables('ResourcePrefix'))]"
- },
- "solutionName": {
- "value": "[variables('ResourcePrefix')]"
- },
- "solutionLocation": {
- "value": "[parameters('solutionLocation')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.35.1.17967",
- "templateHash": "4754404420489871145"
- }
- },
- "parameters": {
- "solutionName": {
- "type": "string",
- "minLength": 3,
- "maxLength": 15,
- "metadata": {
- "description": "Solution Name"
- }
- },
- "solutionLocation": {
- "type": "string",
- "metadata": {
- "description": "Solution Location"
- }
- },
- "miName": {
- "type": "string",
- "metadata": {
- "description": "Name"
- }
- }
- },
- "resources": [
- {
- "type": "Microsoft.ManagedIdentity/userAssignedIdentities",
- "apiVersion": "2023-01-31",
- "name": "[parameters('miName')]",
- "location": "[parameters('solutionLocation')]",
- "tags": {
- "app": "[parameters('solutionName')]",
- "location": "[parameters('solutionLocation')]"
- }
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "name": "[guid(resourceGroup().id, resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'))]",
- "properties": {
- "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').principalId]",
- "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
- "principalType": "ServicePrincipal"
- },
- "dependsOn": [
- "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]"
- ]
- }
- ],
- "outputs": {
- "managedIdentityOutput": {
- "type": "object",
- "value": {
- "id": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]",
- "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31').principalId]",
- "resourceId": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]",
- "location": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName')), '2023-01-31', 'full').location]",
- "name": "[parameters('miName')]"
- }
- },
- "managedIdentityId": {
- "type": "string",
- "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('miName'))]"
- }
- }
- }
- }
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "deploy_keyvault",
- "resourceGroup": "[resourceGroup().name]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "keyvaultName": {
- "value": "[format('{0}{1}', variables('abbrs').security.keyVault, variables('ResourcePrefix'))]"
- },
- "solutionName": {
- "value": "[variables('ResourcePrefix')]"
- },
- "solutionLocation": {
- "value": "[parameters('solutionLocation')]"
- },
- "managedIdentityObjectId": {
- "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.35.1.17967",
- "templateHash": "14711167186840027914"
- }
- },
- "parameters": {
- "solutionName": {
- "type": "string",
- "minLength": 3,
- "maxLength": 15,
- "metadata": {
- "description": "Solution Name"
- }
- },
- "solutionLocation": {
- "type": "string"
- },
- "managedIdentityObjectId": {
- "type": "string"
- },
- "keyvaultName": {
- "type": "string"
- }
- },
- "resources": [
- {
- "type": "Microsoft.KeyVault/vaults",
- "apiVersion": "2022-07-01",
- "name": "[parameters('keyvaultName')]",
- "location": "[parameters('solutionLocation')]",
- "properties": {
- "createMode": "default",
- "accessPolicies": [
- {
- "objectId": "[parameters('managedIdentityObjectId')]",
- "permissions": {
- "certificates": [
- "all"
- ],
- "keys": [
- "all"
- ],
- "secrets": [
- "all"
- ],
- "storage": [
- "all"
- ]
- },
- "tenantId": "[subscription().tenantId]"
- }
- ],
- "enabledForDeployment": true,
- "enabledForDiskEncryption": true,
- "enabledForTemplateDeployment": true,
- "enableRbacAuthorization": true,
- "publicNetworkAccess": "enabled",
- "sku": {
- "family": "A",
- "name": "standard"
- },
- "softDeleteRetentionInDays": 7,
- "tenantId": "[subscription().tenantId]"
- }
- },
- {
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "name": "[guid(resourceGroup().id, parameters('managedIdentityObjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483'))]",
- "properties": {
- "principalId": "[parameters('managedIdentityObjectId')]",
- "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]",
- "principalType": "ServicePrincipal"
- }
- }
- ],
- "outputs": {
- "keyvaultName": {
- "type": "string",
- "value": "[parameters('keyvaultName')]"
- },
- "keyvaultId": {
- "type": "string",
- "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName'))]"
- }
- }
- }
- },
- "dependsOn": [
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]"
- ]
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "deploy_ai_foundry",
- "resourceGroup": "[resourceGroup().name]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "solutionName": {
- "value": "[variables('ResourcePrefix')]"
- },
- "solutionLocation": {
- "value": "[parameters('AzureAiServiceLocation')]"
- },
- "aiFoundryName": {
- "value": "[variables('aiFoundryName')]"
- },
- "keyVaultName": {
- "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault'), '2022-09-01').outputs.keyvaultName.value]"
- },
- "gptModelName": {
- "value": "[parameters('llmModel')]"
- },
- "gptModelVersion": {
- "value": "[parameters('gptModelVersion')]"
- },
- "managedIdentityObjectId": {
- "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.objectId]"
- },
- "aiServicesEndpoint": {
- "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').endpoint]"
- },
- "aiServicesKey": {
- "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName')), '2025-04-01-preview').key1]"
- },
- "aiServicesId": {
- "value": "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]"
- },
- "existingLogAnalyticsWorkspaceId": {
- "value": "[parameters('existingLogAnalyticsWorkspaceId')]"
- },
- "aureaiFoundryEndpoint": {
- "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview').endpoints['AI Foundry API']]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.35.1.17967",
- "templateHash": "12967265180986066679"
- }
- },
- "parameters": {
- "solutionName": {
- "type": "string",
- "minLength": 3,
- "maxLength": 15,
- "metadata": {
- "description": "Solution Name"
- }
- },
- "solutionLocation": {
- "type": "string"
- },
- "keyVaultName": {
- "type": "string"
- },
- "gptModelName": {
- "type": "string"
- },
- "gptModelVersion": {
- "type": "string"
- },
- "managedIdentityObjectId": {
- "type": "string"
- },
- "aiServicesEndpoint": {
- "type": "string"
- },
- "aiServicesKey": {
- "type": "string"
- },
- "aiServicesId": {
- "type": "string"
- },
- "aureaiFoundryEndpoint": {
- "type": "string"
- },
- "aiFoundryName": {
- "type": "string"
- },
- "existingLogAnalyticsWorkspaceId": {
- "type": "string",
- "defaultValue": ""
- }
- },
- "variables": {
- "$fxv#0": {
- "ai": {
- "aiSearch": "srch-",
- "aiServices": "aisa-",
- "aiFoundry": "aif-",
- "aiFoundryProject": "aifp-",
- "aiVideoIndexer": "avi-",
- "machineLearningWorkspace": "mlw-",
- "openAIService": "oai-",
- "botService": "bot-",
- "computerVision": "cv-",
- "contentModerator": "cm-",
- "contentSafety": "cs-",
- "customVisionPrediction": "cstv-",
- "customVisionTraining": "cstvt-",
- "documentIntelligence": "di-",
- "faceApi": "face-",
- "healthInsights": "hi-",
- "immersiveReader": "ir-",
- "languageService": "lang-",
- "speechService": "spch-",
- "translator": "trsl-",
- "aiHub": "aih-",
- "aiHubProject": "aihp-"
- },
- "analytics": {
- "analysisServicesServer": "as",
- "databricksWorkspace": "dbw-",
- "dataExplorerCluster": "dec",
- "dataExplorerClusterDatabase": "dedb",
- "dataFactory": "adf-",
- "digitalTwin": "dt-",
- "streamAnalytics": "asa-",
- "synapseAnalyticsPrivateLinkHub": "synplh-",
- "synapseAnalyticsSQLDedicatedPool": "syndp",
- "synapseAnalyticsSparkPool": "synsp",
- "synapseAnalyticsWorkspaces": "synw",
- "dataLakeStoreAccount": "dls",
- "dataLakeAnalyticsAccount": "dla",
- "eventHubsNamespace": "evhns-",
- "eventHub": "evh-",
- "eventGridDomain": "evgd-",
- "eventGridSubscriptions": "evgs-",
- "eventGridTopic": "evgt-",
- "eventGridSystemTopic": "egst-",
- "hdInsightHadoopCluster": "hadoop-",
- "hdInsightHBaseCluster": "hbase-",
- "hdInsightKafkaCluster": "kafka-",
- "hdInsightSparkCluster": "spark-",
- "hdInsightStormCluster": "storm-",
- "hdInsightMLServicesCluster": "mls-",
- "iotHub": "iot-",
- "provisioningServices": "provs-",
- "provisioningServicesCertificate": "pcert-",
- "powerBIEmbedded": "pbi-",
- "timeSeriesInsightsEnvironment": "tsi-"
- },
- "compute": {
- "appServiceEnvironment": "ase-",
- "appServicePlan": "asp-",
- "loadTesting": "lt-",
- "availabilitySet": "avail-",
- "arcEnabledServer": "arcs-",
- "arcEnabledKubernetesCluster": "arck",
- "batchAccounts": "ba-",
- "cloudService": "cld-",
- "communicationServices": "acs-",
- "diskEncryptionSet": "des",
- "functionApp": "func-",
- "gallery": "gal",
- "hostingEnvironment": "host-",
- "imageTemplate": "it-",
- "managedDiskOS": "osdisk",
- "managedDiskData": "disk",
- "notificationHubs": "ntf-",
- "notificationHubsNamespace": "ntfns-",
- "proximityPlacementGroup": "ppg-",
- "restorePointCollection": "rpc-",
- "snapshot": "snap-",
- "staticWebApp": "stapp-",
- "virtualMachine": "vm",
- "virtualMachineScaleSet": "vmss-",
- "virtualMachineMaintenanceConfiguration": "mc-",
- "virtualMachineStorageAccount": "stvm",
- "webApp": "app-"
- },
- "containers": {
- "aksCluster": "aks-",
- "aksSystemNodePool": "npsystem-",
- "aksUserNodePool": "np-",
- "containerApp": "ca-",
- "containerAppsEnvironment": "cae-",
- "containerRegistry": "cr",
- "containerInstance": "ci",
- "serviceFabricCluster": "sf-",
- "serviceFabricManagedCluster": "sfmc-"
- },
- "databases": {
- "cosmosDBDatabase": "cosmos-",
- "cosmosDBApacheCassandra": "coscas-",
- "cosmosDBMongoDB": "cosmon-",
- "cosmosDBNoSQL": "cosno-",
- "cosmosDBTable": "costab-",
- "cosmosDBGremlin": "cosgrm-",
- "cosmosDBPostgreSQL": "cospos-",
- "cacheForRedis": "redis-",
- "sqlDatabaseServer": "sql-",
- "sqlDatabase": "sqldb-",
- "sqlElasticJobAgent": "sqlja-",
- "sqlElasticPool": "sqlep-",
- "mariaDBServer": "maria-",
- "mariaDBDatabase": "mariadb-",
- "mySQLDatabase": "mysql-",
- "postgreSQLDatabase": "psql-",
- "sqlServerStretchDatabase": "sqlstrdb-",
- "sqlManagedInstance": "sqlmi-"
- },
- "developerTools": {
- "appConfigurationStore": "appcs-",
- "mapsAccount": "map-",
- "signalR": "sigr",
- "webPubSub": "wps-"
- },
- "devOps": {
- "managedGrafana": "amg-"
- },
- "integration": {
- "apiManagementService": "apim-",
- "integrationAccount": "ia-",
- "logicApp": "logic-",
- "serviceBusNamespace": "sbns-",
- "serviceBusQueue": "sbq-",
- "serviceBusTopic": "sbt-",
- "serviceBusTopicSubscription": "sbts-"
- },
- "managementGovernance": {
- "automationAccount": "aa-",
- "applicationInsights": "appi-",
- "monitorActionGroup": "ag-",
- "monitorDataCollectionRules": "dcr-",
- "monitorAlertProcessingRule": "apr-",
- "blueprint": "bp-",
- "blueprintAssignment": "bpa-",
- "dataCollectionEndpoint": "dce-",
- "logAnalyticsWorkspace": "log-",
- "logAnalyticsQueryPacks": "pack-",
- "managementGroup": "mg-",
- "purviewInstance": "pview-",
- "resourceGroup": "rg-",
- "templateSpecsName": "ts-"
- },
- "migration": {
- "migrateProject": "migr-",
- "databaseMigrationService": "dms-",
- "recoveryServicesVault": "rsv-"
- },
- "networking": {
- "applicationGateway": "agw-",
- "applicationSecurityGroup": "asg-",
- "cdnProfile": "cdnp-",
- "cdnEndpoint": "cdne-",
- "connections": "con-",
- "dnsForwardingRuleset": "dnsfrs-",
- "dnsPrivateResolver": "dnspr-",
- "dnsPrivateResolverInboundEndpoint": "in-",
- "dnsPrivateResolverOutboundEndpoint": "out-",
- "firewall": "afw-",
- "firewallPolicy": "afwp-",
- "expressRouteCircuit": "erc-",
- "expressRouteGateway": "ergw-",
- "frontDoorProfile": "afd-",
- "frontDoorEndpoint": "fde-",
- "frontDoorFirewallPolicy": "fdfp-",
- "ipGroups": "ipg-",
- "loadBalancerInternal": "lbi-",
- "loadBalancerExternal": "lbe-",
- "loadBalancerRule": "rule-",
- "localNetworkGateway": "lgw-",
- "natGateway": "ng-",
- "networkInterface": "nic-",
- "networkSecurityGroup": "nsg-",
- "networkSecurityGroupSecurityRules": "nsgsr-",
- "networkWatcher": "nw-",
- "privateLink": "pl-",
- "privateEndpoint": "pep-",
- "publicIPAddress": "pip-",
- "publicIPAddressPrefix": "ippre-",
- "routeFilter": "rf-",
- "routeServer": "rtserv-",
- "routeTable": "rt-",
- "serviceEndpointPolicy": "se-",
- "trafficManagerProfile": "traf-",
- "userDefinedRoute": "udr-",
- "virtualNetwork": "vnet-",
- "virtualNetworkGateway": "vgw-",
- "virtualNetworkManager": "vnm-",
- "virtualNetworkPeering": "peer-",
- "virtualNetworkSubnet": "snet-",
- "virtualWAN": "vwan-",
- "virtualWANHub": "vhub-"
- },
- "security": {
- "bastion": "bas-",
- "keyVault": "kv-",
- "keyVaultManagedHSM": "kvmhsm-",
- "managedIdentity": "id-",
- "sshKey": "sshkey-",
- "vpnGateway": "vpng-",
- "vpnConnection": "vcn-",
- "vpnSite": "vst-",
- "webApplicationFirewallPolicy": "waf",
- "webApplicationFirewallPolicyRuleGroup": "wafrg"
- },
- "storage": {
- "storSimple": "ssimp",
- "backupVault": "bvault-",
- "backupVaultPolicy": "bkpol-",
- "fileShare": "share-",
- "storageAccount": "st",
- "storageSyncService": "sss-"
- },
- "virtualDesktop": {
- "labServicesPlan": "lp-",
- "virtualDesktopHostPool": "vdpool-",
- "virtualDesktopApplicationGroup": "vdag-",
- "virtualDesktopWorkspace": "vdws-",
- "virtualDesktopScalingPlan": "vdscaling-"
- }
- },
- "useExisting": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]",
- "existingLawSubscription": "[if(variables('useExisting'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[2], '')]",
- "existingLawResourceGroup": "[if(variables('useExisting'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[4], '')]",
- "existingLawName": "[if(variables('useExisting'), split(parameters('existingLogAnalyticsWorkspaceId'), '/')[8], '')]",
- "abbrs": "[variables('$fxv#0')]",
- "storageName": "[format('{0}{1}', variables('abbrs').storage.storageAccount, parameters('solutionName'))]",
- "storageSkuName": "Standard_LRS",
- "workspaceName": "[format('{0}{1}', variables('abbrs').managementGovernance.logAnalyticsWorkspace, parameters('solutionName'))]",
- "keyvaultName": "[format('{0}{1}', variables('abbrs').security.keyVault, parameters('solutionName'))]",
- "location": "[parameters('solutionLocation')]",
- "aiSearchName": "[format('{0}-search', parameters('solutionName'))]",
- "applicationInsightsName": "[format('{0}-appi', parameters('solutionName'))]",
- "storageNameCleaned": "[replace(replace(replace(replace(format('{0}cast', variables('storageName')), '-', ''), '_', ''), '.', ''), '/', '')]"
- },
- "resources": [
- {
- "condition": "[not(variables('useExisting'))]",
- "type": "Microsoft.OperationalInsights/workspaces",
- "apiVersion": "2023-09-01",
- "name": "[variables('workspaceName')]",
- "location": "[variables('location')]",
- "tags": {},
- "properties": {
- "retentionInDays": 30,
- "sku": {
- "name": "PerGB2018"
- }
- }
- },
- {
- "type": "Microsoft.Insights/components",
- "apiVersion": "2020-02-02",
- "name": "[variables('applicationInsightsName')]",
- "location": "[variables('location')]",
- "kind": "web",
- "properties": {
- "Application_Type": "web",
- "publicNetworkAccessForIngestion": "Enabled",
- "publicNetworkAccessForQuery": "Enabled",
- "WorkspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]"
- },
- "dependsOn": [
- "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]"
- ]
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'TENANT-ID')]",
- "properties": {
- "value": "[subscription().tenantId]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-INFERENCE-ENDPOINT')]",
- "properties": {
- "value": ""
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-INFERENCE-KEY')]",
- "properties": {
- "value": ""
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-KEY')]",
- "properties": {
- "value": "[parameters('aiServicesKey')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPEN-AI-DEPLOYMENT-MODEL')]",
- "properties": {
- "value": "[parameters('gptModelName')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-PREVIEW-API-VERSION')]",
- "properties": {
- "value": "[parameters('gptModelVersion')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-ENDPOINT')]",
- "properties": {
- "value": "[parameters('aiServicesEndpoint')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AI-PROJECT-ENDPOINT')]",
- "properties": {
- "value": "[parameters('aureaiFoundryEndpoint')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-CU-VERSION')]",
- "properties": {
- "value": "?api-version=2024-12-01-preview"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SEARCH-INDEX')]",
- "properties": {
- "value": "transcripts_index"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-ENDPOINT')]",
- "properties": {
- "value": "[parameters('aiServicesEndpoint')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-KEY')]",
- "properties": {
- "value": "[parameters('aiServicesKey')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-NAME')]",
- "properties": {
- "value": "[parameters('aiFoundryName')]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SUBSCRIPTION-ID')]",
- "properties": {
- "value": "[subscription().subscriptionId]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-RESOURCE-GROUP')]",
- "properties": {
- "value": "[resourceGroup().name]"
- }
- },
- {
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2021-11-01-preview",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-LOCATION')]",
- "properties": {
- "value": "[parameters('solutionLocation')]"
- }
- }
- ],
- "outputs": {
- "keyvaultName": {
- "type": "string",
- "value": "[variables('keyvaultName')]"
- },
- "keyvaultId": {
- "type": "string",
- "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
- },
- "aiSearchName": {
- "type": "string",
- "value": "[variables('aiSearchName')]"
- },
- "storageAccountName": {
- "type": "string",
- "value": "[variables('storageNameCleaned')]"
- },
- "logAnalyticsId": {
- "type": "string",
- "value": "[if(variables('useExisting'), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('existingLawSubscription'), variables('existingLawResourceGroup')), 'Microsoft.OperationalInsights/workspaces', variables('existingLawName')), resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')))]"
- },
- "applicationInsightsConnectionString": {
- "type": "string",
- "value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightsName')), '2020-02-02').ConnectionString]"
- }
- }
- }
- },
- "dependsOn": [
- "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName'))]",
- "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiFoundryName'))]",
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_keyvault')]",
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]"
- ]
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[toLower(format('{0}conAppsEnv', variables('ResourcePrefix')))]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "logAnalyticsWorkspaceResourceId": {
- "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry'), '2022-09-01').outputs.logAnalyticsId.value]"
- },
- "name": {
- "value": "[toLower(format('{0}manenv', variables('ResourcePrefix')))]"
- },
- "location": {
- "value": "[parameters('solutionLocation')]"
- },
- "zoneRedundant": {
- "value": false
- },
- "managedIdentities": {
- "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01')]"
- }
- },
- "template": {
- "$schema": "https://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": "9269440843859343895"
- },
- "name": "App ManagedEnvironments",
- "description": "This module deploys an App Managed Environment (also known as a Container App Environment)."
- },
- "definitions": {
- "managedIdentitiesType": {
- "type": "object",
- "properties": {
- "systemAssigned": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Enables system assigned managed identity on the resource."
- }
- },
- "userAssignedResourceIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The resource ID(s) to assign to the resource."
- }
- }
- },
- "nullable": true
- },
- "lockType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the name of lock."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "CanNotDelete",
- "None",
- "ReadOnly"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the type of lock."
- }
- }
- },
- "nullable": true
- },
- "roleAssignmentType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
- }
- },
- "roleDefinitionIdOrName": {
- "type": "string",
- "metadata": {
- "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
- }
- },
- "principalId": {
- "type": "string",
- "metadata": {
- "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
- }
- },
- "principalType": {
- "type": "string",
- "allowedValues": [
- "Device",
- "ForeignGroup",
- "Group",
- "ServicePrincipal",
- "User"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. The principal type of the assigned principal ID."
- }
- },
- "description": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The description of the role assignment."
- }
- },
- "condition": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
- }
- },
- "conditionVersion": {
- "type": "string",
- "allowedValues": [
- "2.0"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Version of the condition."
- }
- },
- "delegatedManagedIdentityResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The Resource Id of the delegated managed identity resource."
- }
- }
- }
- },
- "nullable": true
- },
- "certificateKeyVaultPropertiesType": {
- "type": "object",
- "properties": {
- "identityResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. The resource ID of the identity. This is the identity that will be used to access the key vault."
- }
- },
- "keyVaultUrl": {
- "type": "string",
- "metadata": {
- "description": "Required. A key vault URL referencing the wildcard certificate that will be used for the custom domain."
- }
- }
- },
- "nullable": true
- },
- "storageType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "accessMode": {
- "type": "string",
- "allowedValues": [
- "ReadOnly",
- "ReadWrite"
- ],
- "metadata": {
- "description": "Required. Access mode for storage: \"ReadOnly\" or \"ReadWrite\"."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "NFS",
- "SMB"
- ],
- "metadata": {
- "description": "Required. Type of storage: \"SMB\" or \"NFS\"."
- }
- },
- "storageAccountName": {
- "type": "string",
- "metadata": {
- "description": "Required. Storage account name."
- }
- },
- "shareName": {
- "type": "string",
- "metadata": {
- "description": "Required. File share name."
- }
- }
- }
- },
- "nullable": true
- }
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the Container Apps Managed Environment."
- }
- },
- "logAnalyticsWorkspaceResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. Existing Log Analytics Workspace resource ID. Note: This value is not required as per the resource type. However, not providing it currently causes an issue that is tracked [here](https://github.com/Azure/bicep/issues/9990)."
- }
- },
- "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."
- }
- },
- "managedIdentities": {
- "$ref": "#/definitions/managedIdentitiesType",
- "metadata": {
- "description": "Optional. The managed identity definition for this resource."
- }
- },
- "roleAssignments": {
- "$ref": "#/definitions/roleAssignmentType",
- "metadata": {
- "description": "Optional. Array of role assignments to create."
- }
- },
- "logsDestination": {
- "type": "string",
- "defaultValue": "log-analytics",
- "metadata": {
- "description": "Optional. Logs destination."
- }
- },
- "enableTelemetry": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Enable/Disable usage telemetry for module."
- }
- },
- "appInsightsConnectionString": {
- "type": "securestring",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Application Insights connection string."
- }
- },
- "daprAIConnectionString": {
- "type": "securestring",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Application Insights connection string used by Dapr to export Service to Service communication telemetry."
- }
- },
- "daprAIInstrumentationKey": {
- "type": "securestring",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Azure Monitor instrumentation key used by Dapr to export Service to Service communication telemetry."
- }
- },
- "dockerBridgeCidr": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Conditional. CIDR notation IP range assigned to the Docker bridge, network. It must not overlap with any other provided IP ranges and can only be used when the environment is deployed into a virtual network. If not provided, it will be set with a default value by the platform. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "infrastructureSubnetId": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Conditional. Resource ID of a subnet for infrastructure components. This is used to deploy the environment into a virtual network. Must not overlap with any other provided IP ranges. Required if \"internal\" is set to true. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "internal": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Conditional. Boolean indicating the environment only has an internal load balancer. These environments do not have a public static IP resource. If set to true, then \"infrastructureSubnetId\" must be provided. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "platformReservedCidr": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Conditional. IP range in CIDR notation that can be reserved for environment infrastructure IP addresses. It must not overlap with any other provided IP ranges and can only be used when the environment is deployed into a virtual network. If not provided, it will be set with a default value by the platform. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "platformReservedDnsIP": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Conditional. An IP address from the IP range defined by \"platformReservedCidr\" that will be reserved for the internal DNS server. It must not be the first address in the range and can only be used when the environment is deployed into a virtual network. If not provided, it will be set with a default value by the platform. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "peerTrafficEncryption": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Whether or not to encrypt peer traffic."
- }
- },
- "zoneRedundant": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Whether or not this Managed Environment is zone-redundant."
- }
- },
- "certificatePassword": {
- "type": "securestring",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Password of the certificate used by the custom domain."
- }
- },
- "certificateValue": {
- "type": "securestring",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Certificate to use for the custom domain. PFX or PEM."
- }
- },
- "certificateKeyVaultProperties": {
- "$ref": "#/definitions/certificateKeyVaultPropertiesType",
- "metadata": {
- "description": "Optional. A key vault reference to the certificate to use for the custom domain."
- }
- },
- "dnsSuffix": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. DNS suffix for the environment domain."
- }
- },
- "lock": {
- "$ref": "#/definitions/lockType",
- "metadata": {
- "description": "Optional. The lock settings of the service."
- }
- },
- "openTelemetryConfiguration": {
- "type": "object",
- "defaultValue": {},
- "metadata": {
- "description": "Optional. Open Telemetry configuration."
- }
- },
- "workloadProfiles": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Conditional. Workload profiles configured for the Managed Environment. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "infrastructureResourceGroupName": {
- "type": "string",
- "defaultValue": "[take(format('ME_{0}', parameters('name')), 63)]",
- "metadata": {
- "description": "Conditional. Name of the infrastructure resource group. If not provided, it will be set with a default value. Required if zoneRedundant is set to true to make the resource WAF compliant."
- }
- },
- "storages": {
- "$ref": "#/definitions/storageType",
- "metadata": {
- "description": "Optional. The list of storages to mount on the environment."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "formattedRoleAssignments",
- "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
- "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
- }
- ],
- "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
- "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
- "builtInRoleNames": {
- "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
- "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
- "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
- "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": {
- "managedEnvironment::storage": {
- "copy": {
- "name": "managedEnvironment::storage",
- "count": "[length(coalesce(parameters('storages'), createArray()))]"
- },
- "type": "Microsoft.App/managedEnvironments/storages",
- "apiVersion": "2024-02-02-preview",
- "name": "[format('{0}/{1}', parameters('name'), coalesce(parameters('storages'), createArray())[copyIndex()].shareName)]",
- "properties": {
- "nfsAzureFile": "[if(equals(coalesce(parameters('storages'), createArray())[copyIndex()].kind, 'NFS'), createObject('accessMode', coalesce(parameters('storages'), createArray())[copyIndex()].accessMode, 'server', format('{0}.file.{1}', coalesce(parameters('storages'), createArray())[copyIndex()].storageAccountName, environment().suffixes.storage), 'shareName', format('/{0}/{1}', coalesce(parameters('storages'), createArray())[copyIndex()].storageAccountName, coalesce(parameters('storages'), createArray())[copyIndex()].shareName)), null())]",
- "azureFile": "[if(equals(coalesce(parameters('storages'), createArray())[copyIndex()].kind, 'SMB'), createObject('accessMode', coalesce(parameters('storages'), createArray())[copyIndex()].accessMode, 'accountName', coalesce(parameters('storages'), createArray())[copyIndex()].storageAccountName, 'accountKey', listkeys(resourceId('Microsoft.Storage/storageAccounts', coalesce(parameters('storages'), createArray())[copyIndex()].storageAccountName), '2023-01-01').keys[0].value, 'shareName', coalesce(parameters('storages'), createArray())[copyIndex()].shareName), null())]"
- },
- "dependsOn": [
- "managedEnvironment"
- ]
- },
- "avmTelemetry": {
- "condition": "[parameters('enableTelemetry')]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2024-03-01",
- "name": "[format('46d3xbcp.res.app-managedenvironment.{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"
- }
- }
- }
- }
- },
- "logAnalyticsWorkspace": {
- "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]",
- "existing": true,
- "type": "Microsoft.OperationalInsights/workspaces",
- "apiVersion": "2023-09-01",
- "subscriptionId": "[split(parameters('logAnalyticsWorkspaceResourceId'), '/')[2]]",
- "resourceGroup": "[split(parameters('logAnalyticsWorkspaceResourceId'), '/')[4]]",
- "name": "[last(split(parameters('logAnalyticsWorkspaceResourceId'), '/'))]"
- },
- "managedEnvironment": {
- "type": "Microsoft.App/managedEnvironments",
- "apiVersion": "2024-02-02-preview",
- "name": "[parameters('name')]",
- "location": "[parameters('location')]",
- "tags": "[parameters('tags')]",
- "identity": "[variables('identity')]",
- "properties": {
- "appInsightsConfiguration": {
- "connectionString": "[parameters('appInsightsConnectionString')]"
- },
- "appLogsConfiguration": {
- "destination": "[parameters('logsDestination')]",
- "logAnalyticsConfiguration": {
- "customerId": "[reference('logAnalyticsWorkspace').customerId]",
- "sharedKey": "[listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[2], split(parameters('logAnalyticsWorkspaceResourceId'), '/')[4]), 'Microsoft.OperationalInsights/workspaces', last(split(parameters('logAnalyticsWorkspaceResourceId'), '/'))), '2023-09-01').primarySharedKey]"
- }
- },
- "daprAIConnectionString": "[parameters('daprAIConnectionString')]",
- "daprAIInstrumentationKey": "[parameters('daprAIInstrumentationKey')]",
- "customDomainConfiguration": {
- "certificatePassword": "[parameters('certificatePassword')]",
- "certificateValue": "[if(not(empty(parameters('certificateValue'))), parameters('certificateValue'), null())]",
- "dnsSuffix": "[parameters('dnsSuffix')]",
- "certificateKeyVaultProperties": "[if(not(empty(parameters('certificateKeyVaultProperties'))), createObject('identity', parameters('certificateKeyVaultProperties').identityResourceId, 'keyVaultUrl', parameters('certificateKeyVaultProperties').keyVaultUrl), null())]"
- },
- "openTelemetryConfiguration": "[if(not(empty(parameters('openTelemetryConfiguration'))), parameters('openTelemetryConfiguration'), null())]",
- "peerTrafficConfiguration": {
- "encryption": {
- "enabled": "[parameters('peerTrafficEncryption')]"
- }
- },
- "vnetConfiguration": {
- "internal": "[parameters('internal')]",
- "infrastructureSubnetId": "[if(not(empty(parameters('infrastructureSubnetId'))), parameters('infrastructureSubnetId'), null())]",
- "dockerBridgeCidr": "[if(not(empty(parameters('infrastructureSubnetId'))), parameters('dockerBridgeCidr'), null())]",
- "platformReservedCidr": "[if(and(empty(parameters('workloadProfiles')), not(empty(parameters('infrastructureSubnetId')))), parameters('platformReservedCidr'), null())]",
- "platformReservedDnsIP": "[if(and(empty(parameters('workloadProfiles')), not(empty(parameters('infrastructureSubnetId')))), parameters('platformReservedDnsIP'), null())]"
- },
- "workloadProfiles": "[if(not(empty(parameters('workloadProfiles'))), parameters('workloadProfiles'), null())]",
- "zoneRedundant": "[parameters('zoneRedundant')]",
- "infrastructureResourceGroup": "[parameters('infrastructureResourceGroupName')]"
- },
- "dependsOn": [
- "logAnalyticsWorkspace"
- ]
- },
- "managedEnvironment_roleAssignments": {
- "copy": {
- "name": "managedEnvironment_roleAssignments",
- "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
- },
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.App/managedEnvironments/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.App/managedEnvironments', 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": [
- "managedEnvironment"
- ]
- },
- "managedEnvironment_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.App/managedEnvironments/{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": [
- "managedEnvironment"
- ]
- }
- },
- "outputs": {
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the Managed Environment was deployed into."
- },
- "value": "[resourceGroup().name]"
- },
- "location": {
- "type": "string",
- "metadata": {
- "description": "The location the resource was deployed into."
- },
- "value": "[reference('managedEnvironment', '2024-02-02-preview', 'full').location]"
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the Managed Environment."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the Managed Environment."
- },
- "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]"
- },
- "systemAssignedMIPrincipalId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "The principal ID of the system assigned identity."
- },
- "value": "[tryGet(tryGet(reference('managedEnvironment', '2024-02-02-preview', 'full'), 'identity'), 'principalId')]"
- },
- "defaultDomain": {
- "type": "string",
- "metadata": {
- "description": "The Default domain of the Managed Environment."
- },
- "value": "[reference('managedEnvironment').defaultDomain]"
- },
- "staticIp": {
- "type": "string",
- "metadata": {
- "description": "The IP address of the Managed Environment."
- },
- "value": "[reference('managedEnvironment').staticIp]"
- },
- "domainVerificationId": {
- "type": "string",
- "metadata": {
- "description": "The domain verification id for custom domains."
- },
- "value": "[reference('managedEnvironment').customDomainConfiguration.customDomainVerificationId]"
- }
- }
- }
- },
- "dependsOn": [
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_ai_foundry')]",
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]"
- ]
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))]"
- },
- "enableAnalyticalStorage": {
- "value": true
- },
- "location": {
- "value": "[parameters('solutionLocation')]"
- },
- "managedIdentities": {
- "value": {
- "systemAssigned": true,
- "userAssignedResourceIds": [
- "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.resourceId]"
- ]
- }
- },
- "networkRestrictions": {
- "value": {
- "networkAclBypass": "AzureServices",
- "publicNetworkAccess": "Enabled",
- "ipRules": [],
- "virtualNetworkRules": []
- }
- },
- "disableKeyBasedMetadataWriteAccess": {
- "value": false
- },
- "locations": {
- "value": [
- {
- "failoverPriority": 0,
- "isZoneRedundant": false,
- "locationName": "[parameters('solutionLocation')]"
- }
- ]
- },
- "sqlDatabases": {
- "value": [
- {
- "containers": [
- {
- "indexingPolicy": {
- "automatic": true
- },
- "name": "[variables('cosmosdbBatchContainer')]",
- "paths": [
- "/batch_id"
- ]
- },
- {
- "indexingPolicy": {
- "automatic": true
- },
- "name": "[variables('cosmosdbFileContainer')]",
- "paths": [
- "/file_id"
- ]
- },
- {
- "indexingPolicy": {
- "automatic": true
- },
- "name": "[variables('cosmosdbLogContainer')]",
- "paths": [
- "/log_id"
- ]
- }
- ],
- "name": "[variables('cosmosdbDatabase')]"
- }
- ]
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "1366944588516308546"
- },
- "name": "DocumentDB Database Accounts",
- "description": "This module deploys a DocumentDB Database Account.",
- "owner": "Azure/module-maintainers"
- },
- "definitions": {
- "managedIdentitiesType": {
- "type": "object",
- "properties": {
- "systemAssigned": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Enables system assigned managed identity on the resource."
- }
- },
- "userAssignedResourceIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The resource ID(s) to assign to the resource."
- }
- }
- },
- "nullable": true
- },
- "lockType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the name of lock."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "CanNotDelete",
- "None",
- "ReadOnly"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the type of lock."
- }
- }
- },
- "nullable": true
- },
- "roleAssignmentType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
- }
- },
- "roleDefinitionIdOrName": {
- "type": "string",
- "metadata": {
- "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
- }
- },
- "principalId": {
- "type": "string",
- "metadata": {
- "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
- }
- },
- "principalType": {
- "type": "string",
- "allowedValues": [
- "Device",
- "ForeignGroup",
- "Group",
- "ServicePrincipal",
- "User"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. The principal type of the assigned principal ID."
- }
- },
- "description": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The description of the role assignment."
- }
- },
- "condition": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
- }
- },
- "conditionVersion": {
- "type": "string",
- "allowedValues": [
- "2.0"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Version of the condition."
- }
- },
- "delegatedManagedIdentityResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The Resource Id of the delegated managed identity resource."
- }
- }
- }
- },
- "nullable": true
- },
- "privateEndpointType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of the private endpoint."
- }
- },
- "location": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The location to deploy the private endpoint to."
- }
- },
- "privateLinkServiceConnectionName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of the private link connection to create."
- }
- },
- "service": {
- "type": "string",
- "metadata": {
- "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\"."
- }
- },
- "subnetResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
- }
- },
- "privateDnsZoneGroup": {
- "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."
- }
- }
- },
- "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": {
- "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."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Custom DNS configurations."
- }
- },
- "ipConfigurations": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the resource that is unique within a resource group."
- }
- },
- "properties": {
- "type": "object",
- "properties": {
- "groupId": {
- "type": "string",
- "metadata": {
- "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to."
- }
- },
- "memberName": {
- "type": "string",
- "metadata": {
- "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to."
- }
- },
- "privateIPAddress": {
- "type": "string",
- "metadata": {
- "description": "Required. A private ip address obtained from the private endpoint's subnet."
- }
- }
- },
- "metadata": {
- "description": "Required. Properties of private endpoint IP configurations."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
- }
- },
- "applicationSecurityGroupResourceIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
- }
- },
- "customNetworkInterfaceName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The custom name of the network interface attached to the private endpoint."
- }
- },
- "lock": {
- "$ref": "#/definitions/lockType",
- "metadata": {
- "description": "Optional. Specify the type of lock."
- }
- },
- "roleAssignments": {
- "$ref": "#/definitions/roleAssignmentType",
- "metadata": {
- "description": "Optional. Array of role assignments to create."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
- }
- },
- "enableTelemetry": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Enable/Disable usage telemetry for module."
- }
- },
- "resourceGroupName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify if you want to deploy the Private Endpoint into a different resource group than the main resource."
- }
- }
- }
- },
- "nullable": true
- },
- "diagnosticSettingType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of diagnostic setting."
- }
- },
- "logCategoriesAndGroups": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "category": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here."
- }
- },
- "categoryGroup": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs."
- }
- },
- "enabled": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Enable or disable the category explicitly. Default is `true`."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection."
- }
- },
- "metricCategories": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "category": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics."
- }
- },
- "enabled": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Enable or disable the category explicitly. Default is `true`."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection."
- }
- },
- "logAnalyticsDestinationType": {
- "type": "string",
- "allowedValues": [
- "AzureDiagnostics",
- "Dedicated"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type."
- }
- },
- "workspaceResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
- }
- },
- "storageAccountResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
- }
- },
- "eventHubAuthorizationRuleResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to."
- }
- },
- "eventHubName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub."
- }
- },
- "marketplacePartnerResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs."
- }
- }
- }
- },
- "nullable": true
- },
- "failoverLocationsType": {
- "type": "object",
- "properties": {
- "failoverPriority": {
- "type": "int",
- "metadata": {
- "description": "Required. The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists."
- }
- },
- "isZoneRedundant": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to true. Flag to indicate whether or not this region is an AvailabilityZone region."
- }
- },
- "locationName": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the region."
- }
- }
- }
- },
- "sqlRoleDefinitionsType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the SQL Role Definition."
- }
- },
- "dataAction": {
- "type": "array",
- "nullable": true,
- "metadata": {
- "description": "Optional. An array of data actions that are allowed."
- }
- },
- "roleName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. A user-friendly name for the Role Definition. Must be unique for the database account."
- }
- },
- "roleType": {
- "type": "string",
- "allowedValues": [
- "BuiltInRole",
- "CustomRole"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Indicates whether the Role Definition was built-in or user created."
- }
- }
- }
- },
- "nullable": true
- },
- "sqlDatabaseType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the SQL database ."
- }
- },
- "throughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to 400. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used."
- }
- },
- "autoscaleSettingsMaxThroughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled."
- }
- },
- "containers": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the container."
- }
- },
- "paths": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "minLength": 1,
- "maxLength": 3,
- "metadata": {
- "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1."
- }
- },
- "analyticalStorageTtl": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store."
- }
- },
- "autoscaleSettingsMaxThroughput": {
- "type": "int",
- "nullable": true,
- "maxValue": 1000000,
- "metadata": {
- "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled."
- }
- },
- "conflictResolutionPolicy": {
- "type": "object",
- "properties": {
- "conflictResolutionPath": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Conditional. The conflict resolution path in the case of LastWriterWins mode. Required if `mode` is set to 'LastWriterWins'."
- }
- },
- "conflictResolutionProcedure": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Conditional. The procedure to resolve conflicts in the case of custom mode. Required if `mode` is set to 'Custom'."
- }
- },
- "mode": {
- "type": "string",
- "allowedValues": [
- "Custom",
- "LastWriterWins"
- ],
- "metadata": {
- "description": "Required. Indicates the conflict resolution mode."
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions."
- }
- },
- "defaultTtl": {
- "type": "int",
- "nullable": true,
- "minValue": -1,
- "maxValue": 2147483647,
- "metadata": {
- "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default."
- }
- },
- "indexingPolicy": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Indexing policy of the container."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "Hash",
- "MultiHash"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning."
- }
- },
- "version": {
- "type": "int",
- "allowedValues": [
- 1,
- 2
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition."
- }
- },
- "throughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used."
- }
- },
- "uniqueKeyPolicyKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "paths": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "metadata": {
- "description": "Required. List of paths must be unique for each document in the Azure Cosmos DB service."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Array of containers to deploy in the SQL database."
- }
- }
- }
- },
- "secretsExportConfigurationType": {
- "type": "object",
- "properties": {
- "keyVaultResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. The resource ID of the key vault where to store the secrets of this module."
- }
- },
- "primaryWriteKeySecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary write key secret name to create."
- }
- },
- "primaryReadOnlyKeySecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary readonly key secret name to create."
- }
- },
- "primaryWriteConnectionStringSecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary write connection string secret name to create."
- }
- },
- "primaryReadonlyConnectionStringSecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary readonly connection string secret name to create."
- }
- },
- "secondaryWriteKeySecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary write key secret name to create."
- }
- },
- "secondaryReadonlyKeySecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary readonly key secret name to create."
- }
- },
- "secondaryWriteConnectionStringSecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary write connection string secret name to create."
- }
- },
- "secondaryReadonlyConnectionStringSecretName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The primary readonly connection string secret name to create."
- }
- }
- }
- },
- "secretsOutputType": {
- "type": "object",
- "properties": {},
- "additionalProperties": {
- "$ref": "#/definitions/secretSetType",
- "metadata": {
- "description": "An exported secret's references."
- }
- }
- },
- "networkRestrictionsType": {
- "type": "object",
- "properties": {
- "ipRules": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "metadata": {
- "description": "Required. A single IPv4 address or a single IPv4 address range in CIDR format. Provided IPs must be well-formatted and cannot be contained in one of the following ranges: 10.0.0.0/8, 100.64.0.0/10, 172.16.0.0/12, 192.168.0.0/16, since these are not enforceable by the IP address filter. Example of valid inputs: \"23.40.210.245\" or \"23.40.210.0/8\"."
- }
- },
- "networkAclBypass": {
- "type": "string",
- "allowedValues": [
- "AzureServices",
- "None"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to AzureServices. Specifies the network ACL bypass for Azure services."
- }
- },
- "publicNetworkAccess": {
- "type": "string",
- "allowedValues": [
- "Disabled",
- "Enabled"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Default to Enabled. Whether requests from Public Network are allowed."
- }
- },
- "virtualNetworkRules": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "subnetResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. Resource ID of a subnet."
- }
- }
- }
- },
- "metadata": {
- "description": "Required. List of Virtual Network ACL rules configured for the Cosmos DB account.."
- }
- }
- }
- },
- "secretSetType": {
- "type": "object",
- "properties": {
- "secretResourceId": {
- "type": "string",
- "metadata": {
- "description": "The resourceId of the exported secret."
- }
- },
- "secretUri": {
- "type": "string",
- "metadata": {
- "description": "The secret URI of the exported secret."
- }
- }
- },
- "metadata": {
- "__bicep_imported_from!": {
- "sourceTemplate": "modules/keyVaultExport.bicep"
- }
- }
- }
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the Database Account."
- }
- },
- "location": {
- "type": "string",
- "defaultValue": "[resourceGroup().location]",
- "metadata": {
- "description": "Optional. Default to current resource group scope location. Location for all resources."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the Database Account resource."
- }
- },
- "managedIdentities": {
- "$ref": "#/definitions/managedIdentitiesType",
- "metadata": {
- "description": "Optional. The managed identity definition for this resource."
- }
- },
- "databaseAccountOfferType": {
- "type": "string",
- "defaultValue": "Standard",
- "allowedValues": [
- "Standard"
- ],
- "metadata": {
- "description": "Optional. Default to Standard. The offer type for the Azure Cosmos DB database account."
- }
- },
- "locations": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/failoverLocationsType"
- },
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Default to the location where the account is deployed. Locations enabled for the Cosmos DB account."
- }
- },
- "defaultConsistencyLevel": {
- "type": "string",
- "defaultValue": "Session",
- "allowedValues": [
- "Eventual",
- "ConsistentPrefix",
- "Session",
- "BoundedStaleness",
- "Strong"
- ],
- "metadata": {
- "description": "Optional. Default to Session. The default consistency level of the Cosmos DB account."
- }
- },
- "disableLocalAuth": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Opt-out of local authentication and ensure only MSI and AAD can be used exclusively for authentication."
- }
- },
- "enableAnalyticalStorage": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Optional. Flag to indicate whether to enable storage analytics."
- }
- },
- "automaticFailover": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Enable automatic failover for regions."
- }
- },
- "enableFreeTier": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Optional. Flag to indicate whether Free Tier is enabled."
- }
- },
- "enableMultipleWriteLocations": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Optional. Enables the account to write in multiple locations. Periodic backup must be used if enabled."
- }
- },
- "disableKeyBasedMetadataWriteAccess": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Disable write operations on metadata resources (databases, containers, throughput) via account keys."
- }
- },
- "maxStalenessPrefix": {
- "type": "int",
- "defaultValue": 100000,
- "minValue": 1,
- "maxValue": 2147483647,
- "metadata": {
- "description": "Optional. Default to 100000. Max stale requests. Required for BoundedStaleness. Valid ranges, Single Region: 10 to 1000000. Multi Region: 100000 to 1000000."
- }
- },
- "maxIntervalInSeconds": {
- "type": "int",
- "defaultValue": 300,
- "minValue": 5,
- "maxValue": 86400,
- "metadata": {
- "description": "Optional. Default to 300. Max lag time (minutes). Required for BoundedStaleness. Valid ranges, Single Region: 5 to 84600. Multi Region: 300 to 86400."
- }
- },
- "serverVersion": {
- "type": "string",
- "defaultValue": "4.2",
- "allowedValues": [
- "3.2",
- "3.6",
- "4.0",
- "4.2",
- "5.0",
- "6.0",
- "7.0"
- ],
- "metadata": {
- "description": "Optional. Default to 4.2. Specifies the MongoDB server version to use."
- }
- },
- "sqlDatabases": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/sqlDatabaseType"
- },
- "defaultValue": [],
- "metadata": {
- "description": "Optional. SQL Databases configurations."
- }
- },
- "sqlRoleAssignmentsPrincipalIds": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. SQL Role Definitions configurations."
- }
- },
- "sqlRoleDefinitions": {
- "$ref": "#/definitions/sqlRoleDefinitionsType",
- "metadata": {
- "description": "Optional. SQL Role Definitions configurations."
- }
- },
- "mongodbDatabases": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. MongoDB Databases configurations."
- }
- },
- "gremlinDatabases": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Gremlin Databases configurations."
- }
- },
- "tables": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Table configurations."
- }
- },
- "enableTelemetry": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Enable/Disable usage telemetry for module."
- }
- },
- "lock": {
- "$ref": "#/definitions/lockType",
- "metadata": {
- "description": "Optional. The lock settings of the service."
- }
- },
- "roleAssignments": {
- "$ref": "#/definitions/roleAssignmentType",
- "metadata": {
- "description": "Optional. Array of role assignment objects that contain the 'roleDefinitionIdOrName' and 'principalIds' to define RBAC role assignments on this resource. In the roleDefinitionIdOrName attribute, you can provide either the display name of the role definition, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
- }
- },
- "diagnosticSettings": {
- "$ref": "#/definitions/diagnosticSettingType",
- "metadata": {
- "description": "Optional. The diagnostic settings of the service."
- }
- },
- "capabilitiesToAdd": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "defaultValue": [],
- "allowedValues": [
- "EnableCassandra",
- "EnableTable",
- "EnableGremlin",
- "EnableMongo",
- "DisableRateLimitingResponses",
- "EnableServerless",
- "EnableNoSQLVectorSearch",
- "EnableNoSQLFullTextSearch",
- "EnableMaterializedViews"
- ],
- "metadata": {
- "description": "Optional. List of Cosmos DB capabilities for the account."
- }
- },
- "backupPolicyType": {
- "type": "string",
- "defaultValue": "Continuous",
- "allowedValues": [
- "Periodic",
- "Continuous"
- ],
- "metadata": {
- "description": "Optional. Default to Continuous. Describes the mode of backups. Periodic backup must be used if multiple write locations are used."
- }
- },
- "backupPolicyContinuousTier": {
- "type": "string",
- "defaultValue": "Continuous30Days",
- "allowedValues": [
- "Continuous30Days",
- "Continuous7Days"
- ],
- "metadata": {
- "description": "Optional. Default to Continuous30Days. Configuration values for continuous mode backup."
- }
- },
- "backupIntervalInMinutes": {
- "type": "int",
- "defaultValue": 240,
- "minValue": 60,
- "maxValue": 1440,
- "metadata": {
- "description": "Optional. Default to 240. An integer representing the interval in minutes between two backups. Only applies to periodic backup type."
- }
- },
- "backupRetentionIntervalInHours": {
- "type": "int",
- "defaultValue": 8,
- "minValue": 2,
- "maxValue": 720,
- "metadata": {
- "description": "Optional. Default to 8. An integer representing the time (in hours) that each backup is retained. Only applies to periodic backup type."
- }
- },
- "backupStorageRedundancy": {
- "type": "string",
- "defaultValue": "Local",
- "allowedValues": [
- "Geo",
- "Local",
- "Zone"
- ],
- "metadata": {
- "description": "Optional. Default to Local. Enum to indicate type of backup residency. Only applies to periodic backup type."
- }
- },
- "privateEndpoints": {
- "$ref": "#/definitions/privateEndpointType",
- "metadata": {
- "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible."
- }
- },
- "secretsExportConfiguration": {
- "$ref": "#/definitions/secretsExportConfigurationType",
- "nullable": true,
- "metadata": {
- "description": "Optional. Key vault reference and secret settings for the module's secrets export."
- }
- },
- "networkRestrictions": {
- "$ref": "#/definitions/networkRestrictionsType",
- "defaultValue": {
- "ipRules": [],
- "virtualNetworkRules": [],
- "publicNetworkAccess": "Disabled"
- },
- "metadata": {
- "description": "Optional. The network configuration of this module. Defaults to `{ ipRules: [], virtualNetworkRules: [], publicNetworkAccess: 'Disabled' }`."
- }
- },
- "minimumTlsVersion": {
- "type": "string",
- "defaultValue": "Tls12",
- "allowedValues": [
- "Tls",
- "Tls11",
- "Tls12"
- ],
- "metadata": {
- "description": "Optional. Default to TLS 1.2. Enum to indicate the minimum allowed TLS version. Azure Cosmos DB for MongoDB RU and Apache Cassandra only work with TLS 1.2 or later."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "databaseAccount_locations",
- "count": "[length(parameters('locations'))]",
- "input": {
- "failoverPriority": "[parameters('locations')[copyIndex('databaseAccount_locations')].failoverPriority]",
- "locationName": "[parameters('locations')[copyIndex('databaseAccount_locations')].locationName]",
- "isZoneRedundant": "[coalesce(tryGet(parameters('locations')[copyIndex('databaseAccount_locations')], 'isZoneRedundant'), true())]"
- }
- },
- {
- "name": "capabilities",
- "count": "[length(parameters('capabilitiesToAdd'))]",
- "input": {
- "name": "[parameters('capabilitiesToAdd')[copyIndex('capabilities')]]"
- }
- },
- {
- "name": "ipRules",
- "count": "[length(coalesce(tryGet(parameters('networkRestrictions'), 'ipRules'), createArray()))]",
- "input": {
- "ipAddressOrRange": "[coalesce(tryGet(parameters('networkRestrictions'), 'ipRules'), createArray())[copyIndex('ipRules')]]"
- }
- },
- {
- "name": "virtualNetworkRules",
- "count": "[length(coalesce(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules'), createArray()))]",
- "input": {
- "id": "[coalesce(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules'), createArray())[copyIndex('virtualNetworkRules')].subnetResourceId]",
- "ignoreMissingVnetServiceEndpoint": false
- }
- },
- {
- "name": "formattedRoleAssignments",
- "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
- "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
- }
- ],
- "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
- "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
- "consistencyPolicy": {
- "Eventual": {
- "defaultConsistencyLevel": "Eventual"
- },
- "ConsistentPrefix": {
- "defaultConsistencyLevel": "ConsistentPrefix"
- },
- "Session": {
- "defaultConsistencyLevel": "Session"
- },
- "BoundedStaleness": {
- "defaultConsistencyLevel": "BoundedStaleness",
- "maxStalenessPrefix": "[parameters('maxStalenessPrefix')]",
- "maxIntervalInSeconds": "[parameters('maxIntervalInSeconds')]"
- },
- "Strong": {
- "defaultConsistencyLevel": "Strong"
- }
- },
- "defaultFailoverLocation": [
- {
- "failoverPriority": 0,
- "locationName": "[parameters('location')]",
- "isZoneRedundant": true
- }
- ],
- "kind": "[if(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('gremlinDatabases')))), 'GlobalDocumentDB', if(not(empty(parameters('mongodbDatabases'))), 'MongoDB', 'GlobalDocumentDB'))]",
- "backupPolicy": "[if(equals(parameters('backupPolicyType'), 'Continuous'), createObject('type', parameters('backupPolicyType'), 'continuousModeProperties', createObject('tier', parameters('backupPolicyContinuousTier'))), createObject('type', parameters('backupPolicyType'), 'periodicModeProperties', createObject('backupIntervalInMinutes', parameters('backupIntervalInMinutes'), 'backupRetentionIntervalInHours', parameters('backupRetentionIntervalInHours'), 'backupStorageRedundancy', parameters('backupStorageRedundancy'))))]",
- "databaseAccountProperties": "[union(createObject('databaseAccountOfferType', parameters('databaseAccountOfferType'), 'backupPolicy', variables('backupPolicy'), 'minimalTlsVersion', parameters('minimumTlsVersion')), if(or(or(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('mongodbDatabases')))), not(empty(parameters('gremlinDatabases')))), not(empty(parameters('tables')))), createObject('consistencyPolicy', variables('consistencyPolicy')[parameters('defaultConsistencyLevel')], 'enableMultipleWriteLocations', parameters('enableMultipleWriteLocations'), 'locations', if(empty(variables('databaseAccount_locations')), variables('defaultFailoverLocation'), variables('databaseAccount_locations')), 'ipRules', variables('ipRules'), 'virtualNetworkRules', variables('virtualNetworkRules'), 'networkAclBypass', coalesce(tryGet(parameters('networkRestrictions'), 'networkAclBypass'), 'AzureServices'), 'publicNetworkAccess', coalesce(tryGet(parameters('networkRestrictions'), 'publicNetworkAccess'), 'Enabled'), 'isVirtualNetworkFilterEnabled', or(not(empty(variables('ipRules'))), not(empty(variables('virtualNetworkRules')))), 'capabilities', variables('capabilities'), 'enableFreeTier', parameters('enableFreeTier'), 'enableAutomaticFailover', parameters('automaticFailover'), 'enableAnalyticalStorage', parameters('enableAnalyticalStorage')), createObject()), if(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('tables')))), createObject('disableLocalAuth', parameters('disableLocalAuth'), 'disableKeyBasedMetadataWriteAccess', parameters('disableKeyBasedMetadataWriteAccess')), createObject()), if(not(empty(parameters('mongodbDatabases'))), createObject('apiProperties', createObject('serverVersion', parameters('serverVersion'))), createObject()))]",
- "builtInRoleNames": {
- "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
- "Cosmos DB Account Reader Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fbdf93bf-df7d-467e-a4d2-9458aa1360c8')]",
- "Cosmos DB Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]",
- "CosmosBackupOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db7b14f2-5adf-42da-9f96-f2ee17bab5cb')]",
- "CosmosRestoreOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5432c526-bc82-444a-b7ba-57c5b0b5b34f')]",
- "DocumentDB Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5bd9cd88-fe45-4216-938b-f97437e15450')]",
- "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
- "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
- "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
- "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
- }
- },
- "resources": {
- "avmTelemetry": {
- "condition": "[parameters('enableTelemetry')]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2024-03-01",
- "name": "[format('46d3xbcp.res.documentdb-databaseaccount.{0}.{1}', replace('0.9.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
- "properties": {
- "mode": "Incremental",
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "resources": [],
- "outputs": {
- "telemetry": {
- "type": "String",
- "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
- }
- }
- }
- }
- },
- "databaseAccount": {
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('name')]",
- "location": "[parameters('location')]",
- "tags": "[parameters('tags')]",
- "identity": "[variables('identity')]",
- "kind": "[variables('kind')]",
- "properties": "[variables('databaseAccountProperties')]"
- },
- "databaseAccount_lock": {
- "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
- "type": "Microsoft.Authorization/locks",
- "apiVersion": "2020-05-01",
- "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
- "properties": {
- "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
- "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]"
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_diagnosticSettings": {
- "copy": {
- "name": "databaseAccount_diagnosticSettings",
- "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]"
- },
- "type": "Microsoft.Insights/diagnosticSettings",
- "apiVersion": "2021-05-01-preview",
- "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]",
- "properties": {
- "copy": [
- {
- "name": "metrics",
- "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]",
- "input": {
- "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]",
- "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]",
- "timeGrain": null
- }
- },
- {
- "name": "logs",
- "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]",
- "input": {
- "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]",
- "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]",
- "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]"
- }
- }
- ],
- "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]",
- "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]",
- "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]",
- "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]",
- "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]",
- "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]"
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_roleAssignments": {
- "copy": {
- "name": "databaseAccount_roleAssignments",
- "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
- },
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
- "properties": {
- "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
- "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
- "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
- "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
- "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
- "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
- "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_sqlDatabases": {
- "copy": {
- "name": "databaseAccount_sqlDatabases",
- "count": "[length(parameters('sqlDatabases'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('location')), parameters('sqlDatabases')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[parameters('sqlDatabases')[copyIndex()].name]"
- },
- "containers": {
- "value": "[tryGet(parameters('sqlDatabases')[copyIndex()], 'containers')]"
- },
- "throughput": {
- "value": "[tryGet(parameters('sqlDatabases')[copyIndex()], 'throughput')]"
- },
- "databaseAccountName": {
- "value": "[parameters('name')]"
- },
- "autoscaleSettingsMaxThroughput": {
- "value": "[tryGet(parameters('sqlDatabases')[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "14039021912249335209"
- },
- "name": "DocumentDB Database Account SQL Databases",
- "description": "This module deploys a SQL Database in a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
- }
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the SQL database ."
- }
- },
- "containers": {
- "type": "array",
- "items": {
- "type": "object"
- },
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Array of containers to deploy in the SQL database."
- }
- },
- "throughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used."
- }
- },
- "autoscaleSettingsMaxThroughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the SQL database resource."
- }
- }
- },
- "resources": {
- "databaseAccount": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('databaseAccountName')]"
- },
- "sqlDatabase": {
- "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
- "tags": "[parameters('tags')]",
- "properties": {
- "resource": {
- "id": "[parameters('name')]"
- },
- "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(equals(parameters('autoscaleSettingsMaxThroughput'), null()), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]"
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "container": {
- "copy": {
- "name": "container",
- "count": "[length(parameters('containers'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('name')), parameters('containers')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "databaseAccountName": {
- "value": "[parameters('databaseAccountName')]"
- },
- "sqlDatabaseName": {
- "value": "[parameters('name')]"
- },
- "name": {
- "value": "[parameters('containers')[copyIndex()].name]"
- },
- "analyticalStorageTtl": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'analyticalStorageTtl')]"
- },
- "autoscaleSettingsMaxThroughput": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'autoscaleSettingsMaxThroughput')]"
- },
- "conflictResolutionPolicy": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'conflictResolutionPolicy')]"
- },
- "defaultTtl": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'defaultTtl')]"
- },
- "indexingPolicy": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'indexingPolicy')]"
- },
- "kind": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'kind')]"
- },
- "version": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'version')]"
- },
- "paths": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'paths')]"
- },
- "throughput": "[if(and(or(not(equals(parameters('throughput'), null())), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), equals(tryGet(parameters('containers')[copyIndex()], 'throughput'), null())), createObject('value', -1), createObject('value', tryGet(parameters('containers')[copyIndex()], 'throughput')))]",
- "uniqueKeyPolicyKeys": {
- "value": "[tryGet(parameters('containers')[copyIndex()], 'uniqueKeyPolicyKeys')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "1471754747460263407"
- },
- "name": "DocumentDB Database Account SQL Database Containers",
- "description": "This module deploys a SQL Database Container in a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
- }
- },
- "sqlDatabaseName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent SQL Database. Required if the template is used in a standalone deployment."
- }
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the container."
- }
- },
- "analyticalStorageTtl": {
- "type": "int",
- "defaultValue": 0,
- "metadata": {
- "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store."
- }
- },
- "conflictResolutionPolicy": {
- "type": "object",
- "defaultValue": {},
- "metadata": {
- "description": "Optional. The conflict resolution policy for the container. Conflicts and conflict resolution policies are applicable if the Azure Cosmos DB account is configured with multiple write regions."
- }
- },
- "defaultTtl": {
- "type": "int",
- "defaultValue": -1,
- "minValue": -1,
- "maxValue": 2147483647,
- "metadata": {
- "description": "Optional. Default to -1. Default time to live (in seconds). With Time to Live or TTL, Azure Cosmos DB provides the ability to delete items automatically from a container after a certain time period. If the value is set to \"-1\", it is equal to infinity, and items don't expire by default."
- }
- },
- "throughput": {
- "type": "int",
- "defaultValue": 400,
- "metadata": {
- "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used."
- }
- },
- "autoscaleSettingsMaxThroughput": {
- "type": "int",
- "nullable": true,
- "maxValue": 1000000,
- "metadata": {
- "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the SQL Database resource."
- }
- },
- "paths": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "minLength": 1,
- "maxLength": 3,
- "metadata": {
- "description": "Required. List of paths using which data within the container can be partitioned. For kind=MultiHash it can be up to 3. For anything else it needs to be exactly 1."
- }
- },
- "indexingPolicy": {
- "type": "object",
- "defaultValue": {},
- "metadata": {
- "description": "Optional. Indexing policy of the container."
- }
- },
- "uniqueKeyPolicyKeys": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. The unique key policy configuration containing a list of unique keys that enforces uniqueness constraint on documents in the collection in the Azure Cosmos DB service."
- }
- },
- "kind": {
- "type": "string",
- "defaultValue": "Hash",
- "allowedValues": [
- "Hash",
- "MultiHash"
- ],
- "metadata": {
- "description": "Optional. Default to Hash. Indicates the kind of algorithm used for partitioning."
- }
- },
- "version": {
- "type": "int",
- "defaultValue": 1,
- "allowedValues": [
- 1,
- 2
- ],
- "metadata": {
- "description": "Optional. Default to 1 for Hash and 2 for MultiHash - 1 is not allowed for MultiHash. Version of the partition key definition."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "partitionKeyPaths",
- "count": "[length(parameters('paths'))]",
- "input": "[if(startsWith(parameters('paths')[copyIndex('partitionKeyPaths')], '/'), parameters('paths')[copyIndex('partitionKeyPaths')], format('/{0}', parameters('paths')[copyIndex('partitionKeyPaths')]))]"
- }
- ],
- "containerResourceParams": "[union(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'defaultTtl', parameters('defaultTtl'), 'id', parameters('name'), 'indexingPolicy', if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null()), 'partitionKey', createObject('paths', variables('partitionKeyPaths'), 'kind', parameters('kind'), 'version', if(equals(parameters('kind'), 'MultiHash'), 2, parameters('version'))), 'uniqueKeyPolicy', if(not(empty(parameters('uniqueKeyPolicyKeys'))), createObject('uniqueKeys', parameters('uniqueKeyPolicyKeys')), null())), if(not(equals(parameters('analyticalStorageTtl'), 0)), createObject('analyticalStorageTtl', parameters('analyticalStorageTtl')), createObject()))]"
- },
- "resources": {
- "databaseAccount::sqlDatabase": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('sqlDatabaseName'))]",
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('databaseAccountName')]"
- },
- "container": {
- "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]",
- "tags": "[parameters('tags')]",
- "properties": {
- "resource": "[variables('containerResourceParams')]",
- "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(and(equals(parameters('autoscaleSettingsMaxThroughput'), null()), not(equals(parameters('throughput'), -1))), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]"
- },
- "dependsOn": [
- "databaseAccount::sqlDatabase"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the container."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the container."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the container was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "sqlDatabase"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the SQL database."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the SQL database."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the SQL database was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_sqlRoleDefinitions": {
- "copy": {
- "name": "databaseAccount_sqlRoleDefinitions",
- "count": "[length(coalesce(parameters('sqlRoleDefinitions'), createArray()))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-sqlrd-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()].name]"
- },
- "databaseAccountName": {
- "value": "[parameters('name')]"
- },
- "dataActions": {
- "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'dataActions')]"
- },
- "roleName": {
- "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'roleName')]"
- },
- "roleType": {
- "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'roleType')]"
- },
- "principalIds": {
- "value": "[parameters('sqlRoleAssignmentsPrincipalIds')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "3860121931480041680"
- },
- "name": "DocumentDB Database Account SQL Role.",
- "description": "This module deploys SQL Role Definision and Assignment in a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
- }
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the SQL Role."
- }
- },
- "dataActions": {
- "type": "array",
- "defaultValue": [
- "Microsoft.DocumentDB/databaseAccounts/readMetadata",
- "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*",
- "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*"
- ],
- "metadata": {
- "description": "Optional. An array of data actions that are allowed."
- }
- },
- "principalIds": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Ids needs to be granted."
- }
- },
- "roleName": {
- "type": "string",
- "defaultValue": "Reader Writer",
- "metadata": {
- "description": "Optional. A user-friendly name for the Role Definition. Must be unique for the database account."
- }
- },
- "roleType": {
- "type": "string",
- "defaultValue": "CustomRole",
- "allowedValues": [
- "CustomRole",
- "BuiltInRole"
- ],
- "metadata": {
- "description": "Optional. Indicates whether the Role Definition was built-in or user created."
- }
- }
- },
- "resources": [
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('sql-role-definition-{0}', uniqueString(parameters('name')))]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "databaseAccountName": {
- "value": "[parameters('databaseAccountName')]"
- },
- "dataActions": {
- "value": "[parameters('dataActions')]"
- },
- "roleName": {
- "value": "[parameters('roleName')]"
- },
- "roleType": {
- "value": "[parameters('roleType')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "2222650596260487600"
- },
- "name": "DocumentDB Database Account SQL Role Definitions.",
- "description": "This module deploys a SQL Role Definision in a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
- }
- },
- "dataActions": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. An array of data actions that are allowed."
- }
- },
- "roleName": {
- "type": "string",
- "defaultValue": "Reader Writer",
- "metadata": {
- "description": "Optional. A user-friendly name for the Role Definition. Must be unique for the database account."
- }
- },
- "roleType": {
- "type": "string",
- "defaultValue": "CustomRole",
- "allowedValues": [
- "CustomRole",
- "BuiltInRole"
- ],
- "metadata": {
- "description": "Optional. Indicates whether the Role Definition was built-in or user created."
- }
- }
- },
- "resources": [
- {
- "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role'))]",
- "properties": {
- "assignableScopes": [
- "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]"
- ],
- "permissions": [
- {
- "dataActions": "[parameters('dataActions')]"
- }
- ],
- "roleName": "[parameters('roleName')]",
- "type": "[parameters('roleType')]"
- }
- }
- ],
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the SQL database."
- },
- "value": "[guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the SQL database."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), 'sql-role'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the SQL database was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- }
- },
- {
- "copy": {
- "name": "sqlRoleAssignment",
- "count": "[length(parameters('principalIds'))]",
- "mode": "serial",
- "batchSize": 1
- },
- "condition": "[not(empty(parameters('principalIds')[copyIndex()]))]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('sql-role-assign-{0}', uniqueString(parameters('principalIds')[copyIndex()]))]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[guid(reference(resourceId('Microsoft.Resources/deployments', format('sql-role-definition-{0}', uniqueString(parameters('name')))), '2022-09-01').outputs.resourceId.value, parameters('principalIds')[copyIndex()], resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]"
- },
- "databaseAccountName": {
- "value": "[parameters('databaseAccountName')]"
- },
- "roleDefinitionId": {
- "value": "[reference(resourceId('Microsoft.Resources/deployments', format('sql-role-definition-{0}', uniqueString(parameters('name')))), '2022-09-01').outputs.resourceId.value]"
- },
- "principalId": {
- "value": "[parameters('principalIds')[copyIndex()]]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "12993275952067538651"
- },
- "name": "DocumentDB Database Account SQL Role Assignments.",
- "description": "This module deploys a SQL Role Assignment in a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
- }
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the SQL Role Assignment."
- }
- },
- "principalId": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Id needs to be granted."
- }
- },
- "roleDefinitionId": {
- "type": "string",
- "metadata": {
- "description": "Required. Id of the SQL Role Definition."
- }
- }
- },
- "resources": [
- {
- "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
- "properties": {
- "principalId": "[parameters('principalId')]",
- "roleDefinitionId": "[parameters('roleDefinitionId')]",
- "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))]"
- }
- }
- ],
- "outputs": {
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the SQL Role Assignment was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "[resourceId('Microsoft.Resources/deployments', format('sql-role-definition-{0}', uniqueString(parameters('name'))))]"
- ]
- }
- ],
- "outputs": {
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the SQL Role Definition and Assignment were created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_mongodbDatabases": {
- "copy": {
- "name": "databaseAccount_mongodbDatabases",
- "count": "[length(parameters('mongodbDatabases'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-mongodb-{1}', uniqueString(deployment().name, parameters('location')), parameters('mongodbDatabases')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "databaseAccountName": {
- "value": "[parameters('name')]"
- },
- "name": {
- "value": "[parameters('mongodbDatabases')[copyIndex()].name]"
- },
- "tags": {
- "value": "[coalesce(tryGet(parameters('mongodbDatabases')[copyIndex()], 'tags'), parameters('tags'))]"
- },
- "collections": {
- "value": "[tryGet(parameters('mongodbDatabases')[copyIndex()], 'collections')]"
- },
- "throughput": {
- "value": "[tryGet(parameters('mongodbDatabases')[copyIndex()], 'throughput')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "18295016247574474595"
- },
- "name": "DocumentDB Database Account MongoDB Databases",
- "description": "This module deploys a MongoDB Database within a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment."
- }
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the mongodb database."
- }
- },
- "throughput": {
- "type": "int",
- "defaultValue": 400,
- "metadata": {
- "description": "Optional. Request Units per second."
- }
- },
- "collections": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Collections in the mongodb database."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the resource."
- }
- }
- },
- "resources": {
- "databaseAccount": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('databaseAccountName')]"
- },
- "mongodbDatabase": {
- "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
- "tags": "[parameters('tags')]",
- "properties": {
- "resource": {
- "id": "[parameters('name')]"
- },
- "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', parameters('throughput')))]"
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "mongodbDatabase_collections": {
- "copy": {
- "name": "mongodbDatabase_collections",
- "count": "[length(parameters('collections'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-collection-{1}', uniqueString(deployment().name, parameters('name')), parameters('collections')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "databaseAccountName": {
- "value": "[parameters('databaseAccountName')]"
- },
- "mongodbDatabaseName": {
- "value": "[parameters('name')]"
- },
- "name": {
- "value": "[parameters('collections')[copyIndex()].name]"
- },
- "indexes": {
- "value": "[parameters('collections')[copyIndex()].indexes]"
- },
- "shardKey": {
- "value": "[parameters('collections')[copyIndex()].shardKey]"
- },
- "throughput": {
- "value": "[tryGet(parameters('collections')[copyIndex()], 'throughput')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "9799909568020880663"
- },
- "name": "DocumentDB Database Account MongoDB Database Collections",
- "description": "This module deploys a MongoDB Database Collection.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment."
- }
- },
- "mongodbDatabaseName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent mongodb database. Required if the template is used in a standalone deployment."
- }
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the collection."
- }
- },
- "throughput": {
- "type": "int",
- "defaultValue": 400,
- "metadata": {
- "description": "Optional. Request Units per second."
- }
- },
- "indexes": {
- "type": "array",
- "metadata": {
- "description": "Required. Indexes for the collection."
- }
- },
- "shardKey": {
- "type": "object",
- "metadata": {
- "description": "Required. ShardKey for the collection."
- }
- }
- },
- "resources": [
- {
- "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]",
- "properties": {
- "options": "[if(contains(reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), '2023-04-15').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', parameters('throughput')))]",
- "resource": {
- "id": "[parameters('name')]",
- "indexes": "[parameters('indexes')]",
- "shardKey": "[parameters('shardKey')]"
- }
- }
- }
- ],
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the mongodb database collection."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the mongodb database collection."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the mongodb database collection was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "mongodbDatabase"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the mongodb database."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the mongodb database."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/mongodbDatabases', parameters('databaseAccountName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the mongodb database was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_gremlinDatabases": {
- "copy": {
- "name": "databaseAccount_gremlinDatabases",
- "count": "[length(parameters('gremlinDatabases'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-gremlin-{1}', uniqueString(deployment().name, parameters('location')), parameters('gremlinDatabases')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "databaseAccountName": {
- "value": "[parameters('name')]"
- },
- "name": {
- "value": "[parameters('gremlinDatabases')[copyIndex()].name]"
- },
- "tags": {
- "value": "[coalesce(tryGet(parameters('gremlinDatabases')[copyIndex()], 'tags'), parameters('tags'))]"
- },
- "graphs": {
- "value": "[tryGet(parameters('gremlinDatabases')[copyIndex()], 'graphs')]"
- },
- "maxThroughput": {
- "value": "[tryGet(parameters('gremlinDatabases')[copyIndex()], 'maxThroughput')]"
- },
- "throughput": {
- "value": "[tryGet(parameters('gremlinDatabases')[copyIndex()], 'throughput')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "6528096364275148764"
- },
- "name": "DocumentDB Database Account Gremlin Databases",
- "description": "This module deploys a Gremlin Database within a CosmosDB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the Gremlin database."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the Gremlin database resource."
- }
- },
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Gremlin database. Required if the template is used in a standalone deployment."
- }
- },
- "graphs": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Array of graphs to deploy in the Gremlin database."
- }
- },
- "maxThroughput": {
- "type": "int",
- "defaultValue": 4000,
- "metadata": {
- "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored."
- }
- },
- "throughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`."
- }
- }
- },
- "resources": {
- "databaseAccount": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('databaseAccountName')]"
- },
- "gremlinDatabase": {
- "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
- "tags": "[parameters('tags')]",
- "properties": {
- "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('maxThroughput')), null()), 'throughput', parameters('throughput')))]",
- "resource": {
- "id": "[parameters('name')]"
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "gremlinDatabase_gremlinGraphs": {
- "copy": {
- "name": "gremlinDatabase_gremlinGraphs",
- "count": "[length(parameters('graphs'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-gremlindb-{1}', uniqueString(deployment().name, parameters('name')), parameters('graphs')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[parameters('graphs')[copyIndex()].name]"
- },
- "gremlinDatabaseName": {
- "value": "[parameters('name')]"
- },
- "databaseAccountName": {
- "value": "[parameters('databaseAccountName')]"
- },
- "indexingPolicy": {
- "value": "[tryGet(parameters('graphs')[copyIndex()], 'indexingPolicy')]"
- },
- "partitionKeyPaths": "[if(not(empty(parameters('graphs')[copyIndex()].partitionKeyPaths)), createObject('value', parameters('graphs')[copyIndex()].partitionKeyPaths), createObject('value', createArray()))]"
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "16994331830326213766"
- },
- "name": "DocumentDB Database Accounts Gremlin Databases Graphs",
- "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the graph."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the Gremlin graph resource."
- }
- },
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment."
- }
- },
- "gremlinDatabaseName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Gremlin Database. Required if the template is used in a standalone deployment."
- }
- },
- "indexingPolicy": {
- "type": "object",
- "defaultValue": {},
- "metadata": {
- "description": "Optional. Indexing policy of the graph."
- }
- },
- "partitionKeyPaths": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. List of paths using which data within the container can be partitioned."
- }
- }
- },
- "resources": {
- "databaseAccount::gremlinDatabase": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'))]",
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('databaseAccountName')]"
- },
- "gremlinGraph": {
- "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]",
- "tags": "[parameters('tags')]",
- "properties": {
- "resource": {
- "id": "[parameters('name')]",
- "indexingPolicy": "[if(not(empty(parameters('indexingPolicy'))), parameters('indexingPolicy'), null())]",
- "partitionKey": {
- "paths": "[if(not(empty(parameters('partitionKeyPaths'))), parameters('partitionKeyPaths'), null())]"
- }
- }
- },
- "dependsOn": [
- "databaseAccount::gremlinDatabase"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the graph."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the graph."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the graph was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "gremlinDatabase"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the Gremlin database."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the Gremlin database."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases', parameters('databaseAccountName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the Gremlin database was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_tables": {
- "copy": {
- "name": "databaseAccount_tables",
- "count": "[length(parameters('tables'))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-table-{1}', uniqueString(deployment().name, parameters('location')), parameters('tables')[copyIndex()].name)]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "databaseAccountName": {
- "value": "[parameters('name')]"
- },
- "name": {
- "value": "[parameters('tables')[copyIndex()].name]"
- },
- "tags": {
- "value": "[coalesce(tryGet(parameters('tables')[copyIndex()], 'tags'), parameters('tags'))]"
- },
- "maxThroughput": {
- "value": "[tryGet(parameters('tables')[copyIndex()], 'maxThroughput')]"
- },
- "throughput": {
- "value": "[tryGet(parameters('tables')[copyIndex()], 'throughput')]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.31.92.45157",
- "templateHash": "6722170581524078621"
- },
- "name": "Azure Cosmos DB account tables",
- "description": "This module deploys a table within an Azure Cosmos DB Account.",
- "owner": "Azure/module-maintainers"
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the table."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags for the table."
- }
- },
- "databaseAccountName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent Azure Cosmos DB account. Required if the template is used in a standalone deployment."
- }
- },
- "maxThroughput": {
- "type": "int",
- "defaultValue": 4000,
- "metadata": {
- "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored."
- }
- },
- "throughput": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`."
- }
- }
- },
- "resources": {
- "databaseAccount": {
- "existing": true,
- "type": "Microsoft.DocumentDB/databaseAccounts",
- "apiVersion": "2023-04-15",
- "name": "[parameters('databaseAccountName')]"
- },
- "table": {
- "type": "Microsoft.DocumentDB/databaseAccounts/tables",
- "apiVersion": "2023-04-15",
- "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]",
- "tags": "[parameters('tags')]",
- "properties": {
- "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('maxThroughput')), null()), 'throughput', parameters('throughput')))]",
- "resource": {
- "id": "[parameters('name')]"
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the table."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the table."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/tables', parameters('databaseAccountName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the table was created in."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "databaseAccount_privateEndpoints": {
- "copy": {
- "name": "databaseAccount_privateEndpoints",
- "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]"
- },
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-databaseAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]",
- "resourceGroup": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupName'), '')]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]"
- },
- "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]",
- "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]",
- "subnetResourceId": {
- "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]"
- },
- "enableTelemetry": {
- "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'enableTelemetry'), parameters('enableTelemetry'))]"
- },
- "location": {
- "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]"
- },
- "lock": {
- "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]"
- },
- "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.29.47.4906",
- "templateHash": "1277254088602407590"
- },
- "name": "Private Endpoints",
- "description": "This module deploys a Private Endpoint.",
- "owner": "Azure/module-maintainers"
- },
- "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."
- }
- }
- }
- },
- "roleAssignmentType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
- }
- },
- "roleDefinitionIdOrName": {
- "type": "string",
- "metadata": {
- "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
- }
- },
- "principalId": {
- "type": "string",
- "metadata": {
- "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
- }
- },
- "principalType": {
- "type": "string",
- "allowedValues": [
- "Device",
- "ForeignGroup",
- "Group",
- "ServicePrincipal",
- "User"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. The principal type of the assigned principal ID."
- }
- },
- "description": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The description of the role assignment."
- }
- },
- "condition": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
- }
- },
- "conditionVersion": {
- "type": "string",
- "allowedValues": [
- "2.0"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Version of the condition."
- }
- },
- "delegatedManagedIdentityResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The Resource Id of the delegated managed identity resource."
- }
- }
- }
- },
- "nullable": true
- },
- "lockType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the name of lock."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "CanNotDelete",
- "None",
- "ReadOnly"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the type of lock."
- }
- }
- },
- "nullable": true
- },
- "ipConfigurationsType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the resource that is unique within a resource group."
- }
- },
- "properties": {
- "type": "object",
- "properties": {
- "groupId": {
- "type": "string",
- "metadata": {
- "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. 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."
- }
- }
- }
- },
- "nullable": true
- },
- "manualPrivateLinkServiceConnectionsType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the private link service connection."
- }
- },
- "properties": {
- "type": "object",
- "properties": {
- "groupIds": {
- "type": "array",
- "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",
- "metadata": {
- "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars."
- }
- }
- },
- "metadata": {
- "description": "Required. Properties of private link service connection."
- }
- }
- }
- },
- "nullable": true
- },
- "privateLinkServiceConnectionsType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the private link service connection."
- }
- },
- "properties": {
- "type": "object",
- "properties": {
- "groupIds": {
- "type": "array",
- "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."
- }
- }
- }
- },
- "nullable": true
- },
- "customDnsConfigType": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "fqdn": {
- "type": "string",
- "metadata": {
- "description": "Required. Fqdn that resolves to private endpoint IP address."
- }
- },
- "ipAddresses": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "metadata": {
- "description": "Required. A list of private IP addresses of the private endpoint."
- }
- }
- }
- },
- "nullable": true
- },
- "privateDnsZoneGroupConfigType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of the private DNS zone group config."
- }
- },
- "privateDnsZoneResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. The resource id of the private DNS zone."
- }
- }
- },
- "metadata": {
- "__bicep_imported_from!": {
- "sourceTemplate": "private-dns-zone-group/main.bicep"
- }
- }
- }
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the private endpoint resource to create."
- }
- },
- "subnetResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. Resource ID of the subnet where the endpoint needs to be created."
- }
- },
- "applicationSecurityGroupResourceIds": {
- "type": "array",
- "nullable": true,
- "metadata": {
- "description": "Optional. Application security groups in which the private endpoint IP configuration is included."
- }
- },
- "customNetworkInterfaceName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The custom name of the network interface attached to the private endpoint."
- }
- },
- "ipConfigurations": {
- "$ref": "#/definitions/ipConfigurationsType",
- "metadata": {
- "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints."
- }
- },
- "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",
- "metadata": {
- "description": "Optional. The lock settings of the service."
- }
- },
- "roleAssignments": {
- "$ref": "#/definitions/roleAssignmentType",
- "metadata": {
- "description": "Optional. Array of role assignments to create."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags to be applied on all resources/resource groups in this deployment."
- }
- },
- "customDnsConfigs": {
- "$ref": "#/definitions/customDnsConfigType",
- "metadata": {
- "description": "Optional. Custom DNS configurations."
- }
- },
- "manualPrivateLinkServiceConnections": {
- "$ref": "#/definitions/manualPrivateLinkServiceConnectionsType",
- "metadata": {
- "description": "Optional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource."
- }
- },
- "privateLinkServiceConnections": {
- "$ref": "#/definitions/privateLinkServiceConnectionsType",
- "metadata": {
- "description": "Optional. A grouping of information about the connection to the remote resource."
- }
- },
- "enableTelemetry": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Enable/Disable usage telemetry for module."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "formattedRoleAssignments",
- "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
- "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
- }
- ],
- "builtInRoleNames": {
- "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
- "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]",
- "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]",
- "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]",
- "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]",
- "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
- "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
- "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]",
- "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
- "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]"
- }
- },
- "resources": {
- "avmTelemetry": {
- "condition": "[parameters('enableTelemetry')]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2024-03-01",
- "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.7.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
- "properties": {
- "mode": "Incremental",
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "resources": [],
- "outputs": {
- "telemetry": {
- "type": "String",
- "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
- }
- }
- }
- }
- },
- "privateEndpoint": {
- "type": "Microsoft.Network/privateEndpoints",
- "apiVersion": "2023-11-01",
- "name": "[parameters('name')]",
- "location": "[parameters('location')]",
- "tags": "[parameters('tags')]",
- "properties": {
- "copy": [
- {
- "name": "applicationSecurityGroups",
- "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]",
- "input": {
- "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]"
- }
- }
- ],
- "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]",
- "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]",
- "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]",
- "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]",
- "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]",
- "subnet": {
- "id": "[parameters('subnetResourceId')]"
- }
- }
- },
- "privateEndpoint_lock": {
- "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
- "type": "Microsoft.Authorization/locks",
- "apiVersion": "2020-05-01",
- "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
- "properties": {
- "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
- "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]"
- },
- "dependsOn": [
- "privateEndpoint"
- ]
- },
- "privateEndpoint_roleAssignments": {
- "copy": {
- "name": "privateEndpoint_roleAssignments",
- "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
- },
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
- "properties": {
- "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
- "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
- "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
- "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
- "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
- "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
- "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
- },
- "dependsOn": [
- "privateEndpoint"
- ]
- },
- "privateEndpoint_privateDnsZoneGroup": {
- "condition": "[not(empty(parameters('privateDnsZoneGroup')))]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "name": {
- "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]"
- },
- "privateEndpointName": {
- "value": "[parameters('name')]"
- },
- "privateDnsZoneConfigs": {
- "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.29.47.4906",
- "templateHash": "5805178546717255803"
- },
- "name": "Private Endpoint Private DNS Zone Groups",
- "description": "This module deploys a Private Endpoint Private DNS Zone Group.",
- "owner": "Azure/module-maintainers"
- },
- "definitions": {
- "privateDnsZoneGroupConfigType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of the private DNS zone group config."
- }
- },
- "privateDnsZoneResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. The resource id of the private DNS zone."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true
- }
- }
- },
- "parameters": {
- "privateEndpointName": {
- "type": "string",
- "metadata": {
- "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment."
- }
- },
- "privateDnsZoneConfigs": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/privateDnsZoneGroupConfigType"
- },
- "minLength": 1,
- "maxLength": 5,
- "metadata": {
- "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones."
- }
- },
- "name": {
- "type": "string",
- "defaultValue": "default",
- "metadata": {
- "description": "Optional. The name of the private DNS zone group."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "privateDnsZoneConfigsVar",
- "count": "[length(parameters('privateDnsZoneConfigs'))]",
- "input": {
- "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]",
- "properties": {
- "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]"
- }
- }
- }
- ]
- },
- "resources": {
- "privateEndpoint": {
- "existing": true,
- "type": "Microsoft.Network/privateEndpoints",
- "apiVersion": "2023-11-01",
- "name": "[parameters('privateEndpointName')]"
- },
- "privateDnsZoneGroup": {
- "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups",
- "apiVersion": "2023-11-01",
- "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]",
- "properties": {
- "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]"
- },
- "dependsOn": [
- "privateEndpoint"
- ]
- }
- },
- "outputs": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the private endpoint DNS zone group."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the private endpoint DNS zone group."
- },
- "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The resource group the private endpoint DNS zone group was deployed into."
- },
- "value": "[resourceGroup().name]"
- }
- }
- }
- },
- "dependsOn": [
- "privateEndpoint"
- ]
- }
- },
- "outputs": {
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The resource group the private endpoint was deployed into."
- },
- "value": "[resourceGroup().name]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the private endpoint."
- },
- "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]"
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the private endpoint."
- },
- "value": "[parameters('name')]"
- },
- "location": {
- "type": "string",
- "metadata": {
- "description": "The location the resource was deployed into."
- },
- "value": "[reference('privateEndpoint', '2023-11-01', 'full').location]"
- },
- "customDnsConfig": {
- "$ref": "#/definitions/customDnsConfigType",
- "metadata": {
- "description": "The custom DNS configurations of the private endpoint."
- },
- "value": "[reference('privateEndpoint').customDnsConfigs]"
- },
- "networkInterfaceIds": {
- "type": "array",
- "metadata": {
- "description": "The IDs of the network interfaces associated with the private endpoint."
- },
- "value": "[reference('privateEndpoint').networkInterfaces]"
- },
- "groupId": {
- "type": "string",
- "metadata": {
- "description": "The group Id for the private endpoint Group."
- },
- "value": "[if(and(not(empty(reference('privateEndpoint').manualPrivateLinkServiceConnections)), greater(length(tryGet(reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties, 'groupIds')), 0)), coalesce(tryGet(reference('privateEndpoint').manualPrivateLinkServiceConnections[0].properties, 'groupIds', 0), ''), if(and(not(empty(reference('privateEndpoint').privateLinkServiceConnections)), greater(length(tryGet(reference('privateEndpoint').privateLinkServiceConnections[0].properties, 'groupIds')), 0)), coalesce(tryGet(reference('privateEndpoint').privateLinkServiceConnections[0].properties, 'groupIds', 0), ''), ''))]"
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- },
- "secretsExport": {
- "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]",
- "subscriptionId": "[split(coalesce(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '//'), '/')[2]]",
- "resourceGroup": "[split(coalesce(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '////'), '/')[4]]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "keyVaultName": {
- "value": "[last(split(coalesce(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '//'), '/'))]"
- },
- "secretsToSet": {
- "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'primaryWriteKeySecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').primaryWriteKeySecretName, 'value', listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').primaryMasterKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'primaryReadOnlyKeySecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').primaryReadOnlyKeySecretName, 'value', listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').primaryReadonlyMasterKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'primaryWriteConnectionStringSecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').primaryWriteConnectionStringSecretName, 'value', listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').connectionStrings[0].connectionString)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'primaryReadonlyConnectionStringSecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').primaryReadonlyConnectionStringSecretName, 'value', listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').connectionStrings[2].connectionString)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryWriteKeySecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').secondaryWriteKeySecretName, 'value', listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').secondaryMasterKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryReadonlyKeySecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').secondaryReadonlyKeySecretName, 'value', listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').secondaryReadonlyMasterKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryWriteConnectionStringSecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').secondaryWriteConnectionStringSecretName, 'value', listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').connectionStrings[1].connectionString)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryReadonlyConnectionStringSecretName'), createArray(createObject('name', parameters('secretsExportConfiguration').secondaryReadonlyConnectionStringSecretName, 'value', listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '2023-04-15').connectionStrings[3].connectionString)), 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.31.92.45157",
- "templateHash": "7954388693868310378"
- }
- },
- "definitions": {
- "secretSetType": {
- "type": "object",
- "properties": {
- "secretResourceId": {
- "type": "string",
- "metadata": {
- "description": "The resourceId of the exported secret."
- }
- },
- "secretUri": {
- "type": "string",
- "metadata": {
- "description": "The secret URI of the exported secret."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true
- }
- },
- "secretToSetType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the secret to set."
- }
- },
- "value": {
- "type": "securestring",
- "metadata": {
- "description": "Required. The value of the secret to set."
- }
- }
- }
- }
- },
- "parameters": {
- "keyVaultName": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the Key Vault to set the ecrets in."
- }
- },
- "secretsToSet": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/secretToSetType"
- },
- "metadata": {
- "description": "Required. The secrets to set in the Key Vault."
- }
- }
- },
- "resources": {
- "keyVault": {
- "existing": true,
- "type": "Microsoft.KeyVault/vaults",
- "apiVersion": "2022-07-01",
- "name": "[parameters('keyVaultName')]"
- },
- "secrets": {
- "copy": {
- "name": "secrets",
- "count": "[length(parameters('secretsToSet'))]"
- },
- "type": "Microsoft.KeyVault/vaults/secrets",
- "apiVersion": "2023-07-01",
- "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]",
- "properties": {
- "value": "[parameters('secretsToSet')[copyIndex()].value]"
- },
- "dependsOn": [
- "keyVault"
- ]
- }
- },
- "outputs": {
- "secretsSet": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/secretSetType"
- },
- "metadata": {
- "description": "The references to the secrets exported to the provided Key Vault."
- },
- "copy": {
- "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]",
- "input": {
- "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]",
- "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]"
- }
- }
- }
- }
- }
- },
- "dependsOn": [
- "databaseAccount"
- ]
- }
- },
- "outputs": {
- "exportedSecrets": {
- "$ref": "#/definitions/secretsOutputType",
- "metadata": {
- "description": "The references to the secrets exported to the provided Key Vault."
- },
- "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]"
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the database account."
- },
- "value": "[parameters('name')]"
- },
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the database account."
- },
- "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the database account was created in."
- },
- "value": "[resourceGroup().name]"
- },
- "systemAssignedMIPrincipalId": {
- "type": "string",
- "metadata": {
- "description": "The principal ID of the system assigned identity."
- },
- "value": "[coalesce(tryGet(tryGet(reference('databaseAccount', '2023-04-15', 'full'), 'identity'), 'principalId'), '')]"
- },
- "location": {
- "type": "string",
- "metadata": {
- "description": "The location the resource was deployed into."
- },
- "value": "[reference('databaseAccount', '2023-04-15', 'full').location]"
- },
- "endpoint": {
- "type": "string",
- "metadata": {
- "description": "The endpoint of the database account."
- },
- "value": "[reference('databaseAccount').documentEndpoint]"
- },
- "privateEndpoints": {
- "type": "array",
- "metadata": {
- "description": "The private endpoints of the database account."
- },
- "copy": {
- "count": "[length(if(not(empty(parameters('privateEndpoints'))), array(parameters('privateEndpoints')), createArray()))]",
- "input": {
- "name": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]",
- "resourceId": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]",
- "groupId": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.groupId.value]",
- "customDnsConfig": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfig.value]",
- "networkInterfaceIds": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceIds.value]"
- }
- }
- }
- }
- }
- },
- "dependsOn": [
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]"
- ]
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "[toLower(format('{0}{1}containerAppFrontend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))]",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "managedIdentities": {
- "value": {
- "systemAssigned": true,
- "userAssignedResourceIds": [
- "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityOutput.value.resourceId]"
- ]
- }
- },
- "containers": {
- "value": [
- {
- "env": [
- {
- "name": "API_URL",
- "value": "[format('https://{0}', reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01').configuration.ingress.fqdn)]"
- }
- ],
- "image": "[format('cmsacontainerreg.azurecr.io/cmsafrontend:{0}', parameters('imageVersion'))]",
- "name": "cmsafrontend",
- "resources": {
- "cpu": "1",
- "memory": "2.0Gi"
- }
- }
- ]
- },
- "ingressTargetPort": {
- "value": 3000
- },
- "ingressExternal": {
- "value": true
- },
- "scaleMinReplicas": {
- "value": 1
- },
- "scaleMaxReplicas": {
- "value": 1
- },
- "environmentResourceId": {
- "value": "[reference(resourceId('Microsoft.Resources/deployments', toLower(format('{0}conAppsEnv', variables('ResourcePrefix')))), '2022-09-01').outputs.resourceId.value]"
- },
- "name": {
- "value": "[toLower(format('{0}{1}Frontend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))]"
- },
- "location": {
- "value": "[parameters('solutionLocation')]"
- }
- },
- "template": {
- "$schema": "https://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": "1964938100334091991"
- },
- "name": "Container Apps",
- "description": "This module deploys a Container App."
- },
- "definitions": {
- "containerType": {
- "type": "object",
- "properties": {
- "args": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Container start command arguments."
- }
- },
- "command": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Container start command."
- }
- },
- "env": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/environmentVarType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Container environment variables."
- }
- },
- "image": {
- "type": "string",
- "metadata": {
- "description": "Required. Container image tag."
- }
- },
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Custom container name."
- }
- },
- "probes": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/containerAppProbeType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. List of probes for the container."
- }
- },
- "resources": {
- "type": "object",
- "metadata": {
- "description": "Required. Container resource requirements."
- }
- },
- "volumeMounts": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/volumeMountType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Container volume mounts."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true,
- "description": "The type for a container."
- }
- },
- "ingressPortMappingType": {
- "type": "object",
- "properties": {
- "exposedPort": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the exposed port for the target port. If not specified, it defaults to target port."
- }
- },
- "external": {
- "type": "bool",
- "metadata": {
- "description": "Required. Specifies whether the app port is accessible outside of the environment."
- }
- },
- "targetPort": {
- "type": "int",
- "metadata": {
- "description": "Required. Specifies the port the container listens on."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true,
- "description": "The type for an ingress port mapping."
- }
- },
- "serviceBindingType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the service."
- }
- },
- "serviceId": {
- "type": "string",
- "metadata": {
- "description": "Required. The service ID."
- }
- }
- },
- "metadata": {
- "description": "The type for a service binding."
- }
- },
- "environmentVarType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Environment variable name."
- }
- },
- "secretRef": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Name of the Container App secret from which to pull the environment variable value."
- }
- },
- "value": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Non-secret environment variable value."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true,
- "description": "The type for an environment variable."
- }
- },
- "containerAppProbeType": {
- "type": "object",
- "properties": {
- "failureThreshold": {
- "type": "int",
- "nullable": true,
- "minValue": 1,
- "maxValue": 10,
- "metadata": {
- "description": "Optional. Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3."
- }
- },
- "httpGet": {
- "$ref": "#/definitions/containerAppProbeHttpGetType",
- "nullable": true,
- "metadata": {
- "description": "Optional. HTTPGet specifies the http request to perform."
- }
- },
- "initialDelaySeconds": {
- "type": "int",
- "nullable": true,
- "minValue": 1,
- "maxValue": 60,
- "metadata": {
- "description": "Optional. Number of seconds after the container has started before liveness probes are initiated."
- }
- },
- "periodSeconds": {
- "type": "int",
- "nullable": true,
- "minValue": 1,
- "maxValue": 240,
- "metadata": {
- "description": "Optional. How often (in seconds) to perform the probe. Default to 10 seconds."
- }
- },
- "successThreshold": {
- "type": "int",
- "nullable": true,
- "minValue": 1,
- "maxValue": 10,
- "metadata": {
- "description": "Optional. Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup."
- }
- },
- "tcpSocket": {
- "$ref": "#/definitions/containerAppProbeTcpSocketType",
- "nullable": true,
- "metadata": {
- "description": "Optional. The TCP socket specifies an action involving a TCP port. TCP hooks not yet supported."
- }
- },
- "terminationGracePeriodSeconds": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is an alpha field and requires enabling ProbeTerminationGracePeriod feature gate. Maximum value is 3600 seconds (1 hour)."
- }
- },
- "timeoutSeconds": {
- "type": "int",
- "nullable": true,
- "minValue": 1,
- "maxValue": 240,
- "metadata": {
- "description": "Optional. Number of seconds after which the probe times out. Defaults to 1 second."
- }
- },
- "type": {
- "type": "string",
- "allowedValues": [
- "Liveness",
- "Readiness",
- "Startup"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. The type of probe."
- }
- }
- },
- "metadata": {
- "description": "The type for a container app probe."
- }
- },
- "corsPolicyType": {
- "type": "object",
- "properties": {
- "allowCredentials": {
- "type": "bool",
- "nullable": true,
- "metadata": {
- "description": "Optional. Switch to determine whether the resource allows credentials."
- }
- },
- "allowedHeaders": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the content for the access-control-allow-headers header."
- }
- },
- "allowedMethods": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the content for the access-control-allow-methods header."
- }
- },
- "allowedOrigins": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the content for the access-control-allow-origins header."
- }
- },
- "exposeHeaders": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the content for the access-control-expose-headers header."
- }
- },
- "maxAge": {
- "type": "int",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specifies the content for the access-control-max-age header."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true,
- "description": "The type for a CORS policy."
- }
- },
- "containerAppProbeHttpGetType": {
- "type": "object",
- "properties": {
- "host": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Host name to connect to. Defaults to the pod IP."
- }
- },
- "httpHeaders": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/containerAppProbeHttpGetHeadersItemType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. HTTP headers to set in the request."
- }
- },
- "path": {
- "type": "string",
- "metadata": {
- "description": "Required. Path to access on the HTTP server."
- }
- },
- "port": {
- "type": "int",
- "metadata": {
- "description": "Required. Name or number of the port to access on the container."
- }
- },
- "scheme": {
- "type": "string",
- "allowedValues": [
- "HTTP",
- "HTTPS"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Scheme to use for connecting to the host. Defaults to HTTP."
- }
- }
- },
- "metadata": {
- "description": "The type for a container app probe HTTP GET."
- }
- },
- "containerAppProbeHttpGetHeadersItemType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the header."
- }
- },
- "value": {
- "type": "string",
- "metadata": {
- "description": "Required. Value of the header."
- }
- }
- },
- "metadata": {
- "description": "The type for a container app probe HTTP GET header."
- }
- },
- "containerAppProbeTcpSocketType": {
- "type": "object",
- "properties": {
- "host": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Host name to connect to, defaults to the pod IP."
- }
- },
- "port": {
- "type": "int",
- "minValue": 1,
- "maxValue": 65535,
- "metadata": {
- "description": "Required. Number of the port to access on the container. Name must be an IANA_SVC_NAME."
- }
- }
- },
- "metadata": {
- "description": "The type for a container app probe TCP socket."
- }
- },
- "volumeMountType": {
- "type": "object",
- "properties": {
- "mountPath": {
- "type": "string",
- "metadata": {
- "description": "Required. Path within the container at which the volume should be mounted.Must not contain ':'."
- }
- },
- "subPath": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)."
- }
- },
- "volumeName": {
- "type": "string",
- "metadata": {
- "description": "Required. This must match the Name of a Volume."
- }
- }
- },
- "metadata": {
- "description": "The type for a volume mount."
- }
- },
- "runtimeType": {
- "type": "object",
- "properties": {
- "dotnet": {
- "type": "object",
- "properties": {
- "autoConfigureDataProtection": {
- "type": "bool",
- "metadata": {
- "description": "Required. Enable to auto configure the ASP.NET Core Data Protection feature."
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Runtime configuration for ASP.NET Core."
- }
- },
- "java": {
- "type": "object",
- "properties": {
- "enableMetrics": {
- "type": "bool",
- "metadata": {
- "description": "Required. Enable JMX core metrics for the Java app."
- }
- },
- "enableJavaAgent": {
- "type": "bool",
- "metadata": {
- "description": "Required. Enable Java agent injection for the Java app."
- }
- },
- "loggerSettings": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "logger": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the logger."
- }
- },
- "level": {
- "type": "string",
- "allowedValues": [
- "debug",
- "error",
- "info",
- "off",
- "trace",
- "warn"
- ],
- "metadata": {
- "description": "Required. Java agent logging level."
- }
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Java agent logging configuration."
- }
- }
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Runtime configuration for Java."
- }
- }
- },
- "nullable": true,
- "metadata": {
- "__bicep_export!": true,
- "description": "Optional. App runtime configuration for the Container App."
- }
- },
- "secretType": {
- "type": "object",
- "properties": {
- "identity": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Resource ID of a managed identity to authenticate with Azure Key Vault, or System to use a system-assigned identity."
- }
- },
- "keyVaultUrl": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Conditional. Azure Key Vault URL pointing to the secret referenced by the Container App Job. Required if `value` is null."
- }
- },
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name of the secret."
- }
- },
- "value": {
- "type": "securestring",
- "nullable": true,
- "metadata": {
- "description": "Conditional. The secret value, if not fetched from Key Vault. Required if `keyVaultUrl` is not null."
- }
- }
- },
- "metadata": {
- "__bicep_export!": true,
- "description": "The type for a secret."
- }
- },
- "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.4.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.4.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.4.1"
- }
- }
- }
- },
- "parameters": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. Name of the Container App."
- }
- },
- "location": {
- "type": "string",
- "defaultValue": "[resourceGroup().location]",
- "metadata": {
- "description": "Optional. Location for all Resources."
- }
- },
- "disableIngress": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Optional. Bool to disable all ingress traffic for the container app."
- }
- },
- "ingressExternal": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Bool indicating if the App exposes an external HTTP endpoint."
- }
- },
- "clientCertificateMode": {
- "type": "string",
- "defaultValue": "ignore",
- "allowedValues": [
- "accept",
- "ignore",
- "require"
- ],
- "metadata": {
- "description": "Optional. Client certificate mode for mTLS."
- }
- },
- "corsPolicy": {
- "$ref": "#/definitions/corsPolicyType",
- "nullable": true,
- "metadata": {
- "description": "Optional. Object userd to configure CORS policy."
- }
- },
- "stickySessionsAffinity": {
- "type": "string",
- "defaultValue": "none",
- "allowedValues": [
- "none",
- "sticky"
- ],
- "metadata": {
- "description": "Optional. Bool indicating if the Container App should enable session affinity."
- }
- },
- "ingressTransport": {
- "type": "string",
- "defaultValue": "auto",
- "allowedValues": [
- "auto",
- "http",
- "http2",
- "tcp"
- ],
- "metadata": {
- "description": "Optional. Ingress transport protocol."
- }
- },
- "service": {
- "type": "object",
- "defaultValue": {},
- "metadata": {
- "description": "Optional. Dev ContainerApp service type."
- }
- },
- "includeAddOns": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Optional. Toggle to include the service configuration."
- }
- },
- "additionalPortMappings": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ingressPortMappingType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Settings to expose additional ports on container app."
- }
- },
- "ingressAllowInsecure": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Bool indicating if HTTP connections to is allowed. If set to false HTTP connections are automatically redirected to HTTPS connections."
- }
- },
- "ingressTargetPort": {
- "type": "int",
- "defaultValue": 80,
- "metadata": {
- "description": "Optional. Target Port in containers for traffic from ingress."
- }
- },
- "scaleMaxReplicas": {
- "type": "int",
- "defaultValue": 10,
- "metadata": {
- "description": "Optional. Maximum number of container replicas. Defaults to 10 if not set."
- }
- },
- "scaleMinReplicas": {
- "type": "int",
- "defaultValue": 3,
- "metadata": {
- "description": "Optional. Minimum number of container replicas. Defaults to 3 if not set."
- }
- },
- "scaleRules": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Scaling rules."
- }
- },
- "serviceBinds": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/serviceBindingType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. List of container app services bound to the app."
- }
- },
- "activeRevisionsMode": {
- "type": "string",
- "defaultValue": "Single",
- "allowedValues": [
- "Multiple",
- "Single"
- ],
- "metadata": {
- "description": "Optional. Controls how active revisions are handled for the Container app."
- }
- },
- "environmentResourceId": {
- "type": "string",
- "metadata": {
- "description": "Required. Resource ID of environment."
- }
- },
- "lock": {
- "$ref": "#/definitions/lockType",
- "nullable": true,
- "metadata": {
- "description": "Optional. The lock settings of the service."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Tags of the resource."
- }
- },
- "registries": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Collection of private container registry credentials for containers used by the Container app."
- }
- },
- "managedIdentities": {
- "$ref": "#/definitions/managedIdentityAllType",
- "nullable": true,
- "metadata": {
- "description": "Optional. The managed identity definition for this resource."
- }
- },
- "roleAssignments": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/roleAssignmentType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. Array of role assignments to create."
- }
- },
- "enableTelemetry": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Enable/Disable usage telemetry for module."
- }
- },
- "customDomains": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Custom domain bindings for Container App hostnames."
- }
- },
- "exposedPort": {
- "type": "int",
- "defaultValue": 0,
- "metadata": {
- "description": "Optional. Exposed Port in containers for TCP traffic from ingress."
- }
- },
- "ipSecurityRestrictions": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. Rules to restrict incoming IP address."
- }
- },
- "trafficLabel": {
- "type": "string",
- "defaultValue": "label-1",
- "metadata": {
- "description": "Optional. Associates a traffic label with a revision. Label name should be consist of lower case alphanumeric characters or dashes."
- }
- },
- "trafficLatestRevision": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Indicates that the traffic weight belongs to a latest stable revision."
- }
- },
- "trafficRevisionName": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Name of a revision."
- }
- },
- "trafficWeight": {
- "type": "int",
- "defaultValue": 100,
- "metadata": {
- "description": "Optional. Traffic weight assigned to a revision."
- }
- },
- "dapr": {
- "type": "object",
- "defaultValue": {},
- "metadata": {
- "description": "Optional. Dapr configuration for the Container App."
- }
- },
- "maxInactiveRevisions": {
- "type": "int",
- "defaultValue": 0,
- "metadata": {
- "description": "Optional. Max inactive revisions a Container App can have."
- }
- },
- "runtime": {
- "$ref": "#/definitions/runtimeType",
- "metadata": {
- "description": "Optional. Runtime configuration for the Container App."
- }
- },
- "containers": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/containerType"
- },
- "metadata": {
- "description": "Required. List of container definitions for the Container App."
- }
- },
- "initContainersTemplate": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. List of specialized containers that run before app containers."
- }
- },
- "secrets": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/secretType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The secrets of the Container App."
- }
- },
- "revisionSuffix": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. User friendly suffix that is appended to the revision name."
- }
- },
- "volumes": {
- "type": "array",
- "defaultValue": [],
- "metadata": {
- "description": "Optional. List of volume definitions for the Container App."
- }
- },
- "workloadProfileName": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. Workload profile name to pin for container app execution."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "formattedRoleAssignments",
- "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
- "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
- }
- ],
- "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
- "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]",
- "builtInRoleNames": {
- "ContainerApp Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ad2dd5fb-cd4b-4fd4-a9b6-4fed3630980b')]",
- "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
- "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
- "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
- "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
- "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
- }
- },
- "resources": {
- "avmTelemetry": {
- "condition": "[parameters('enableTelemetry')]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2024-03-01",
- "name": "[format('46d3xbcp.res.app-containerapp.{0}.{1}', replace('0.13.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
- "properties": {
- "mode": "Incremental",
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "resources": [],
- "outputs": {
- "telemetry": {
- "type": "String",
- "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
- }
- }
- }
- }
- },
- "containerApp": {
- "type": "Microsoft.App/containerApps",
- "apiVersion": "2024-10-02-preview",
- "name": "[parameters('name')]",
- "tags": "[parameters('tags')]",
- "location": "[parameters('location')]",
- "identity": "[variables('identity')]",
- "properties": {
- "environmentId": "[parameters('environmentResourceId')]",
- "configuration": {
- "activeRevisionsMode": "[parameters('activeRevisionsMode')]",
- "dapr": "[if(not(empty(parameters('dapr'))), parameters('dapr'), null())]",
- "ingress": "[if(parameters('disableIngress'), null(), createObject('additionalPortMappings', parameters('additionalPortMappings'), 'allowInsecure', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('ingressAllowInsecure'), false()), 'customDomains', if(not(empty(parameters('customDomains'))), parameters('customDomains'), null()), 'corsPolicy', if(and(not(equals(parameters('corsPolicy'), null())), not(equals(parameters('ingressTransport'), 'tcp'))), createObject('allowCredentials', coalesce(tryGet(parameters('corsPolicy'), 'allowCredentials'), false()), 'allowedHeaders', coalesce(tryGet(parameters('corsPolicy'), 'allowedHeaders'), createArray()), 'allowedMethods', coalesce(tryGet(parameters('corsPolicy'), 'allowedMethods'), createArray()), 'allowedOrigins', coalesce(tryGet(parameters('corsPolicy'), 'allowedOrigins'), createArray()), 'exposeHeaders', coalesce(tryGet(parameters('corsPolicy'), 'exposeHeaders'), createArray()), 'maxAge', tryGet(parameters('corsPolicy'), 'maxAge')), null()), 'clientCertificateMode', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('clientCertificateMode'), null()), 'exposedPort', parameters('exposedPort'), 'external', parameters('ingressExternal'), 'ipSecurityRestrictions', if(not(empty(parameters('ipSecurityRestrictions'))), parameters('ipSecurityRestrictions'), null()), 'targetPort', parameters('ingressTargetPort'), 'stickySessions', createObject('affinity', parameters('stickySessionsAffinity')), 'traffic', if(not(equals(parameters('ingressTransport'), 'tcp')), createArray(createObject('label', parameters('trafficLabel'), 'latestRevision', parameters('trafficLatestRevision'), 'revisionName', parameters('trafficRevisionName'), 'weight', parameters('trafficWeight'))), null()), 'transport', parameters('ingressTransport')))]",
- "service": "[if(and(parameters('includeAddOns'), not(empty(parameters('service')))), parameters('service'), null())]",
- "maxInactiveRevisions": "[parameters('maxInactiveRevisions')]",
- "registries": "[if(not(empty(parameters('registries'))), parameters('registries'), null())]",
- "secrets": "[parameters('secrets')]",
- "runtime": {
- "dotnet": "[if(not(empty(tryGet(parameters('runtime'), 'dotnet'))), createObject('autoConfigureDataProtection', tryGet(parameters('runtime'), 'dotnet', 'autoConfigureDataProtection')), null())]",
- "java": "[if(not(empty(tryGet(parameters('runtime'), 'java'))), createObject('enableMetrics', tryGet(parameters('runtime'), 'java', 'enableMetrics'), 'javaAgent', createObject('enabled', tryGet(parameters('runtime'), 'java', 'enableJavaAgent'), 'logging', createObject('loggerSettings', tryGet(tryGet(parameters('runtime'), 'java'), 'loggerSettings')))), null())]"
- }
- },
- "template": {
- "containers": "[parameters('containers')]",
- "initContainers": "[if(not(empty(parameters('initContainersTemplate'))), parameters('initContainersTemplate'), null())]",
- "revisionSuffix": "[parameters('revisionSuffix')]",
- "scale": {
- "maxReplicas": "[parameters('scaleMaxReplicas')]",
- "minReplicas": "[parameters('scaleMinReplicas')]",
- "rules": "[if(not(empty(parameters('scaleRules'))), parameters('scaleRules'), null())]"
- },
- "serviceBinds": "[if(and(parameters('includeAddOns'), not(empty(parameters('serviceBinds')))), parameters('serviceBinds'), null())]",
- "volumes": "[if(not(empty(parameters('volumes'))), parameters('volumes'), null())]"
- },
- "workloadProfileName": "[parameters('workloadProfileName')]"
- }
- },
- "containerApp_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.App/containerApps/{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": [
- "containerApp"
- ]
- },
- "containerApp_roleAssignments": {
- "copy": {
- "name": "containerApp_roleAssignments",
- "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
- },
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.App/containerApps/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.App/containerApps', 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": [
- "containerApp"
- ]
- }
- },
- "outputs": {
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the Container App."
- },
- "value": "[resourceId('Microsoft.App/containerApps', parameters('name'))]"
- },
- "fqdn": {
- "type": "string",
- "metadata": {
- "description": "The configuration of ingress fqdn."
- },
- "value": "[if(parameters('disableIngress'), 'IngressDisabled', reference('containerApp').configuration.ingress.fqdn)]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The name of the resource group the Container App was deployed into."
- },
- "value": "[resourceGroup().name]"
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the Container App."
- },
- "value": "[parameters('name')]"
- },
- "systemAssignedMIPrincipalId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "The principal ID of the system assigned identity."
- },
- "value": "[tryGet(tryGet(reference('containerApp', '2024-10-02-preview', 'full'), 'identity'), 'principalId')]"
- },
- "location": {
- "type": "string",
- "metadata": {
- "description": "The location the resource was deployed into."
- },
- "value": "[reference('containerApp', '2024-10-02-preview', 'full').location]"
- }
- }
- }
- },
- "dependsOn": [
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]",
- "[resourceId('Microsoft.Resources/deployments', toLower(format('{0}conAppsEnv', variables('ResourcePrefix'))))]",
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]"
- ]
- },
- {
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2022-09-01",
- "name": "deploymentScriptCLI",
- "properties": {
- "expressionEvaluationOptions": {
- "scope": "inner"
- },
- "mode": "Incremental",
- "parameters": {
- "kind": {
- "value": "AzureCLI"
- },
- "name": {
- "value": "rdsmin001"
- },
- "azCliVersion": {
- "value": "2.69.0"
- },
- "location": {
- "value": "[resourceGroup().location]"
- },
- "managedIdentities": {
- "value": {
- "userAssignedResourceIds": [
- "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity'), '2022-09-01').outputs.managedIdentityId.value]"
- ]
- }
- },
- "scriptContent": {
- "value": "[format('az cosmosdb sql role assignment create --resource-group \"{0}\" --account-name \"{1}\" --role-definition-id \"{2}\" --scope \"{3}\" --principal-id \"{4}\"', resourceGroup().name, reference(resourceId('Microsoft.Resources/deployments', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))), '2022-09-01').outputs.name.value, resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', split(format('{0}/00000000-0000-0000-0000-000000000002', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))), '/')[0], split(format('{0}/00000000-0000-0000-0000-000000000002', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))), '/')[1]), reference(resourceId('Microsoft.Resources/deployments', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix')))), '2022-09-01').outputs.resourceId.value, reference(resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2023-05-01', 'full').identity.principalId)]"
- }
- },
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "languageVersion": "2.0",
- "contentVersion": "1.0.0.0",
- "metadata": {
- "_generator": {
- "name": "bicep",
- "version": "0.32.4.45862",
- "templateHash": "8965217851411422458"
- },
- "name": "Deployment Scripts",
- "description": "This module deploys Deployment Scripts.",
- "owner": "Azure/module-maintainers"
- },
- "definitions": {
- "environmentVariableType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "metadata": {
- "description": "Required. The name of the environment variable."
- }
- },
- "secureValue": {
- "type": "securestring",
- "nullable": true,
- "metadata": {
- "description": "Conditional. The value of the secure environment variable. Required if `value` is null."
- }
- },
- "value": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Conditional. The value of the environment variable. Required if `secureValue` is null."
- }
- }
- }
- },
- "lockType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the name of lock."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "CanNotDelete",
- "None",
- "ReadOnly"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Specify the type of lock."
- }
- }
- },
- "metadata": {
- "description": "An AVM-aligned type for a lock.",
- "__bicep_imported_from!": {
- "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1"
- }
- }
- },
- "managedIdentityOnlyUserAssignedType": {
- "type": "object",
- "properties": {
- "userAssignedResourceIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption."
- }
- }
- },
- "metadata": {
- "description": "An AVM-aligned type for a managed identity configuration. To be used if only user-assigned identities are supported by the resource provider.",
- "__bicep_imported_from!": {
- "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1"
- }
- }
- },
- "roleAssignmentType": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated."
- }
- },
- "roleDefinitionIdOrName": {
- "type": "string",
- "metadata": {
- "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'."
- }
- },
- "principalId": {
- "type": "string",
- "metadata": {
- "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to."
- }
- },
- "principalType": {
- "type": "string",
- "allowedValues": [
- "Device",
- "ForeignGroup",
- "Group",
- "ServicePrincipal",
- "User"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. The principal type of the assigned principal ID."
- }
- },
- "description": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The description of the role assignment."
- }
- },
- "condition": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"."
- }
- },
- "conditionVersion": {
- "type": "string",
- "allowedValues": [
- "2.0"
- ],
- "nullable": true,
- "metadata": {
- "description": "Optional. Version of the condition."
- }
- },
- "delegatedManagedIdentityResourceId": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. The Resource Id of the delegated managed identity resource."
- }
- }
- },
- "metadata": {
- "description": "An AVM-aligned type for a role assignment.",
- "__bicep_imported_from!": {
- "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1"
- }
- }
- }
- },
- "parameters": {
- "name": {
- "type": "string",
- "maxLength": 90,
- "metadata": {
- "description": "Required. Name of the Deployment Script."
- }
- },
- "location": {
- "type": "string",
- "defaultValue": "[resourceGroup().location]",
- "metadata": {
- "description": "Optional. Location for all resources."
- }
- },
- "kind": {
- "type": "string",
- "allowedValues": [
- "AzureCLI",
- "AzurePowerShell"
- ],
- "metadata": {
- "description": "Required. Specifies the Kind of the Deployment Script."
- }
- },
- "managedIdentities": {
- "$ref": "#/definitions/managedIdentityOnlyUserAssignedType",
- "nullable": true,
- "metadata": {
- "description": "Optional. The managed identity definition for this resource."
- }
- },
- "tags": {
- "type": "object",
- "nullable": true,
- "metadata": {
- "description": "Optional. Resource tags."
- }
- },
- "azPowerShellVersion": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list."
- }
- },
- "azCliVersion": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list."
- }
- },
- "scriptContent": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead."
- }
- },
- "primaryScriptUri": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead."
- }
- },
- "environmentVariables": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/environmentVariableType"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. The environment variables to pass over to the script."
- }
- },
- "supportingScriptUris": {
- "type": "array",
- "nullable": true,
- "metadata": {
- "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)."
- }
- },
- "subnetResourceIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "metadata": {
- "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)."
- }
- },
- "arguments": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces."
- }
- },
- "retentionInterval": {
- "type": "string",
- "defaultValue": "P1D",
- "metadata": {
- "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)."
- }
- },
- "baseTime": {
- "type": "string",
- "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]",
- "metadata": {
- "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed."
- }
- },
- "runOnce": {
- "type": "bool",
- "defaultValue": false,
- "metadata": {
- "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once."
- }
- },
- "cleanupPreference": {
- "type": "string",
- "defaultValue": "Always",
- "allowedValues": [
- "Always",
- "OnSuccess",
- "OnExpiration"
- ],
- "metadata": {
- "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)."
- }
- },
- "containerGroupName": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed."
- }
- },
- "storageAccountResourceId": {
- "type": "string",
- "defaultValue": "",
- "metadata": {
- "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account."
- }
- },
- "timeout": {
- "type": "string",
- "nullable": true,
- "metadata": {
- "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year."
- }
- },
- "lock": {
- "$ref": "#/definitions/lockType",
- "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."
- }
- },
- "enableTelemetry": {
- "type": "bool",
- "defaultValue": true,
- "metadata": {
- "description": "Optional. Enable/Disable usage telemetry for module."
- }
- }
- },
- "variables": {
- "copy": [
- {
- "name": "formattedRoleAssignments",
- "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]",
- "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]"
- },
- {
- "name": "subnetIds",
- "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]",
- "input": {
- "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]"
- }
- }
- ],
- "builtInRoleNames": {
- "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
- "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]",
- "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
- "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]",
- "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]"
- },
- "containerSettings": {
- "containerGroupName": "[parameters('containerGroupName')]",
- "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]"
- },
- "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]",
- "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]"
- },
- "resources": {
- "storageAccount": {
- "condition": "[not(empty(parameters('storageAccountResourceId')))]",
- "existing": true,
- "type": "Microsoft.Storage/storageAccounts",
- "apiVersion": "2023-05-01",
- "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]",
- "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]",
- "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]"
- },
- "avmTelemetry": {
- "condition": "[parameters('enableTelemetry')]",
- "type": "Microsoft.Resources/deployments",
- "apiVersion": "2024-03-01",
- "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]",
- "properties": {
- "mode": "Incremental",
- "template": {
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
- "contentVersion": "1.0.0.0",
- "resources": [],
- "outputs": {
- "telemetry": {
- "type": "String",
- "value": "For more information, see https://aka.ms/avm/TelemetryInfo"
- }
- }
- }
- }
- },
- "deploymentScript": {
- "type": "Microsoft.Resources/deploymentScripts",
- "apiVersion": "2023-08-01",
- "name": "[parameters('name')]",
- "location": "[parameters('location')]",
- "tags": "[parameters('tags')]",
- "identity": "[variables('identity')]",
- "kind": "[parameters('kind')]",
- "properties": {
- "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]",
- "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]",
- "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]",
- "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]",
- "arguments": "[parameters('arguments')]",
- "environmentVariables": "[parameters('environmentVariables')]",
- "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]",
- "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]",
- "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]",
- "cleanupPreference": "[parameters('cleanupPreference')]",
- "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]",
- "retentionInterval": "[parameters('retentionInterval')]",
- "timeout": "[parameters('timeout')]"
- }
- },
- "deploymentScript_lock": {
- "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]",
- "type": "Microsoft.Authorization/locks",
- "apiVersion": "2020-05-01",
- "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]",
- "properties": {
- "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]",
- "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]"
- },
- "dependsOn": [
- "deploymentScript"
- ]
- },
- "deploymentScript_roleAssignments": {
- "copy": {
- "name": "deploymentScript_roleAssignments",
- "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]"
- },
- "type": "Microsoft.Authorization/roleAssignments",
- "apiVersion": "2022-04-01",
- "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]",
- "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]",
- "properties": {
- "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]",
- "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]",
- "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]",
- "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]",
- "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]",
- "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]",
- "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]"
- },
- "dependsOn": [
- "deploymentScript"
- ]
- },
- "deploymentScriptLogs": {
- "existing": true,
- "type": "Microsoft.Resources/deploymentScripts/logs",
- "apiVersion": "2023-08-01",
- "name": "[format('{0}/{1}', parameters('name'), 'default')]",
- "dependsOn": [
- "deploymentScript"
- ]
- }
- },
- "outputs": {
- "resourceId": {
- "type": "string",
- "metadata": {
- "description": "The resource ID of the deployment script."
- },
- "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]"
- },
- "resourceGroupName": {
- "type": "string",
- "metadata": {
- "description": "The resource group the deployment script was deployed into."
- },
- "value": "[resourceGroup().name]"
- },
- "name": {
- "type": "string",
- "metadata": {
- "description": "The name of the deployment script."
- },
- "value": "[parameters('name')]"
- },
- "location": {
- "type": "string",
- "metadata": {
- "description": "The location the resource was deployed into."
- },
- "value": "[reference('deploymentScript', '2023-08-01', 'full').location]"
- },
- "outputs": {
- "type": "object",
- "metadata": {
- "description": "The output of the deployment script."
- },
- "value": "[coalesce(tryGet(reference('deploymentScript'), 'outputs'), createObject())]"
- },
- "deploymentScriptLogs": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "metadata": {
- "description": "The logs of the deployment script."
- },
- "value": "[split(reference('deploymentScriptLogs').log, '\n')]"
- }
- }
- }
- },
- "dependsOn": [
- "[resourceId('Microsoft.App/containerApps', toLower(format('{0}{1}Backend', variables('abbrs').containers.containerApp, variables('ResourcePrefix'))))]",
- "[resourceId('Microsoft.Resources/deployments', toLower(format('{0}{1}databaseAccount', variables('abbrs').databases.cosmosDBDatabase, variables('ResourcePrefix'))))]",
- "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', 'deploy_managed_identity')]"
- ]
- }
- ],
- "outputs": {
- "AZURE_AIFOUNDRY_NAME": {
- "type": "string",
- "value": "[variables('aiFoundryName')]"
- },
- "WEB_APP_URL": {
- "type": "string",
- "value": "[format('https://{0}', reference(resourceId('Microsoft.Resources/deployments', toLower(format('{0}{1}containerAppFrontend', variables('abbrs').containers.containerApp, variables('ResourcePrefix')))), '2022-09-01').outputs.fqdn.value)]"
- },
- "aiFoundryName": {
- "type": "string",
- "value": "[variables('aiFoundryName')]"
- },
- "aiProjectName": {
- "type": "string",
- "value": "[variables('aiProjectName')]"
- },
- "projectEndpointString": {
- "type": "string",
- "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('aiFoundryName'), variables('aiProjectName')), '2025-04-01-preview').endpoints['AI Foundry API']]"
- }
- }
-}
\ No newline at end of file
diff --git a/infra/main.parameters.json b/infra/main.parameters.json
index 307cd258..e1a4d73d 100644
--- a/infra/main.parameters.json
+++ b/infra/main.parameters.json
@@ -1,58 +1,88 @@
{
- "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
- "contentVersion": "1.0.0.0",
- "parameters": {
- "location": {
- "value": "${AZURE_LOCATION}"
- },
- "backendExists": {
- "value": "${SERVICE_BACKEND_RESOURCE_EXISTS=false}"
- },
- "backendDefinition": {
- "value": {
- "settings": [
- {
- "name": "",
- "value": "${VAR}",
- "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
- "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment."
- },
- {
- "name": "",
- "value": "${VAR_S}",
- "secret": true,
- "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
- "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment."
- }
- ]
- }
- },
- "frontendExists": {
- "value": "${SERVICE_FRONTEND_RESOURCE_EXISTS=false}"
- },
- "frontendDefinition": {
- "value": {
- "settings": [
- {
- "name": "",
- "value": "${VAR}",
- "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
- "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment."
- },
- {
- "name": "",
- "value": "${VAR_S}",
- "secret": true,
- "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
- "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment."
- }
- ]
- }
- },
- "principalId": {
- "value": "${AZURE_PRINCIPAL_ID}"
- },
- "aiModelDeployments": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "solutionName": {
+ "value": "${AZURE_ENV_NAME}"
+ },
+ "location": {
+ "value": "${AZURE_LOCATION}"
+ },
+ "deploymentType": {
+ "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}"
+ },
+ "llmModel": {
+ "value": "${AZURE_ENV_MODEL_NAME}"
+ },
+ "capacity": {
+ "value": "${AZURE_ENV_MODEL_CAPACITY}"
+ },
+ "gptModelVersion": {
+ "value": "${AZURE_ENV_MODEL_VERSION}"
+ },
+ "imageVersion": {
+ "value": "${AZURE_ENV_IMAGETAG}"
+ },
+ "existingLogAnalyticsWorkspaceId": {
+ "value": "${AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID}"
+ },
+ "secondaryLocation": {
+ "value": "${AZURE_ENV_COSMOS_SECONDARY_LOCATION}"
+ },
+ "vmAdminUsername": {
+ "value": "${AZURE_ENV_JUMPBOX_ADMIN_USERNAME}"
+ },
+ "vmAdminPassword": {
+ "value": "${AZURE_ENV_JUMPBOX_ADMIN_PASSWORD}"
+ },
+ "backendExists": {
+ "value": "${SERVICE_BACKEND_RESOURCE_EXISTS=false}"
+ },
+ "backendDefinition": {
+ "value": {
+ "settings": [
+ {
+ "name": "",
+ "value": "${VAR}",
+ "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
+ "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment."
+ },
+ {
+ "name": "",
+ "value": "${VAR_S}",
+ "secret": true,
+ "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
+ "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment."
+ }
+ ]
+ }
+ },
+ "frontendExists": {
+ "value": "${SERVICE_FRONTEND_RESOURCE_EXISTS=false}"
+ },
+ "frontendDefinition": {
+ "value": {
+ "settings": [
+ {
+ "name": "",
+ "value": "${VAR}",
+ "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
+ "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment."
+ },
+ {
+ "name": "",
+ "value": "${VAR_S}",
+ "secret": true,
+ "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.",
+ "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment."
+ }
+ ]
+ }
+ },
+ "principalId": {
+ "value": "${AZURE_PRINCIPAL_ID}"
+ },
+ "aiModelDeployments": {
"value": [
{
"name": "gpt-4o",
@@ -68,5 +98,5 @@
}
]
}
- }
+ }
}
diff --git a/infra/modules/ai-foundry/ai-services.bicep b/infra/modules/ai-foundry/ai-services.bicep
new file mode 100644
index 00000000..bb601e0b
--- /dev/null
+++ b/infra/modules/ai-foundry/ai-services.bicep
@@ -0,0 +1,552 @@
+// This module is here solely to provide network injection for Cognitive Services.
+// The AVM Module 'br/public:avm/res/cognitive-services/account:0.11.0' does not support that feature as of version 0.11.0
+metadata name = 'Cognitive Services'
+metadata description = 'This module deploys a Cognitive Service.'
+
+@description('Required. The name of Cognitive Services account.')
+param name string
+
+@description('Required. Kind of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.')
+@allowed([
+ 'AIServices'
+ 'AnomalyDetector'
+ 'CognitiveServices'
+ 'ComputerVision'
+ 'ContentModerator'
+ 'ContentSafety'
+ 'ConversationalLanguageUnderstanding'
+ 'CustomVision.Prediction'
+ 'CustomVision.Training'
+ 'Face'
+ 'FormRecognizer'
+ 'HealthInsights'
+ 'ImmersiveReader'
+ 'Internal.AllInOne'
+ 'LUIS'
+ 'LUIS.Authoring'
+ 'LanguageAuthoring'
+ 'MetricsAdvisor'
+ 'OpenAI'
+ 'Personalizer'
+ 'QnAMaker.v2'
+ 'SpeechServices'
+ 'TextAnalytics'
+ 'TextTranslation'
+])
+param kind string = 'AIServices'
+
+@description('Optional. SKU of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.')
+@allowed([
+ 'C2'
+ 'C3'
+ 'C4'
+ 'F0'
+ 'F1'
+ 'S'
+ 'S0'
+ 'S1'
+ 'S10'
+ 'S2'
+ 'S3'
+ 'S4'
+ 'S5'
+ 'S6'
+ 'S7'
+ 'S8'
+ 'S9'
+])
+param sku string = 'S0'
+
+@description('Optional. Location for all Resources.')
+param location string = resourceGroup().location
+
+import { diagnosticSettingFullType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. The diagnostic settings of the service.')
+param diagnosticSettings diagnosticSettingFullType[]?
+
+@description('Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set.')
+@allowed([
+ 'Enabled'
+ 'Disabled'
+])
+param publicNetworkAccess string?
+
+@description('Conditional. Subdomain name used for token-based authentication. Required if \'networkAcls\' or \'privateEndpoints\' are set.')
+param customSubDomainName string?
+
+@description('Optional. A collection of rules governing the accessibility from specific network locations.')
+param networkAcls object?
+
+@description('Optional. The network injection subnet resource Id for the Cognitive Services account. This allows to use the AI Services account with a virtual network.')
+param networkInjectionSubnetResourceId string?
+
+import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible.')
+param privateEndpoints privateEndpointSingleServiceType[]?
+
+import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Array of role assignments to create.')
+param roleAssignments roleAssignmentType[]?
+
+@description('Optional. Tags of the resource.')
+param tags object?
+
+@description('Optional. List of allowed FQDN.')
+param allowedFqdnList array?
+
+@description('Optional. The API properties for special APIs.')
+param apiProperties object?
+
+@description('Optional. Allow only Azure AD authentication. Should be enabled for security reasons.')
+param disableLocalAuth bool = true
+
+@description('Optional. The flag to enable dynamic throttling.')
+param dynamicThrottlingEnabled bool = false
+
+@secure()
+@description('Optional. Resource migration token.')
+param migrationToken string?
+
+@description('Optional. Restore a soft-deleted cognitive service at deployment time. Will fail if no such soft-deleted resource exists.')
+param restore bool = false
+
+@description('Optional. Restrict outbound network access.')
+param restrictOutboundNetworkAccess bool = true
+
+@description('Optional. The storage accounts for this resource.')
+param userOwnedStorage array?
+
+import { managedIdentityAllType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. The managed identity definition for this resource.')
+param managedIdentities managedIdentityAllType?
+
+@description('Optional. Array of deployments about cognitive service accounts to create.')
+param deployments deploymentType[]?
+
+var enableReferencedModulesTelemetry = false
+
+var formattedUserAssignedIdentities = reduce(
+ map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }),
+ {},
+ (cur, next) => union(cur, next)
+) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} }
+var identity = !empty(managedIdentities)
+ ? {
+ type: (managedIdentities.?systemAssigned ?? false)
+ ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned')
+ : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : null)
+ userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null
+ }
+ : null
+
+var builtInRoleNames = {
+ 'Cognitive Services Contributor': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68'
+ )
+ 'Cognitive Services Custom Vision Contributor': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3'
+ )
+ 'Cognitive Services Custom Vision Deployment': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '5c4089e1-6d96-4d2f-b296-c1bc7137275f'
+ )
+ 'Cognitive Services Custom Vision Labeler': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '88424f51-ebe7-446f-bc41-7fa16989e96c'
+ )
+ 'Cognitive Services Custom Vision Reader': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '93586559-c37d-4a6b-ba08-b9f0940c2d73'
+ )
+ 'Cognitive Services Custom Vision Trainer': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '0a5ae4ab-0d65-4eeb-be61-29fc9b54394b'
+ )
+ 'Cognitive Services Data Reader (Preview)': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'b59867f0-fa02-499b-be73-45a86b5b3e1c'
+ )
+ 'Cognitive Services Face Recognizer': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '9894cab4-e18a-44aa-828b-cb588cd6f2d7'
+ )
+ 'Cognitive Services Immersive Reader User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'b2de6794-95db-4659-8781-7e080d3f2b9d'
+ )
+ 'Cognitive Services Language Owner': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'f07febfe-79bc-46b1-8b37-790e26e6e498'
+ )
+ 'Cognitive Services Language Reader': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '7628b7b8-a8b2-4cdc-b46f-e9b35248918e'
+ )
+ 'Cognitive Services Language Writer': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8'
+ )
+ 'Cognitive Services LUIS Owner': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'f72c8140-2111-481c-87ff-72b910f6e3f8'
+ )
+ 'Cognitive Services LUIS Reader': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '18e81cdc-4e98-4e29-a639-e7d10c5a6226'
+ )
+ 'Cognitive Services LUIS Writer': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '6322a993-d5c9-4bed-b113-e49bbea25b27'
+ )
+ 'Cognitive Services Metrics Advisor Administrator': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'cb43c632-a144-4ec5-977c-e80c4affc34a'
+ )
+ 'Cognitive Services Metrics Advisor User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '3b20f47b-3825-43cb-8114-4bd2201156a8'
+ )
+ 'Cognitive Services OpenAI Contributor': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'a001fd3d-188f-4b5d-821b-7da978bf7442'
+ )
+ 'Cognitive Services OpenAI User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'
+ )
+ 'Cognitive Services QnA Maker Editor': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'f4cc2bf9-21be-47a1-bdf1-5c5804381025'
+ )
+ 'Cognitive Services QnA Maker Reader': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '466ccd10-b268-4a11-b098-b4849f024126'
+ )
+ 'Cognitive Services Speech Contributor': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '0e75ca1e-0464-4b4d-8b93-68208a576181'
+ )
+ 'Cognitive Services Speech User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'f2dc8367-1007-4938-bd23-fe263f013447'
+ )
+ 'Cognitive Services User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'a97b65f3-24c7-4388-baec-2e87135dc908'
+ )
+ Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
+ Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
+ Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
+ 'Role Based Access Control Administrator': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'f58310d9-a9f6-439a-9e8d-f62e7b41a168'
+ )
+ 'User Access Administrator': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'
+ )
+}
+
+var formattedRoleAssignments = [
+ for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, {
+ roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains(
+ roleAssignment.roleDefinitionIdOrName,
+ '/providers/Microsoft.Authorization/roleDefinitions/'
+ )
+ ? roleAssignment.roleDefinitionIdOrName
+ : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName))
+ })
+]
+
+resource cognitiveService 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
+ name: name
+ kind: kind
+ identity: identity
+ location: location
+ tags: tags
+ sku: {
+ name: sku
+ }
+ properties: {
+ customSubDomainName: customSubDomainName
+ allowProjectManagement: true
+ networkAcls: !empty(networkAcls ?? {})
+ ? {
+ defaultAction: networkAcls.?defaultAction
+ virtualNetworkRules: networkAcls.?virtualNetworkRules ?? []
+ ipRules: networkAcls.?ipRules ?? []
+ }
+ : null
+ publicNetworkAccess: publicNetworkAccess != null
+ ? publicNetworkAccess
+ : (!empty(networkAcls) ? 'Enabled' : 'Disabled')
+ allowedFqdnList: allowedFqdnList
+ apiProperties: apiProperties
+ disableLocalAuth: disableLocalAuth
+ #disable-next-line BCP036
+ networkInjections: networkInjectionSubnetResourceId != null
+ ? [
+ {
+ scenario: 'agent'
+ subnetArmId: networkInjectionSubnetResourceId
+ useMicrosoftManagedNetwork: false
+ }
+ ]
+ : null
+ // true is not supported today
+ encryption: null // Customer managed key encryption is used, but the property is required.
+ migrationToken: migrationToken
+ restore: restore
+ restrictOutboundNetworkAccess: restrictOutboundNetworkAccess
+ userOwnedStorage: userOwnedStorage
+ dynamicThrottlingEnabled: dynamicThrottlingEnabled
+ }
+}
+
+@batchSize(1)
+resource cognitiveService_deployments 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = [
+ for (deployment, index) in (deployments ?? []): {
+ parent: cognitiveService
+ name: deployment.?name ?? '${name}-deployments'
+ properties: {
+ model: deployment.model
+ raiPolicyName: deployment.?raiPolicyName
+ versionUpgradeOption: deployment.?versionUpgradeOption
+ }
+ sku: deployment.?sku ?? {
+ name: sku
+ capacity: sku.?capacity
+ tier: sku.?tier
+ size: sku.?size
+ family: sku.?family
+ }
+ }
+]
+
+#disable-next-line use-recent-api-versions
+resource cognitiveService_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [
+ for (diagnosticSetting, index) in (diagnosticSettings ?? []): {
+ name: diagnosticSetting.?name ?? '${name}-diagnosticSettings'
+ properties: {
+ storageAccountId: diagnosticSetting.?storageAccountResourceId
+ workspaceId: diagnosticSetting.?workspaceResourceId
+ eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId
+ eventHubName: diagnosticSetting.?eventHubName
+ metrics: [
+ for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): {
+ category: group.category
+ enabled: group.?enabled ?? true
+ timeGrain: null
+ }
+ ]
+ logs: [
+ for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): {
+ categoryGroup: group.?categoryGroup
+ category: group.?category
+ enabled: group.?enabled ?? true
+ }
+ ]
+ marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId
+ logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType
+ }
+ scope: cognitiveService
+ }
+]
+
+module cognitiveService_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.0' = [
+ for (privateEndpoint, index) in (privateEndpoints ?? []): {
+ name: take('${uniqueString(deployment().name, location)}-cognitiveService-PrivateEndpoint-${index}', 64)
+ scope: resourceGroup(
+ split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[2],
+ split(privateEndpoint.?resourceGroupResourceId ?? resourceGroup().id, '/')[4]
+ )
+ params: {
+ name: privateEndpoint.?name ?? 'pep-${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}'
+ privateLinkServiceConnections: privateEndpoint.?isManualConnection != true
+ ? [
+ {
+ name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}'
+ properties: {
+ privateLinkServiceId: cognitiveService.id
+ groupIds: [
+ privateEndpoint.?service ?? 'account'
+ ]
+ }
+ }
+ ]
+ : null
+ manualPrivateLinkServiceConnections: privateEndpoint.?isManualConnection == true
+ ? [
+ {
+ name: privateEndpoint.?privateLinkServiceConnectionName ?? '${last(split(cognitiveService.id, '/'))}-${privateEndpoint.?service ?? 'account'}-${index}'
+ properties: {
+ privateLinkServiceId: cognitiveService.id
+ groupIds: [
+ privateEndpoint.?service ?? 'account'
+ ]
+ requestMessage: privateEndpoint.?manualConnectionRequestMessage ?? 'Manual approval required.'
+ }
+ }
+ ]
+ : null
+ subnetResourceId: privateEndpoint.subnetResourceId
+ enableTelemetry: enableReferencedModulesTelemetry
+ location: privateEndpoint.?location ?? reference(
+ split(privateEndpoint.subnetResourceId, '/subnets/')[0],
+ '2020-06-01',
+ 'Full'
+ ).location
+ privateDnsZoneGroup: privateEndpoint.?privateDnsZoneGroup
+ roleAssignments: privateEndpoint.?roleAssignments
+ tags: privateEndpoint.?tags ?? tags
+ customDnsConfigs: privateEndpoint.?customDnsConfigs
+ ipConfigurations: privateEndpoint.?ipConfigurations
+ applicationSecurityGroupResourceIds: privateEndpoint.?applicationSecurityGroupResourceIds
+ customNetworkInterfaceName: privateEndpoint.?customNetworkInterfaceName
+ }
+ }
+]
+
+resource cognitiveService_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [
+ for (roleAssignment, index) in (formattedRoleAssignments ?? []): {
+ name: roleAssignment.?name ?? guid(cognitiveService.id, roleAssignment.principalId, roleAssignment.roleDefinitionId)
+ properties: {
+ roleDefinitionId: roleAssignment.roleDefinitionId
+ principalId: roleAssignment.principalId
+ description: roleAssignment.?description
+ principalType: roleAssignment.?principalType
+ condition: roleAssignment.?condition
+ conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set
+ delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId
+ }
+ scope: cognitiveService
+ }
+]
+
+@description('The name of the cognitive services account.')
+output name string = cognitiveService.name
+
+@description('The resource ID of the cognitive services account.')
+output resourceId string = cognitiveService.id
+
+@description('The resource group the cognitive services account was deployed into.')
+output resourceGroupName string = resourceGroup().name
+
+@description('The service endpoint of the cognitive services account.')
+output endpoint string = cognitiveService.properties.endpoint
+
+@description('All endpoints available for the cognitive services account, types depends on the cognitive service kind.')
+output endpoints endpointType = cognitiveService.properties.endpoints
+
+@description('The principal ID of the system assigned identity.')
+output systemAssignedMIPrincipalId string? = cognitiveService.?identity.?principalId
+
+@description('The location the resource was deployed into.')
+output location string = cognitiveService.location
+
+@description('The private endpoints of the congitive services account.')
+output privateEndpoints privateEndpointOutputType[] = [
+ for (pe, index) in (privateEndpoints ?? []): {
+ name: cognitiveService_privateEndpoints[index].outputs.name
+ resourceId: cognitiveService_privateEndpoints[index].outputs.resourceId
+ groupId: cognitiveService_privateEndpoints[index].outputs.?groupId!
+ customDnsConfigs: cognitiveService_privateEndpoints[index].outputs.customDnsConfigs
+ networkInterfaceResourceIds: cognitiveService_privateEndpoints[index].outputs.networkInterfaceResourceIds
+ }
+]
+
+// ================ //
+// Definitions //
+// ================ //
+
+@export()
+@description('The type for the private endpoint output.')
+type privateEndpointOutputType = {
+ @description('The name of the private endpoint.')
+ name: string
+
+ @description('The resource ID of the private endpoint.')
+ resourceId: string
+
+ @description('The group Id for the private endpoint Group.')
+ groupId: string?
+
+ @description('The custom DNS configurations of the private endpoint.')
+ customDnsConfigs: {
+ @description('FQDN that resolves to private endpoint IP address.')
+ fqdn: string?
+
+ @description('A list of private IP addresses of the private endpoint.')
+ ipAddresses: string[]
+ }[]
+
+ @description('The IDs of the network interfaces associated with the private endpoint.')
+ networkInterfaceResourceIds: string[]
+}
+
+@export()
+@description('The type for a cognitive services account deployment.')
+type deploymentType = {
+ @description('Optional. Specify the name of cognitive service account deployment.')
+ name: string?
+
+ @description('Required. Properties of Cognitive Services account deployment model.')
+ model: {
+ @description('Required. The name of Cognitive Services account deployment model.')
+ name: string
+
+ @description('Required. The format of Cognitive Services account deployment model.')
+ format: string
+
+ @description('Required. The version of Cognitive Services account deployment model.')
+ version: string
+ }
+
+ @description('Optional. The resource model definition representing SKU.')
+ sku: {
+ @description('Required. The name of the resource model definition representing SKU.')
+ name: string
+
+ @description('Optional. The capacity of the resource model definition representing SKU.')
+ capacity: int?
+
+ @description('Optional. The tier of the resource model definition representing SKU.')
+ tier: string?
+
+ @description('Optional. The size of the resource model definition representing SKU.')
+ size: string?
+
+ @description('Optional. The family of the resource model definition representing SKU.')
+ family: string?
+ }?
+
+ @description('Optional. The name of RAI policy.')
+ raiPolicyName: string?
+
+ @description('Optional. The version upgrade option.')
+ versionUpgradeOption: string?
+}
+
+@export()
+@description('The type for a cognitive services account endpoint.')
+type endpointType = {
+ @description('Type of the endpoint.')
+ name: string?
+ @description('The endpoint URI.')
+ endpoint: string?
+}
+
+@export()
+@description('The type of the secrets exported to the provided Key Vault.')
+type secretsExportConfigurationType = {
+ @description('Required. The key vault name where to store the keys and connection strings generated by the modules.')
+ keyVaultResourceId: string
+
+ @description('Optional. The name for the accessKey1 secret to create.')
+ accessKey1Name: string?
+
+ @description('Optional. The name for the accessKey2 secret to create.')
+ accessKey2Name: string?
+}
diff --git a/infra/modules/ai-foundry/main.bicep b/infra/modules/ai-foundry/main.bicep
new file mode 100644
index 00000000..1058d2cc
--- /dev/null
+++ b/infra/modules/ai-foundry/main.bicep
@@ -0,0 +1,250 @@
+metadata name = 'AI Services and Project Module'
+metadata description = 'This module creates an AI Services resource and an AI Foundry project within it. It supports private networking, OpenAI deployments, and role assignments.'
+
+@description('Required. Name of the Cognitive Services resource. Must be unique in the resource group.')
+param name string
+
+@description('Optional. The location of the Cognitive Services resource.')
+param location string // this should be passed
+
+@description('Optional. Kind of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.')
+@allowed([
+ 'AIServices'
+ 'AnomalyDetector'
+ 'CognitiveServices'
+ 'ComputerVision'
+ 'ContentModerator'
+ 'ContentSafety'
+ 'ConversationalLanguageUnderstanding'
+ 'CustomVision.Prediction'
+ 'CustomVision.Training'
+ 'Face'
+ 'FormRecognizer'
+ 'HealthInsights'
+ 'ImmersiveReader'
+ 'Internal.AllInOne'
+ 'LUIS'
+ 'LUIS.Authoring'
+ 'LanguageAuthoring'
+ 'MetricsAdvisor'
+ 'OpenAI'
+ 'Personalizer'
+ 'QnAMaker.v2'
+ 'SpeechServices'
+ 'TextAnalytics'
+ 'TextTranslation'
+])
+param kind string = 'AIServices'
+
+@description('Optional. The SKU of the Cognitive Services account. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.')
+@allowed([
+ 'S'
+ 'S0'
+ 'S1'
+ 'S2'
+ 'S3'
+ 'S4'
+ 'S5'
+ 'S6'
+ 'S7'
+ 'S8'
+])
+param sku string = 'S0'
+
+@description('Required. The name of the AI Foundry project to create.')
+param projectName string
+
+@description('Optional. The description of the AI Foundry project to create.')
+param projectDescription string = projectName
+
+@description('Optional. The resource ID of the Log Analytics workspace to use for diagnostic settings.')
+param logAnalyticsWorkspaceResourceId string?
+
+import { deploymentType } from 'br/public:avm/res/cognitive-services/account:0.10.2'
+@description('Optional. Specifies the OpenAI deployments to create.')
+param deployments deploymentType[] = []
+
+import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Array of role assignments to create.')
+param roleAssignments roleAssignmentType[] = []
+
+@description('Optional. Values to establish private networking for the AI Services resource.')
+param privateNetworking aiServicesPrivateNetworkingType?
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+module cognitiveServicesPrivateDnsZone '../privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?cogServicesPrivateDnsZoneResourceId)) {
+ name: take('${name}-cognitiveservices-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.cognitiveservices.${toLower(environment().name) == 'azureusgovernment' ? 'azure.us' : 'azure.com'}'
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+module openAiPrivateDnsZone '../privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?openAIPrivateDnsZoneResourceId)) {
+ name: take('${name}-openai-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.openai.${toLower(environment().name) == 'azureusgovernment' ? 'azure.us' : 'azure.com'}'
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+module aiServicesPrivateDnsZone '../privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?aiServicesPrivateDnsZoneResourceId)) {
+ name: take('${name}-ai-services-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.services.ai.${toLower(environment().name) == 'azureusgovernment' ? 'azure.us' : 'azure.com'}'
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+var cogServicesPrivateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?cogServicesPrivateDnsZoneResourceId)
+ ? cognitiveServicesPrivateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?cogServicesPrivateDnsZoneResourceId)
+ : ''
+var openAIPrivateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?openAIPrivateDnsZoneResourceId)
+ ? openAiPrivateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?openAIPrivateDnsZoneResourceId)
+ : ''
+
+var aiServicesPrivateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?aiServicesPrivateDnsZoneResourceId)
+ ? aiServicesPrivateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?aiServicesPrivateDnsZoneResourceId)
+ : ''
+
+module cognitiveService 'ai-services.bicep' = {
+ name: take('${name}-aiservices-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [cognitiveServicesPrivateDnsZone, openAiPrivateDnsZone, aiServicesPrivateDnsZone] // required due to optional flags that could change dependency
+ params: {
+ name: name
+ location: location
+ tags: tags
+ sku: sku
+ kind: kind
+ managedIdentities: {
+ systemAssigned: true
+ }
+ deployments: deployments
+ customSubDomainName: name
+ disableLocalAuth: false
+ publicNetworkAccess: privateNetworking != null ? 'Disabled' : 'Enabled'
+ // rules to allow firewall and virtual network access
+ networkAcls: {
+ defaultAction: 'Allow'
+ virtualNetworkRules: []
+ ipRules: []
+ }
+ diagnosticSettings: !empty(logAnalyticsWorkspaceResourceId)
+ ? [
+ {
+ workspaceResourceId: logAnalyticsWorkspaceResourceId
+ }
+ ]
+ : []
+ roleAssignments: roleAssignments
+ privateEndpoints: privateNetworking != null
+ ? [
+ {
+ name:'pep-${name}-aiservices' // private endpoint name
+ customNetworkInterfaceName: 'nic-${name}-aiservices'
+ subnetResourceId: privateNetworking.?subnetResourceId ?? ''
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ {
+ privateDnsZoneResourceId: cogServicesPrivateDnsZoneResourceId
+ }
+ {
+ privateDnsZoneResourceId: openAIPrivateDnsZoneResourceId
+ }
+ {
+ privateDnsZoneResourceId: aiServicesPrivateDnsZoneResourceId
+ }
+ ]
+ }
+ }
+ ]
+ : []
+ }
+}
+
+
+module aiProject 'project.bicep' = {
+ name: take('${name}-ai-project-${projectName}-deployment', 64)
+ params: {
+ name: projectName
+ desc: projectDescription
+ aiServicesName: cognitiveService.outputs.name
+ location: location
+ roleAssignments: roleAssignments
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+@description('The resource group the resources were deployed into.')
+output resourceGroupName string = resourceGroup().name
+
+@description('Name of the Cognitive Services resource.')
+output name string = cognitiveService.outputs.name
+
+@description('Resource ID of the Cognitive Services resource.')
+output resourceId string = cognitiveService.outputs.resourceId
+
+@description('Principal ID of the system assigned managed identity for the Cognitive Services resource. This is only available if the resource has a system assigned managed identity.')
+output systemAssignedMIPrincipalId string? = cognitiveService.outputs.?systemAssignedMIPrincipalId
+
+@description('The endpoint of the Cognitive Services resource.')
+output endpoint string = cognitiveService.outputs.endpoint
+
+import { aiProjectOutputType } from 'project.bicep'
+@description('AI Foundry Project information.')
+output project aiProjectOutputType = {
+ name: aiProject.name
+ resourceId: aiProject.outputs.resourceId
+ apiEndpoint: aiProject.outputs.apiEndpoint
+}
+
+@export()
+@description('A custom AVM-aligned type for a role assignment for AI Services and Project.')
+type aiServicesRoleAssignmentType = {
+ @description('Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated.')
+ name: string?
+
+ @description('Required. The role to assign. You can provide either the role definition GUID or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.')
+ roleDefinitionId: string
+
+ @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.')
+ principalId: string
+
+ @description('Optional. The principal type of the assigned principal ID.')
+ principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')?
+}
+
+@export()
+@description('Values to establish private networking for resources that support createing private endpoints.')
+type aiServicesPrivateNetworkingType = {
+ @description('Required. The Resource ID of the virtual network.')
+ virtualNetworkResourceId: string
+
+ @description('Required. The Resource ID of the subnet to establish the Private Endpoint(s).')
+ subnetResourceId: string
+
+ @description('Optional. The Resource ID of an existing "cognitiveservices" Private DNS Zone Resource to link to the virtual network. If not provided, a new "cognitiveservices" Private DNS Zone(s) will be created.')
+ cogServicesPrivateDnsZoneResourceId: string?
+
+ @description('Optional. The Resource ID of an existing "openai" Private DNS Zone Resource to link to the virtual network. If not provided, a new "openai" Private DNS Zone(s) will be created.')
+ openAIPrivateDnsZoneResourceId: string?
+
+ @description('Optional. The Resource ID of an existing "services.ai" Private DNS Zone Resource to link to the virtual network. If not provided, a new "services.ai" Private DNS Zone(s) will be created.')
+ aiServicesPrivateDnsZoneResourceId: string?
+}
diff --git a/infra/modules/ai-foundry/project.bicep b/infra/modules/ai-foundry/project.bicep
new file mode 100644
index 00000000..17b4475f
--- /dev/null
+++ b/infra/modules/ai-foundry/project.bicep
@@ -0,0 +1,105 @@
+@description('Required. Name of the AI Services project.')
+param name string
+
+@description('Required. The location of the Project resource.')
+param location string = resourceGroup().location
+
+@description('Optional. The description of the AI Foundry project to create. Defaults to the project name.')
+param desc string = name
+
+@description('Required. Name of the existing Cognitive Services resource to create the AI Foundry project in.')
+param aiServicesName string
+
+import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Array of role assignments to create.')
+param roleAssignments roleAssignmentType[] = []
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+// using a few built-in roles here that makes sense for Foundry projects only
+var builtInRoleNames = {
+ 'Cognitive Services OpenAI Contributor': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ 'a001fd3d-188f-4b5d-821b-7da978bf7442'
+ )
+ 'Cognitive Services OpenAI User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'
+ )
+ 'Azure AI Developer': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '64702f94-c441-49e6-a78b-ef80e0188fee'
+ )
+ 'Azure AI User': subscriptionResourceId(
+ 'Microsoft.Authorization/roleDefinitions',
+ '53ca6127-db72-4b80-b1b0-d745d6d5456d'
+ )
+}
+
+var formattedRoleAssignments = [
+ for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, {
+ roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains(
+ roleAssignment.roleDefinitionIdOrName,
+ '/providers/Microsoft.Authorization/roleDefinitions/'
+ )
+ ? roleAssignment.roleDefinitionIdOrName
+ : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName))
+ })
+]
+
+resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = {
+ name: aiServicesName
+}
+
+resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
+ parent: cogServiceReference
+ name: name
+ tags: tags
+ location: location
+ identity: {
+ type: 'SystemAssigned'
+ }
+ properties: {
+ description: desc
+ displayName: name
+ }
+}
+
+module aiProjectRoleAssignement 'br/public:avm/ptn/authorization/resource-role-assignment:0.1.2' = [
+ for (roleAssignment, i) in formattedRoleAssignments: {
+ name: 'avm.ptn.authorization.resource-role-assignment.${uniqueString(name, roleAssignment.roleDefinitionId, roleAssignment.principalId)}'
+ params: {
+ roleDefinitionId: roleAssignment.roleDefinitionId
+ principalId: roleAssignment.principalId
+ principalType: 'ServicePrincipal'
+ resourceId: aiProject.id
+ enableTelemetry: enableTelemetry
+ }
+ }
+]
+
+@description('Name of the AI Foundry project.')
+output name string = aiProject.name
+
+@description('Resource ID of the AI Foundry project.')
+output resourceId string = aiProject.id
+
+@description('API endpoint for the AI Foundry project.')
+output apiEndpoint string = aiProject.properties.endpoints['AI Foundry API']
+
+@export()
+@description('Output type representing AI project information.')
+type aiProjectOutputType = {
+ @description('Required. Name of the AI project.')
+ name: string
+
+ @description('Required. Resource ID of the AI project.')
+ resourceId: string
+
+ @description('Required. API endpoint for the AI project.')
+ apiEndpoint: string
+}
diff --git a/infra/modules/cosmosDb.bicep b/infra/modules/cosmosDb.bicep
new file mode 100644
index 00000000..2fdb19c0
--- /dev/null
+++ b/infra/modules/cosmosDb.bicep
@@ -0,0 +1,175 @@
+@description('Required. Name of the Cosmos DB Account.')
+param name string
+
+@description('Required. Specifies the location for all the Azure resources.')
+param location string
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Required. Managed Identity princpial to assign data plane roles for the Cosmos DB Account.')
+param dataAccessIdentityPrincipalId string
+
+@description('Optional. The resource ID of an existing Log Analytics workspace to associate with AI Foundry for monitoring.')
+param logAnalyticsWorkspaceResourceId string?
+
+@description('Required. Indicates whether the single-region account is zone redundant. This property is ignored for multi-region accounts.')
+param zoneRedundant bool
+
+@description('Optional. The secondary location for the Cosmos DB Account for failover and multiple writes.')
+param secondaryLocation string?
+
+import { resourcePrivateNetworkingType } from 'customTypes.bicep'
+@description('Optional. Values to establish private networking for the Cosmos DB resource.')
+param privateNetworking resourcePrivateNetworkingType?
+
+import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Array of role assignments to create.')
+param roleAssignments roleAssignmentType[]?
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+module privateDnsZone 'privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?privateDnsZoneResourceId)) {
+ name: take('${name}-documents-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.documents.azure.com'
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+var privateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?privateDnsZoneResourceId)
+ ? privateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?privateDnsZoneResourceId ?? '')
+ : ''
+
+resource sqlContributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-11-15' existing = {
+ name: '${name}/00000000-0000-0000-0000-000000000002'
+}
+
+var databaseName = 'cmsadb'
+var batchContainerName = 'cmsabatch'
+var fileContainerName = 'cmsafile'
+var logContainerName = 'cmsalog'
+
+module cosmosAccount 'br/public:avm/res/document-db/database-account:0.15.0' = {
+ name: take('${name}-account-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [privateDnsZone] // required due to optional flags that could change dependency
+ params: {
+ name: name
+ enableAnalyticalStorage: true
+ location: location
+ minimumTlsVersion: 'Tls12'
+ defaultConsistencyLevel: 'Session'
+ networkRestrictions: {
+ networkAclBypass: 'AzureServices'
+ publicNetworkAccess: privateNetworking != null ? 'Disabled' : 'Enabled'
+ ipRules: []
+ virtualNetworkRules: []
+ }
+ zoneRedundant: zoneRedundant
+ automaticFailover: !empty(secondaryLocation)
+ failoverLocations: !empty(secondaryLocation)
+ ? [
+ {
+ failoverPriority: 0
+ isZoneRedundant: zoneRedundant
+ locationName: location
+ }
+ {
+ failoverPriority: 1
+ isZoneRedundant: zoneRedundant
+ locationName: secondaryLocation!
+ }
+ ]
+ : []
+ enableMultipleWriteLocations: !empty(secondaryLocation)
+ backupPolicyType: !empty(secondaryLocation) ? 'Periodic' : 'Continuous'
+ backupStorageRedundancy: zoneRedundant ? 'Zone' : 'Local'
+ disableKeyBasedMetadataWriteAccess: false
+ disableLocalAuthentication: privateNetworking != null
+ diagnosticSettings: !empty(logAnalyticsWorkspaceResourceId)
+ ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }]
+ : []
+ privateEndpoints: privateNetworking != null
+ ? [
+ {
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ {
+ privateDnsZoneResourceId: privateDnsZoneResourceId
+ }
+ ]
+ }
+ service: 'Sql'
+ subnetResourceId: privateNetworking.?subnetResourceId ?? ''
+ }
+ ]
+ : []
+ sqlDatabases: [
+ {
+ containers: [
+ {
+ indexingPolicy: {
+ automatic: true
+ }
+ name: batchContainerName
+ paths: [
+ '/batch_id'
+ ]
+ }
+ {
+ indexingPolicy: {
+ automatic: true
+ }
+ name: fileContainerName
+ paths: [
+ '/file_id'
+ ]
+ }
+ {
+ indexingPolicy: {
+ automatic: true
+ }
+ name: logContainerName
+ paths: [
+ '/log_id'
+ ]
+ }
+ ]
+ name: databaseName
+ }
+ ]
+ dataPlaneRoleAssignments: [
+ {
+ principalId: dataAccessIdentityPrincipalId
+ roleDefinitionId: sqlContributorRoleDefinition.id
+ }
+ ]
+ roleAssignments: roleAssignments
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+@description('Name of the Cosmos DB Account resource.')
+output name string = cosmosAccount.outputs.name
+
+@description('Resource ID of the Cosmos DB Account.')
+output resourceId string = cosmosAccount.outputs.resourceId
+
+@description('Endpoint of the Cosmos DB Account.')
+output endpoint string = cosmosAccount.outputs.endpoint
+
+@description('Name of the Cosmos DB database.')
+output databaseName string = databaseName
+
+@description('Complex object containing the names of the Cosmos DB containers.')
+output containerNames object = {
+ batch: batchContainerName
+ file: fileContainerName
+ log: logContainerName
+}
diff --git a/infra/modules/customTypes.bicep b/infra/modules/customTypes.bicep
new file mode 100644
index 00000000..95d15fec
--- /dev/null
+++ b/infra/modules/customTypes.bicep
@@ -0,0 +1,12 @@
+@export()
+@description('Values to establish private networking for resources that support createing private endpoints.')
+type resourcePrivateNetworkingType = {
+ @description('Required. The Resource ID of the virtual network.')
+ virtualNetworkResourceId: string
+
+ @description('Required. The Resource ID of the subnet to establish the Private Endpoint(s).')
+ subnetResourceId: string
+
+ @description('Optional. The Resource ID of an existing Private DNS Zone Resource to link to the virtual network. If not provided, a new Private DNS Zone(s) will be created.')
+ privateDnsZoneResourceId: string?
+}
diff --git a/infra/modules/fetch-container-image.bicep b/infra/modules/fetch-container-image.bicep
deleted file mode 100644
index 78d1e7ee..00000000
--- a/infra/modules/fetch-container-image.bicep
+++ /dev/null
@@ -1,8 +0,0 @@
-param exists bool
-param name string
-
-resource existingApp 'Microsoft.App/containerApps@2023-05-02-preview' existing = if (exists) {
- name: name
-}
-
-output containers array = exists ? existingApp.properties.template.containers : []
diff --git a/infra/modules/keyVault.bicep b/infra/modules/keyVault.bicep
new file mode 100644
index 00000000..880f3d0e
--- /dev/null
+++ b/infra/modules/keyVault.bicep
@@ -0,0 +1,103 @@
+@description('Required. Name of the Key Vault.')
+param name string
+
+@description('Required. Specifies the location for all the Azure resources.')
+param location string
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. Specifies the SKU for the vault.')
+@allowed([
+ 'premium'
+ 'standard'
+])
+param sku string = 'premium'
+
+@description('Optional. Resource ID of the Log Analytics workspace to use for diagnostic settings.')
+param logAnalyticsWorkspaceResourceId string?
+
+import { resourcePrivateNetworkingType } from 'customTypes.bicep'
+@description('Optional. Values to establish private networking for the Key Vault resource.')
+param privateNetworking resourcePrivateNetworkingType?
+
+import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Array of role assignments to create.')
+param roleAssignments roleAssignmentType[]?
+
+import { secretType } from 'br/public:avm/res/key-vault/vault:0.12.1'
+@description('Optional. Array of secrets to create in the Key Vault.')
+param secrets secretType[]?
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+module privateDnsZone 'privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?privateDnsZoneResourceId)) {
+ name: take('${name}-kv-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.${toLower(environment().name) == 'azureusgovernment' ? 'vaultcore.usgovcloudapi.net' : 'vaultcore.azure.net'}'
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+var privateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?privateDnsZoneResourceId)
+ ? privateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?privateDnsZoneResourceId ?? '')
+ : ''
+
+module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = {
+ name: take('${name}-kv-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [privateDnsZone] // required due to optional flags that could change dependency
+ params: {
+ name: name
+ location: location
+ tags: tags
+ createMode: 'default'
+ sku: sku
+ publicNetworkAccess: privateNetworking != null ? 'Disabled' : 'Enabled'
+ networkAcls: {
+ defaultAction: 'Allow'
+ }
+ enableVaultForDeployment: true
+ enableVaultForDiskEncryption: true
+ enableVaultForTemplateDeployment: true
+ enablePurgeProtection: false
+ enableRbacAuthorization: true
+ enableSoftDelete: true
+ softDeleteRetentionInDays: 7
+ diagnosticSettings: !empty(logAnalyticsWorkspaceResourceId)
+ ? [
+ {
+ workspaceResourceId: logAnalyticsWorkspaceResourceId
+ }
+ ]
+ : []
+ privateEndpoints: privateNetworking != null
+ ? [
+ {
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ {
+ privateDnsZoneResourceId: privateDnsZoneResourceId
+ }
+ ]
+ }
+ service: 'vault'
+ subnetResourceId: privateNetworking.?subnetResourceId ?? ''
+ }
+ ]
+ : []
+ roleAssignments: roleAssignments
+ secrets: secrets
+ enableTelemetry: enableTelemetry
+ }
+}
+
+@description('Name of the Key Vault resource.')
+output name string = keyvault.outputs.name
+
+@description('Resource ID of the Key Vault.')
+output resourceId string = keyvault.outputs.resourceId
diff --git a/infra/modules/network.bicep b/infra/modules/network.bicep
new file mode 100644
index 00000000..b4e252f8
--- /dev/null
+++ b/infra/modules/network.bicep
@@ -0,0 +1,175 @@
+@description('Required. Named used for all resource naming.')
+param resourcesName string
+
+@description('Required. Resource ID of the Log Analytics Workspace for monitoring and diagnostics.')
+param logAnalyticsWorkSpaceResourceId string
+
+@minLength(3)
+@description('Required. Azure region for all services.')
+param location string
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+@description('Required. Admin username for the VM.')
+@secure()
+param vmAdminUsername string
+
+@description('Required. Admin password for the VM.')
+@secure()
+param vmAdminPassword string
+
+// Subnet Classless Inter-Doman Routing (CIDR) Sizing Reference Table (Best Practices)
+// | CIDR | # of Addresses | # of /24s | Notes |
+// |-----------|---------------|-----------|----------------------------------------|
+// | /24 | 256 | 1 | Smallest recommended for Azure subnets |
+// | /23 | 512 | 2 | Good for 1-2 workloads per subnet |
+// | /22 | 1024 | 4 | Good for 2-4 workloads per subnet |
+// | /21 | 2048 | 8 | |
+// | /20 | 4096 | 16 | Used for default VNet in this solution |
+// | /19 | 8192 | 32 | |
+// | /18 | 16384 | 64 | |
+// | /17 | 32768 | 128 | |
+// | /16 | 65536 | 256 | |
+// | /15 | 131072 | 512 | |
+// | /14 | 262144 | 1024 | |
+// | /13 | 524288 | 2048 | |
+// | /12 | 1048576 | 4096 | |
+// | /11 | 2097152 | 8192 | |
+// | /10 | 4194304 | 16384 | |
+// | /9 | 8388608 | 32768 | |
+// | /8 | 16777216 | 65536 | |
+//
+// Best Practice Notes:
+// - Use /24 as the minimum subnet size for Azure (smaller subnets are not supported for most services).
+// - Plan for future growth: allocate larger address spaces (e.g., /20 or /21 for VNets) to allow for new subnets.
+// - Avoid overlapping address spaces with on-premises or other VNets.
+// - Use contiguous, non-overlapping ranges for subnets.
+// - Document subnet usage and purpose in code comments.
+// - For AVM modules, ensure only one delegation per subnet and leave delegations empty if not required.
+
+module network 'network/main.bicep' = {
+ name: take('network-${resourcesName}-create', 64)
+ params: {
+ resourcesName: resourcesName
+ location: location
+ logAnalyticsWorkSpaceResourceId: logAnalyticsWorkSpaceResourceId
+ tags: tags
+ addressPrefixes: ['10.0.0.0/20'] // 4096 addresses (enough for 8 /23 subnets or 16 /24)
+ subnets: [
+ // Only one delegation per subnet is supported by the AVM module as of June 2025.
+ // For subnets that do not require delegation, leave the value empty.
+ {
+ name: 'web'
+ addressPrefixes: ['10.0.0.0/23'] // /23 (10.0.0.0 - 10.0.1.255), 512 addresses
+ networkSecurityGroup: {
+ name: 'web-nsg'
+ securityRules: [
+ {
+ name: 'AllowHttpsInbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '443'
+ sourceAddressPrefixes: ['0.0.0.0/0']
+ destinationAddressPrefixes: ['10.0.0.0/23']
+ }
+ }
+ {
+ name: 'AllowIntraSubnetTraffic'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 200
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefixes: ['10.0.0.0/23'] // From same subnet
+ destinationAddressPrefixes: ['10.0.0.0/23'] // To same subnet
+ }
+ }
+ {
+ name: 'AllowAzureLoadBalancer'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 300
+ protocol: '*'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefix: 'AzureLoadBalancer'
+ destinationAddressPrefix: '10.0.0.0/23'
+ }
+ }
+ ]
+ }
+ delegation: 'Microsoft.App/environments'
+ }
+ {
+ name: 'peps'
+ addressPrefixes: ['10.0.2.0/23'] // /23 (10.0.2.0 - 10.0.3.255), 512 addresses
+ privateEndpointNetworkPolicies: 'Disabled'
+ privateLinkServiceNetworkPolicies: 'Disabled'
+ }
+ ]
+ bastionConfiguration: {
+ name: 'bastion-${resourcesName}'
+ subnetAddressPrefixes: ['10.0.10.0/26']
+ }
+ jumpboxConfiguration: {
+ name: 'vm-jumpbox-${resourcesName}'
+ size: 'Standard_D2s_v3'
+ username: vmAdminUsername
+ password: vmAdminPassword
+ subnet: {
+ name: 'jumpbox'
+ addressPrefixes: ['10.0.12.0/23'] // /23 (10.0.12.0 - 10.0.13.255), 512 addresses
+ networkSecurityGroup: {
+ name: 'jumpbox-nsg'
+ securityRules: [
+ {
+ name: 'AllowRdpFromBastion'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '3389'
+ sourceAddressPrefixes: [
+ '10.0.10.0/26' // Azure Bastion subnet
+ ]
+ destinationAddressPrefixes: ['10.0.12.0/23']
+ }
+ }
+ ]
+ }
+ }
+ }
+ enableTelemetry: enableTelemetry
+ }
+}
+
+@description('Name of the Virtual Network resource.')
+output vnetName string = network.outputs.vnetName
+
+@description('Resource ID of the Virtual Network.')
+output vnetResourceId string = network.outputs.vnetResourceId
+
+@description('Resource ID of the "web" subnet.')
+output subnetWebResourceId string = first(filter(network.outputs.subnets, s => s.name == 'web')).?resourceId ?? ''
+
+@description('Resource ID of the "peps" subnet for Private Endpoints.')
+output subnetPrivateEndpointsResourceId string = first(filter(network.outputs.subnets, s => s.name == 'peps')).?resourceId ?? ''
+
+@description('Resource ID of the Bastion Host.')
+output bastionResourceId string = network.outputs.bastionHostId
+
+@description('Resource ID of the Jumpbox VM.')
+output jumpboxResourceId string = network.outputs.jumpboxResourceId
diff --git a/infra/modules/network/bastionHost.bicep b/infra/modules/network/bastionHost.bicep
new file mode 100644
index 00000000..423fe57b
--- /dev/null
+++ b/infra/modules/network/bastionHost.bicep
@@ -0,0 +1,81 @@
+// /****************************************************************************************************************************/
+// Create Azure Bastion Subnet and Azure Bastion Host
+// /****************************************************************************************************************************/
+
+@description('Name of the Azure Bastion Host resource.')
+param name string
+
+@description('Azure region to deploy resources.')
+param location string = resourceGroup().location
+
+@description('Conditional. List of address prefixes for the subnet. Leave empty to skip subnet creation.')
+param subnetAddressPrefixes string[]?
+
+@description('Resource ID of the Virtual Network where the Azure Bastion Host will be deployed.')
+param vnetId string
+
+@description('Name of the Virtual Network where the Azure Bastion Host will be deployed.')
+param vnetName string
+
+@description('Resource ID of the Log Analytics Workspace for monitoring and diagnostics.')
+param logAnalyticsWorkspaceId string
+
+@description('Optional. Tags to apply to the resources.')
+param tags object = {}
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+// 1. Create Azure Bastion Host using AVM Subnet Module with special config for Azure Bastion Subnet
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network/subnet
+module bastionSubnet 'br/public:avm/res/network/virtual-network/subnet:0.1.2' = if (!empty(subnetAddressPrefixes)) {
+ name: take('bastionSubnet-${vnetName}', 64)
+ params: {
+ virtualNetworkName: vnetName
+ name: 'AzureBastionSubnet' // this name required as is for Azure Bastion Host subnet
+ addressPrefixes: subnetAddressPrefixes
+ enableTelemetry: enableTelemetry
+ }
+}
+
+// 2. Create Azure Bastion Host in AzureBastionsubnetSubnet using AVM Bastion Host module
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/bastion-host
+
+module bastionHost 'br/public:avm/res/network/bastion-host:0.6.1' = {
+ name: take('bastionHost-${vnetName}-${name}', 64)
+ params: {
+ name: name
+ skuName: 'Standard'
+ location: location
+ virtualNetworkResourceId: vnetId
+ diagnosticSettings: [
+ {
+ name: 'bastionDiagnostics'
+ workspaceResourceId: logAnalyticsWorkspaceId
+ logCategoriesAndGroups: [
+ {
+ categoryGroup: 'allLogs'
+ enabled: true
+ }
+ ]
+ }
+ ]
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+output resourceId string = bastionHost.outputs.resourceId
+output name string = bastionHost.outputs.name
+output subnetId string = bastionSubnet.outputs.resourceId
+output subnetName string = bastionSubnet.outputs.name
+
+@export()
+@description('Custom type definition for establishing Bastion Host for remote connection.')
+type bastionHostConfigurationType = {
+ @description('The name of the Bastion Host resource.')
+ name: string
+
+ @description('Optional. List of address prefixes for the subnet.')
+ subnetAddressPrefixes: string[]?
+}
diff --git a/infra/modules/network/jumpbox.bicep b/infra/modules/network/jumpbox.bicep
new file mode 100644
index 00000000..b1280a95
--- /dev/null
+++ b/infra/modules/network/jumpbox.bicep
@@ -0,0 +1,154 @@
+// /****************************************************************************************************************************/
+// Create Jumpbox NSG and Jumpbox Subnet, then create Jumpbox VM
+// /****************************************************************************************************************************/
+
+@description('Name of the Jumpbox Virtual Machine.')
+param name string
+
+@description('Azure region to deploy resources.')
+param location string = resourceGroup().location
+
+@description('Name of the Virtual Network where the Jumpbox VM will be deployed.')
+param vnetName string
+
+@description('Size of the Jumpbox Virtual Machine.')
+param size string
+
+import { subnetType } from 'virtualNetwork.bicep'
+@description('Optional. Subnet configuration for the Jumpbox VM.')
+param subnet subnetType?
+
+@description('Username to access the Jumpbox VM.')
+param username string
+
+@secure()
+@description('Password to access the Jumpbox VM.')
+param password string
+
+@description('Optional. Tags to apply to the resources.')
+param tags object = {}
+
+@description('Log Analytics Workspace Resource ID for VM diagnostics.')
+param logAnalyticsWorkspaceId string
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+// 1. Create Jumpbox NSG
+// using AVM Network Security Group module
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group
+module nsg 'br/public:avm/res/network/network-security-group:0.5.1' = if (!empty(subnet)) {
+ name: '${vnetName}-${subnet.?networkSecurityGroup.name}'
+ params: {
+ name: '${vnetName}-${subnet.?networkSecurityGroup.name}'
+ location: location
+ securityRules: subnet.?networkSecurityGroup.securityRules
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+// 2. Create Jumpbox subnet as part of the existing VNet
+// using AVM Virtual Network Subnet module
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network/subnet
+module subnetResource 'br/public:avm/res/network/virtual-network/subnet:0.1.2' = if (!empty(subnet)) {
+ name: subnet.?name ?? '${vnetName}-jumpbox-subnet'
+ params: {
+ virtualNetworkName: vnetName
+ name: subnet.?name ?? ''
+ addressPrefixes: subnet.?addressPrefixes
+ networkSecurityGroupResourceId: nsg.outputs.resourceId
+ enableTelemetry: enableTelemetry
+ }
+}
+
+// 3. Create Jumpbox VM
+// using AVM Virtual Machine module
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/compute/virtual-machine
+var vmName = take(name, 15) // Shorten VM name to 15 characters to avoid Azure limits
+
+module vm 'br/public:avm/res/compute/virtual-machine:0.15.0' = {
+ name: take('${vmName}-jumpbox', 64)
+ params: {
+ name: vmName
+ vmSize: size
+ location: location
+ adminUsername: username
+ adminPassword: password
+ tags: tags
+ zone: 2
+ imageReference: {
+ offer: 'WindowsServer'
+ publisher: 'MicrosoftWindowsServer'
+ sku: '2019-datacenter'
+ version: 'latest'
+ }
+ osType: 'Windows'
+ osDisk: {
+ managedDisk: {
+ storageAccountType: 'Standard_LRS'
+ }
+ }
+ encryptionAtHost: false // Some Azure subscriptions do not support encryption at host
+ nicConfigurations: [
+ {
+ name: '${vmName}-nic'
+ ipConfigurations: [
+ {
+ name: 'ipconfig1'
+ subnetResourceId: subnetResource.outputs.resourceId
+ }
+ ]
+ networkSecurityGroupResourceId: nsg.outputs.resourceId
+ diagnosticSettings: [
+ {
+ name: 'jumpboxDiagnostics'
+ workspaceResourceId: logAnalyticsWorkspaceId
+ logCategoriesAndGroups: [
+ {
+ categoryGroup: 'allLogs'
+ enabled: true
+ }
+ ]
+ metricCategories: [
+ {
+ category: 'AllMetrics'
+ enabled: true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ enableTelemetry: enableTelemetry
+ }
+}
+
+output resourceId string = vm.outputs.resourceId
+output name string = vm.outputs.name
+output location string = vm.outputs.location
+
+output subnetId string = subnetResource.outputs.resourceId
+output subnetName string = subnetResource.outputs.name
+output nsgId string = nsg.outputs.resourceId
+output nsgName string = nsg.outputs.name
+
+@export()
+@description('Custom type definition for establishing Jumpbox Virtual Machine and its associated resources.')
+type jumpBoxConfigurationType = {
+ @description('The name of the Virtual Machine.')
+ name: string
+
+ @description('The size of the VM.')
+ size: string?
+
+ @description('Username to access VM.')
+ username: string
+
+ @secure()
+ @description('Password to access VM.')
+ password: string
+
+ @description('Optional. Subnet configuration for the Jumpbox VM.')
+ subnet: subnetType?
+}
diff --git a/infra/modules/network/main.bicep b/infra/modules/network/main.bicep
new file mode 100644
index 00000000..9b616267
--- /dev/null
+++ b/infra/modules/network/main.bicep
@@ -0,0 +1,104 @@
+@minLength(6)
+@maxLength(25)
+@description('Name used for naming all network resources.')
+param resourcesName string
+
+@minLength(3)
+@description('Azure region for all services.')
+param location string
+
+@description('Resource ID of the Log Analytics Workspace for monitoring and diagnostics.')
+param logAnalyticsWorkSpaceResourceId string
+
+@description('Networking address prefix for the VNET.')
+param addressPrefixes array
+
+import { subnetType } from 'virtualNetwork.bicep'
+@description('Array of subnets to be created within the VNET.')
+param subnets subnetType[]
+
+import { jumpBoxConfigurationType } from 'jumpbox.bicep'
+@description('Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation.')
+param jumpboxConfiguration jumpBoxConfigurationType?
+
+import { bastionHostConfigurationType } from 'bastionHost.bicep'
+@description('Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation.')
+param bastionConfiguration bastionHostConfigurationType?
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+// /****************************************************************************************************************************/
+// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG
+// /****************************************************************************************************************************/
+
+module virtualNetwork 'virtualNetwork.bicep' = {
+ name: '${resourcesName}-virtualNetwork'
+ params: {
+ name: 'vnet-${resourcesName}'
+ addressPrefixes: addressPrefixes
+ subnets: subnets
+ location: location
+ tags: tags
+ logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId
+ enableTelemetry: enableTelemetry
+ }
+}
+
+// /****************************************************************************************************************************/
+// // Create Azure Bastion Subnet and Azure Bastion Host
+// /****************************************************************************************************************************/
+
+module bastionHost 'bastionHost.bicep' = if (!empty(bastionConfiguration)) {
+ name: '${resourcesName}-bastionHost'
+ params: {
+ name: bastionConfiguration.?name ?? 'bastion-${resourcesName}'
+ vnetId: virtualNetwork.outputs.resourceId
+ vnetName: virtualNetwork.outputs.name
+ location: location
+ logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId
+ subnetAddressPrefixes: bastionConfiguration.?subnetAddressPrefixes
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+// /****************************************************************************************************************************/
+// // create Jumpbox NSG and Jumpbox Subnet, then create Jumpbox VM
+// /****************************************************************************************************************************/
+
+module jumpbox 'jumpbox.bicep' = if (!empty(jumpboxConfiguration)) {
+ name: '${resourcesName}-jumpbox'
+ params: {
+ name: jumpboxConfiguration.?name ?? 'vm-jumpbox-${resourcesName}'
+ vnetName: virtualNetwork.outputs.name
+ size: jumpboxConfiguration.?size ?? 'Standard_D2s_v3'
+ logAnalyticsWorkspaceId: logAnalyticsWorkSpaceResourceId
+ location: location
+ subnet: jumpboxConfiguration.?subnet
+ username: jumpboxConfiguration.?username ?? '' // required
+ password: jumpboxConfiguration.?password ?? '' // required
+ enableTelemetry: enableTelemetry
+ tags: tags
+ }
+}
+
+output vnetName string = virtualNetwork.outputs.name
+output vnetResourceId string = virtualNetwork.outputs.resourceId
+
+import { subnetOutputType } from 'virtualNetwork.bicep'
+output subnets subnetOutputType[] = virtualNetwork.outputs.subnets // This one holds critical info for subnets, including NSGs
+
+output bastionSubnetId string = bastionHost.outputs.subnetId
+output bastionSubnetName string = bastionHost.outputs.subnetName
+output bastionHostId string = bastionHost.outputs.resourceId
+output bastionHostName string = bastionHost.outputs.name
+
+output jumpboxSubnetName string = jumpbox.outputs.subnetName
+output jumpboxSubnetId string = jumpbox.outputs.subnetId
+output jumpboxName string = jumpbox.outputs.name
+output jumpboxResourceId string = jumpbox.outputs.resourceId
+
diff --git a/infra/modules/network/virtualNetwork.bicep b/infra/modules/network/virtualNetwork.bicep
new file mode 100644
index 00000000..c017850f
--- /dev/null
+++ b/infra/modules/network/virtualNetwork.bicep
@@ -0,0 +1,157 @@
+/****************************************************************************************************************************/
+// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG
+/****************************************************************************************************************************/
+@description('Name of the virtual network.')
+param name string
+
+@description('Azure region to deploy resources.')
+param location string = resourceGroup().location
+
+@description('Required. An Array of 1 or more IP Address Prefixes OR the resource ID of the IPAM pool to be used for the Virtual Network. When specifying an IPAM pool resource ID you must also set a value for the parameter called `ipamPoolNumberOfIpAddresses`.')
+param addressPrefixes array
+
+@description('An array of subnets to be created within the virtual network. Each subnet can have its own configuration and associated Network Security Group (NSG).')
+param subnets subnetType[]
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. The resource ID of the Log Analytics Workspace to send diagnostic logs to.')
+param logAnalyticsWorkspaceId string
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+// 1. Create NSGs for subnets
+// using AVM Network Security Group module
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/network-security-group
+
+@batchSize(1)
+module nsgs 'br/public:avm/res/network/network-security-group:0.5.1' = [
+ for (subnet, i) in subnets: if (!empty(subnet.?networkSecurityGroup)) {
+ name: take('${name}-${subnet.?networkSecurityGroup.name}-networksecuritygroup', 64)
+ params: {
+ name: '${name}-${subnet.?networkSecurityGroup.name}'
+ location: location
+ securityRules: subnet.?networkSecurityGroup.securityRules
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+ }
+]
+
+// 2. Create VNet and subnets, with subnets associated with corresponding NSGs
+// using AVM Virtual Network module
+// https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/virtual-network
+
+module virtualNetwork 'br/public:avm/res/network/virtual-network:0.7.0' = {
+ name: take('${name}-virtualNetwork', 64)
+ params: {
+ name: name
+ location: location
+ addressPrefixes: addressPrefixes
+ subnets: [
+ for (subnet, i) in subnets: {
+ name: subnet.name
+ addressPrefixes: subnet.?addressPrefixes
+ networkSecurityGroupResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i].outputs.resourceId : null
+ privateEndpointNetworkPolicies: subnet.?privateEndpointNetworkPolicies
+ privateLinkServiceNetworkPolicies: subnet.?privateLinkServiceNetworkPolicies
+ delegation: subnet.?delegation
+ }
+ ]
+ diagnosticSettings: [
+ {
+ name: 'vnetDiagnostics'
+ workspaceResourceId: logAnalyticsWorkspaceId
+ logCategoriesAndGroups: [
+ {
+ categoryGroup: 'allLogs'
+ enabled: true
+ }
+ ]
+ metricCategories: [
+ {
+ category: 'AllMetrics'
+ enabled: true
+ }
+ ]
+ }
+ ]
+ tags: tags
+ enableTelemetry: enableTelemetry
+ }
+}
+
+output name string = virtualNetwork.outputs.name
+output resourceId string = virtualNetwork.outputs.resourceId
+
+// combined output array that holds subnet details along with NSG information
+output subnets subnetOutputType[] = [
+ for (subnet, i) in subnets: {
+ name: subnet.name
+ resourceId: virtualNetwork.outputs.subnetResourceIds[i]
+ nsgName: !empty(subnet.?networkSecurityGroup) ? subnet.?networkSecurityGroup.name : null
+ nsgResourceId: !empty(subnet.?networkSecurityGroup) ? nsgs[i].outputs.resourceId : null
+ }
+]
+
+@export()
+@description('Custom type definition for subnet resource information as output')
+type subnetOutputType = {
+ @description('The name of the subnet.')
+ name: string
+
+ @description('The resource ID of the subnet.')
+ resourceId: string
+
+ @description('The name of the associated network security group, if any.')
+ nsgName: string?
+
+ @description('The resource ID of the associated network security group, if any.')
+ nsgResourceId: string?
+}
+
+@export()
+@description('Custom type definition for subnet configuration')
+type subnetType = {
+ @description('Required. The Name of the subnet resource.')
+ name: string
+
+ @description('Required. Prefixes for the subnet.') // Required to ensure at least one prefix is provided
+ addressPrefixes: string[]
+
+ @description('Optional. The delegation to enable on the subnet.')
+ delegation: string?
+
+ @description('Optional. enable or disable apply network policies on private endpoint in the subnet.')
+ privateEndpointNetworkPolicies: ('Disabled' | 'Enabled' | 'NetworkSecurityGroupEnabled' | 'RouteTableEnabled')?
+
+ @description('Optional. Enable or disable apply network policies on private link service in the subnet.')
+ privateLinkServiceNetworkPolicies: ('Disabled' | 'Enabled')?
+
+ @description('Optional. Network Security Group configuration for the subnet.')
+ networkSecurityGroup: networkSecurityGroupType?
+
+ @description('Optional. The resource ID of the route table to assign to the subnet.')
+ routeTableResourceId: string?
+
+ @description('Optional. An array of service endpoint policies.')
+ serviceEndpointPolicies: object[]?
+
+ @description('Optional. The service endpoints to enable on the subnet.')
+ serviceEndpoints: string[]?
+
+ @description('Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet.')
+ defaultOutboundAccess: bool?
+}
+
+@export()
+@description('Custom type definition for network security group configuration')
+type networkSecurityGroupType = {
+ @description('Required. The name of the network security group.')
+ name: string
+
+ @description('Required. The security rules for the network security group.')
+ securityRules: object[]
+}
diff --git a/infra/modules/privateDnsZone.bicep b/infra/modules/privateDnsZone.bicep
new file mode 100644
index 00000000..225ab756
--- /dev/null
+++ b/infra/modules/privateDnsZone.bicep
@@ -0,0 +1,44 @@
+// This module is here solely to reduce the size of the main bicep file for meet the 4MB limit
+// The AVM Module 'br/public:avm/res/network/private-dns-zone' should be used if size is available.
+
+@description('Required. Private DNS zone name.')
+param name string
+
+@description('Required. The resource ID of the virtual network to link.')
+param virtualNetworkResourceId string
+
+@description('Optional. Tags of the resource.')
+param tags object?
+
+// Private DNS Zones are global resources and may not support all regions, even if those regions support the underlying services.
+// The Private DNS Zone creation should use 'global' as the location.
+resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
+ name: name
+ location: 'global' // Private DNS zones must use 'global' as location
+ tags: tags
+}
+
+resource virtualNetworkLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
+ name: '${last(split(virtualNetworkResourceId, '/'))}-vnetlink'
+ parent: privateDnsZone
+ location: 'global' // Virtual Network Links must also use 'global' as location
+ tags: tags
+ properties: {
+ registrationEnabled: false
+ virtualNetwork: {
+ id: virtualNetworkResourceId
+ }
+ }
+}
+
+@description('The resource group the private DNS zone was deployed into.')
+output resourceGroupName string = resourceGroup().name
+
+@description('The name of the private DNS zone.')
+output name string = privateDnsZone.name
+
+@description('The resource ID of the private DNS zone.')
+output resourceId string = privateDnsZone.id
+
+@description('The location the resource was deployed into.')
+output location string = privateDnsZone.location
diff --git a/infra/modules/storageAccount.bicep b/infra/modules/storageAccount.bicep
new file mode 100644
index 00000000..b109de49
--- /dev/null
+++ b/infra/modules/storageAccount.bicep
@@ -0,0 +1,157 @@
+@description('Required. Name of the Storage Account.')
+param name string
+
+@description('Required. Specifies the location for all the Azure resources.')
+param location string
+
+@allowed([
+ 'Standard_LRS'
+ 'Standard_GRS'
+ 'Standard_RAGRS'
+ 'Standard_ZRS'
+ 'Premium_LRS'
+ 'Premium_ZRS'
+ 'Standard_GZRS'
+ 'Standard_RAGZRS'
+])
+@description('Optional. Storage Account Sku Name. Defaults to Standard_LRS.')
+param skuName string = 'Standard_LRS'
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Optional. Resource ID of the Log Analytics workspace to use for diagnostic settings.')
+param logAnalyticsWorkspaceResourceId string?
+
+@description('Optional. Values to establish private networking for the Storage Account.')
+param privateNetworking storageAccountPrivateNetworkingType?
+
+import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1'
+@description('Optional. Array of role assignments to create.')
+param roleAssignments roleAssignmentType[]?
+
+@description('Optional. List of the blob storage containers to create in the Storage Account.')
+param containers array?
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+module blobPrivateDnsZone 'privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?blobPrivateDnsZoneResourceId)) {
+ name: take('${name}-blob-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.blob.${environment().suffixes.storage}'
+ //location: location
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+module filePrivateDnsZone 'privateDnsZone.bicep' = if (privateNetworking != null && empty(privateNetworking.?filePrivateDnsZoneResourceId)) {
+ name: take('${name}-file-pdns-deployment', 64)
+ params: {
+ name: 'privatelink.file.${environment().suffixes.storage}'
+ //location: location
+ virtualNetworkResourceId: privateNetworking.?virtualNetworkResourceId ?? ''
+ tags: tags
+ }
+}
+
+var blobPrivateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?blobPrivateDnsZoneResourceId)
+ ? blobPrivateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?blobPrivateDnsZoneResourceId)
+ : ''
+var filePrivateDnsZoneResourceId = privateNetworking != null
+ ? (empty(privateNetworking.?filePrivateDnsZoneResourceId)
+ ? filePrivateDnsZone.outputs.resourceId ?? ''
+ : privateNetworking.?filePrivateDnsZoneResourceId)
+ : ''
+
+module storageAccount 'br/public:avm/res/storage/storage-account:0.20.0' = {
+ name: take('${name}-sa-deployment', 64)
+ #disable-next-line no-unnecessary-dependson
+ dependsOn: [filePrivateDnsZone, blobPrivateDnsZone] // required due to optional flags that could change dependency
+ params: {
+ name: name
+ location: location
+ kind: 'StorageV2'
+ skuName: skuName
+ accessTier: 'Hot'
+ tags: tags
+ publicNetworkAccess: privateNetworking != null ? 'Disabled' : 'Enabled'
+ allowBlobPublicAccess: false
+ allowSharedKeyAccess: false
+ allowCrossTenantReplication: false
+ minimumTlsVersion: 'TLS1_2'
+ requireInfrastructureEncryption: false
+ keyType: 'Service'
+ enableHierarchicalNamespace: false
+ enableNfsV3: false
+ largeFileSharesState: 'Disabled'
+ networkAcls: {
+ defaultAction: privateNetworking != null ? 'Deny' : 'Allow'
+ bypass: 'AzureServices'
+ }
+ supportsHttpsTrafficOnly: true
+ diagnosticSettings: !empty(logAnalyticsWorkspaceResourceId)
+ ? [
+ {
+ workspaceResourceId: logAnalyticsWorkspaceResourceId
+ }
+ ]
+ : []
+ privateEndpoints: privateNetworking != null
+ ? [
+ {
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ {
+ privateDnsZoneResourceId: blobPrivateDnsZoneResourceId
+ }
+ ]
+ }
+ service: 'blob'
+ subnetResourceId: privateNetworking.?subnetResourceId ?? ''
+ }
+ {
+ privateDnsZoneGroup: {
+ privateDnsZoneGroupConfigs: [
+ {
+ privateDnsZoneResourceId: filePrivateDnsZoneResourceId
+ }
+ ]
+ }
+ service: 'file'
+ subnetResourceId: privateNetworking.?subnetResourceId ?? ''
+ }
+ ]
+ : []
+ roleAssignments: roleAssignments
+ blobServices: {
+ containers: containers ?? []
+ }
+ enableTelemetry: enableTelemetry
+ }
+}
+
+@description('Name of the Storage Account.')
+output name string = storageAccount.outputs.name
+
+@description('Resource ID of the Storage Account.')
+output resourceId string = storageAccount.outputs.resourceId
+
+@export()
+@description('Values to establish private networking for resources that support createing private endpoints.')
+type storageAccountPrivateNetworkingType = {
+ @description('Required. The Resource ID of the virtual network.')
+ virtualNetworkResourceId: string
+
+ @description('Required. The Resource ID of the subnet to establish the Private Endpoint(s).')
+ subnetResourceId: string
+
+ @description('Optional. The Resource ID of an existing "file" Private DNS Zone Resource to link to the virtual network. If not provided, a new "file" Private DNS Zone(s) will be created.')
+ filePrivateDnsZoneResourceId: string?
+
+ @description('Optional. The Resource ID of an existing "blob" Private DNS Zone Resource to link to the virtual network. If not provided, a new "blob" Private DNS Zone(s) will be created.')
+ blobPrivateDnsZoneResourceId: string?
+}
diff --git a/infra/samples/network-subnet-design.bicep b/infra/samples/network-subnet-design.bicep
new file mode 100644
index 00000000..0cf7b240
--- /dev/null
+++ b/infra/samples/network-subnet-design.bicep
@@ -0,0 +1,297 @@
+// /******************************************************************************************************************/
+// This is an example test program to create private networking resources independently with sample network design.
+// It is an illustration of how to use the main.bicep in the infra/modules/network folder, with your own parameters.
+// You can independently deploy this module to create a network with subnets, NSGs, Azure Bastion Host, and Jumpbox VM.
+// Test them with this test program. Then integrate your design into the modules/network.bicep which is intended for
+// a specific network design.
+//
+// All things in infra/modules/network are designed to be reusable and composable without the need to modify
+// any code in the network folder.
+//
+// Please review below modules to understand how things are wired together:
+// infra/main.bicep
+// infra/modules/network.bicep
+// infra/moddules/network/main.bicep
+//
+// /******************************************************************************************************************/
+
+@minLength(6)
+@maxLength(25)
+@description('Name used for naming all network resources.')
+param resourcesName string = 'nettest'
+
+@minLength(3)
+@description('Azure region for all services.')
+param location string = resourceGroup().location
+
+@description('Optional. Enable/Disable usage telemetry for module.')
+param enableTelemetry bool = true
+
+@description('Optional. Tags to be applied to the resources.')
+param tags object = {}
+
+@description('Admin username for the VM.')
+@secure()
+param vmAdminUsername string = 'JumpboxAdminUser'
+
+@description('Admin password for the VM.')
+@secure()
+param vmAdminPassword string = 'JumpboxAdminP@ssw0rd1234!'
+
+
+import { bastionHostConfigurationType } from '../modules/network/bastionHost.bicep'
+@description('Optional. Configuration for the Azure Bastion Host. Leave null to omit Bastion creation.')
+param bastionConfiguration bastionHostConfigurationType = {
+ name: 'bastion-${resourcesName}'
+ subnetAddressPrefixes: ['10.0.10.0/23'] // /23 (10.0.10.0 - 10.0.11.255), 512 addresses
+}
+
+import { jumpBoxConfigurationType } from '../modules/network/jumpbox.bicep'
+@description('Optional. Configuration for the Jumpbox VM. Leave null to omit Jumpbox creation.')
+param jumpboxConfiguration jumpBoxConfigurationType = {
+ name: 'vm-jumpbox-${resourcesName}'
+ size: 'Standard_D2s_v3' // Default size, can be overridden
+ username: vmAdminUsername
+ password: vmAdminPassword
+ subnet: {
+ name: 'jumpbox-subnet'
+ addressPrefixes: ['10.0.12.0/23'] // /23 (10.0.12.0 - 10.0.13.255), 512 addresses
+ networkSecurityGroup: {
+ name: 'jumpbox-nsg'
+ securityRules: [
+ {
+ name: 'AllowJumpboxInbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '22'
+ sourceAddressPrefixes: [
+ '10.0.10.0/23' // Azure Bastion subnet as an example here. You can adjust this as needed by adding more
+ ]
+ destinationAddressPrefixes: ['10.0.12.0/23']
+ }
+ }
+ ]
+ }
+ }
+}
+
+// ====================================================================================================================
+// Below paremeters define default the VNET and subnets. You can customize them as needed.
+// ====================================================================================================================
+@description('Networking address prefix for the VNET.')
+param addressPrefixes array = ['10.0.0.0/20'] // 4096 addresses (enough for 8 /23 subnets or 16 /24 subnets)
+
+// Subnet Classless Inter-Doman Routing (CIDR) Sizing Reference Table (Best Practices)
+// | CIDR | # of Addresses | # of /24s | Notes |
+// |-----------|---------------|-----------|----------------------------------------|
+// | /24 | 256 | 1 | Smallest recommended for Azure subnets |
+// | /23 | 512 | 2 | Good for 1-2 workloads per subnet |
+// | /22 | 1024 | 4 | Good for 2-4 workloads per subnet |
+// | /21 | 2048 | 8 | |
+// | /20 | 4096 | 16 | Used for default VNet in this solution |
+// | /19 | 8192 | 32 | |
+// | /18 | 16384 | 64 | |
+// | /17 | 32768 | 128 | |
+// | /16 | 65536 | 256 | |
+// | /15 | 131072 | 512 | |
+// | /14 | 262144 | 1024 | |
+// | /13 | 524288 | 2048 | |
+// | /12 | 1048576 | 4096 | |
+// | /11 | 2097152 | 8192 | |
+// | /10 | 4194304 | 16384 | |
+// | /9 | 8388608 | 32768 | |
+// | /8 | 16777216 | 65536 | |
+//
+// Best Practice Notes:
+// - Use /24 as the minimum subnet size for Azure (smaller subnets are not supported for most services).
+// - Plan for future growth: allocate larger address spaces (e.g., /20 or /21 for VNets) to allow for new subnets.
+// - Avoid overlapping address spaces with on-premises or other VNets.
+// - Use contiguous, non-overlapping ranges for subnets.
+// - Document subnet usage and purpose in code comments.
+// - For AVM modules, ensure only one delegation per subnet and leave delegations empty if not required.
+
+import { subnetType } from '../modules/network/virtualNetwork.bicep'
+@description('Array of subnets to be created within the VNET.')
+param subnets subnetType[] = [
+ {
+ name: 'peps'
+ addressPrefixes: ['10.0.0.0/23'] // /23 (10.0.0.0 - 10.0.1.255), 512 addresses
+ privateEndpointNetworkPolicies: 'Disabled' // 'Disabled': to use private endpoints in the subnet.
+ privateLinkServiceNetworkPolicies: 'Disabled' // 'Disabled': to deploy a private link service in the subnet.
+ networkSecurityGroup: {
+ name: 'peps-nsg'
+ securityRules: []
+ }
+ }
+ {
+ name: 'web'
+ addressPrefixes: ['10.0.2.0/23'] // /23 (10.0.2.0 - 10.0.3.255), 512 addresses
+ privateEndpointNetworkPolicies: 'Enabled' // 'Disabled' only if you need to support private endpoints or private link services in the subnet.
+ privateLinkServiceNetworkPolicies: 'Enabled' // 'Disabled' only if you need to support private endpoints or private link services in the subnet.
+ networkSecurityGroup: {
+ name: 'web-nsg'
+ securityRules: [
+ {
+ name: 'AllowHttpsInbound'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '443'
+ sourceAddressPrefixes: ['0.0.0.0/0']
+ destinationAddressPrefixes: ['10.0.2.0/23']
+ }
+ }
+ ]
+ }
+ delegations: [
+ {
+ name: 'containerapps-delegation'
+ serviceName: 'Microsoft.App/environments'
+ }
+ ]
+ }
+ {
+ name: 'app'
+ addressPrefixes: ['10.0.4.0/23'] // /23 (10.0.4.0 - 10.0.5.255), 512 addresses
+ privateEndpointNetworkPolicies: 'Enabled' // 'Disabled' only if you need to support private endpoints or private link services in the subnet.
+ privateLinkServiceNetworkPolicies: 'Enabled' // 'Disabled' only if you need to support private endpoints or private link services in the subnet.
+ networkSecurityGroup: {
+ name: 'app-nsg'
+ securityRules: [
+ {
+ name: 'AllowWebToApp'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefixes: ['10.0.2.0/23'] // web subnet
+ destinationAddressPrefixes: ['10.0.4.0/23']
+ }
+ }
+ ]
+ }
+ delegations: [
+ {
+ name: 'containerapps-delegation'
+ serviceName: 'Microsoft.App/environments'
+ }
+ ]
+ }
+ {
+ name: 'ai'
+ addressPrefixes: ['10.0.6.0/23'] // /23 (10.0.6.0 - 10.0.7.255), 512 addresses
+ privateEndpointNetworkPolicies: 'Enabled' // 'Disabled' only if you need to support private endpoints or private link services in the subnet.
+ privateLinkServiceNetworkPolicies: 'Enabled' // 'Disabled' only if you need to support private endpoints or private link services in the subnet.
+ networkSecurityGroup: {
+ name: 'ai-nsg'
+ securityRules: [
+ {
+ name: 'AllowWebAppToAI'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefixes: [
+ '10.0.2.0/23' // web subnet
+ '10.0.4.0/23' // app subnet
+ ]
+ destinationAddressPrefixes: ['10.0.6.0/23']
+ }
+ }
+ ]
+ }
+ delegations: [] // No delegation required for this subnet.
+ }
+ {
+ name: 'data'
+ addressPrefixes: ['10.0.8.0/23'] // /23 (10.0.8.0 - 10.0.9.255), 512 addresses
+ privateEndpointNetworkPolicies: 'Disabled' // 'Disabled': to use private endpoints in the subnet.
+ privateLinkServiceNetworkPolicies: 'Disabled' // 'Disabled': to deploy a private link service in the subnet.
+ networkSecurityGroup: {
+ name: 'data-nsg'
+ securityRules: [
+ {
+ name: 'AllowWebAppAiToData'
+ properties: {
+ access: 'Allow'
+ direction: 'Inbound'
+ priority: 100
+ protocol: 'Tcp'
+ sourcePortRange: '*'
+ destinationPortRange: '*'
+ sourceAddressPrefixes: [
+ '10.0.2.0/23' // web subnet
+ '10.0.4.0/23' // app subnet
+ '10.0.6.0/23' // ai subnet
+ ]
+ destinationAddressPrefixes: ['10.0.8.0/23']
+ }
+ }
+ ]
+ }
+ delegations: [] // No delegation required for this subnet.
+ }
+]
+
+// /******************************************************************************************************************/
+// Create Log Analytics Workspace for monitoring and diagnostics
+// /******************************************************************************************************************/
+module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.11.2' = {
+ name: take('log-analytics-${resourcesName}-deployment', 64)
+ params: {
+ name: 'log-${resourcesName}'
+ location: location
+ skuName: 'PerGB2018'
+ dataRetention: 30
+ diagnosticSettings: [{ useThisWorkspace: true }]
+ tags: tags
+ }
+}
+
+// /******************************************************************************************************************/
+// Networking - NSGs, VNET and Subnets. Each subnet has its own NSG
+// /******************************************************************************************************************/
+
+module network '../modules/network/main.bicep' = {
+ name: take('network-${resourcesName}-create', 64)
+ params: {
+ resourcesName: resourcesName
+ location: location
+ logAnalyticsWorkSpaceResourceId: logAnalyticsWorkspace.outputs.resourceId
+ tags: tags
+ addressPrefixes: addressPrefixes
+ subnets: subnets
+ bastionConfiguration: bastionConfiguration
+ jumpboxConfiguration: jumpboxConfiguration
+ enableTelemetry: enableTelemetry
+ }
+}
+
+
+output vnetName string = network.outputs.vnetName
+output vnetResourceId string = network.outputs.vnetResourceId
+
+output subnetPrivateEndpointsResourceId string = first(filter(network.outputs.subnets, s => s.name == 'peps')).?resourceId ?? ''
+
+
+output subnetWebResourceId string = first(filter(network.outputs.subnets, s => s.name == 'web')).?resourceId ?? ''
+output subnetAppResourceId string = first(filter(network.outputs.subnets, s => s.name == 'app')).?resourceId ?? ''
+output subnetAiResourceId string = first(filter(network.outputs.subnets, s => s.name == 'ai')).?resourceId ?? ''
+output subnetDataResourceId string = first(filter(network.outputs.subnets, s => s.name == 'data')).?resourceId ?? ''
+
+output bastionHostResourceId string = bastionConfiguration != null ? network.outputs.bastionHostId : ''
+output bastionSubnetResourceId string = bastionConfiguration != null ? network.outputs.bastionSubnetId : ''
+
diff --git a/infra/samples/network_subnet_design.md b/infra/samples/network_subnet_design.md
new file mode 100644
index 00000000..e867e0df
--- /dev/null
+++ b/infra/samples/network_subnet_design.md
@@ -0,0 +1,58 @@
+# Network Subnet Design Guide
+
+This guide explains how to use the sample Bicep program `network-subnet-design.bicep` to create and test your own Azure network design using reusable modules. The sample is designed to be a standalone test harness and a reference for integrating your network design into the solution.
+
+## Purpose and Approach
+
+- **Sample File:** `infra/samples/network-subnet-design.bicep`
+- **Reusable Modules:** All code in `infra/modules/network` is designed to be reusable. You should not need to modify code in the modules folder.
+- **Custom Network File:** For your own solution, create a `network.bicep` in `infra/modules/` that represents your specific network design, using the modules as building blocks.
+
+## How to Use the Sample
+
+- The sample demonstrates how to deploy a virtual network, subnets, NSGs, Azure Bastion Host, and Jumpbox VM using parameters and reusable modules.
+- The sample is parameterized, so you can easily adjust subnet names, address spaces, NSG rules, and delegations.
+- You can design and validate your network design with `infra/samples/network-subnet-design.bicep`, then integrate the code by updating `infra/modules/network.bicep` with your tested design.
+
+## Key Features in the Sample
+
+- **Parameterization:**
+ - `resourcesName`, `location`, `addressPrefixes`, `subnets`, `bastionConfiguration`, and `jumpboxConfiguration` are all parameterized for flexibility.
+- **Subnet Design:**
+ - Subnets are defined with clear address ranges and optional NSGs and delegations.
+ - Example subnets: `peps`, `web`, `app`, `ai`, `data`, and a dedicated subnet for Jumpbox.
+- **Security:**
+ - Each subnet can have its own NSG with custom rules.
+ - Example: The `web` subnet allows HTTPS inbound from anywhere; the `app` subnet allows traffic from the `web` subnet, etc.
+- **Bastion and Jumpbox:**
+ - Optional Azure Bastion Host and Jumpbox VM are included for secure management access.
+
+## Example Workflow
+
+1. **Review the Sample:**
+ - Open `infra/samples/network-subnet-design.bicep` and read the comments at the top for context and usage.
+2. **Customize Parameters:**
+ - Adjust the subnet array, address prefixes, NSG rules, and other parameters to match your requirements.
+3. **Deploy the Sample:**
+ - Use Azure Developer CLI (`azd`) or Azure CLI to deploy the sample and validate your design.
+4. **Integrate into Solution:**
+ - Once validated, use the same approach to build your own `network.bicep` in `infra/modules/` for your solution. Test your solution with i`nfra/main.bicep`.
+
+## Example Directory Structure
+
+```
+infra/
+ modules/
+ network/
+ ... (reusable modules)
+ network.bicep # <-- your custom network design
+ samples/
+ network-subnet-design.bicep # <-- reference
+```
+
+## Best Practices
+
+- Keep your customizations in your own `network.bicep` minimal and focused on your solution’s needs.
+- Reuse modules as much as possible to reduce duplication and improve maintainability.
+- Clearly document subnet address ranges, NSG rules, and any special configuration in comments.
+- Validate your design with Azure best practices for security, scalability, and manageability.
\ No newline at end of file