diff --git a/.github/workflows/broken-links-checker.yml b/.github/workflows/broken-links-checker.yml index af100332..db06cb2a 100644 --- a/.github/workflows/broken-links-checker.yml +++ b/.github/workflows/broken-links-checker.yml @@ -37,7 +37,7 @@ jobs: uses: lycheeverse/lychee-action@v2.8.0 with: args: > - --verbose --exclude-mail --no-progress --exclude ^https?:// + --verbose --no-progress --exclude ^https?:// ${{ steps.changed-markdown-files.outputs.all_changed_files }} failIfEmpty: false env: @@ -50,7 +50,7 @@ jobs: uses: lycheeverse/lychee-action@v2.8.0 with: args: > - --verbose --exclude-mail --no-progress --exclude ^https?:// + --verbose --no-progress --exclude ^https?:// '**/*.md' failIfEmpty: false env: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd2422df..95a62fe9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,10 @@ jobs: AZURE_REGIONS: ${{ vars.AZURE_REGIONS }} GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }} run: | - chmod +x scripts/checkquota.sh - if ! scripts/checkquota.sh; then + chmod +x infra/scripts/pre-provision/checkquota.sh + if ! infra/scripts/pre-provision/checkquota.sh; then # If quota check fails due to insufficient quota, set the flag - if grep -q "No region with sufficient quota found" scripts/checkquota.sh; then + if grep -q "No region with sufficient quota found" infra/scripts/pre-provision/checkquota.sh; then echo "QUOTA_FAILED=true" >> $GITHUB_ENV fi exit 1 # Fail the pipeline if any other failure occurs diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index f9cd652a..c4c1dcc2 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -331,10 +331,10 @@ jobs: AZURE_REGIONS: ${{ vars.AZURE_REGIONS }} GPT_MIN_CAPACITY: ${{ env.GPT_MIN_CAPACITY }} run: | - chmod +x scripts/checkquota.sh - if ! scripts/checkquota.sh; then + chmod +x infra/scripts/pre-provision/checkquota.sh + if ! infra/scripts/pre-provision/checkquota.sh; then # If quota check fails due to insufficient quota, set the flag - if grep -q "No region with sufficient quota found" scripts/checkquota.sh; then + if grep -q "No region with sufficient quota found" infra/scripts/pre-provision/checkquota.sh; then echo "QUOTA_FAILED=true" >> $GITHUB_ENV fi exit 1 # Fail the pipeline if any other failure occurs diff --git a/.github/workflows/validate-bicep-params.yml b/.github/workflows/validate-bicep-params.yml index ce0ebcb1..384cdea5 100644 --- a/.github/workflows/validate-bicep-params.yml +++ b/.github/workflows/validate-bicep-params.yml @@ -13,7 +13,7 @@ on: paths: - 'infra/**/*.bicep' - 'infra/**/*.parameters.json' - - 'scripts/validate_bicep_params.py' + - 'infra/scripts/utilities/validate_bicep_params.py' workflow_dispatch: env: @@ -39,7 +39,7 @@ jobs: run: | set +e RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" - python scripts/validate_bicep_params.py --dir infra --strict --no-color \ + python infra/scripts/utilities/validate_bicep_params.py --dir infra --strict --no-color \ --json-output infra_results.json \ --html-output email_body.html \ --accelerator-name "${ACCELERATOR_NAME}" \ diff --git a/docs/QuotaCheck.md b/docs/QuotaCheck.md index c1659a6a..e06fa632 100644 --- a/docs/QuotaCheck.md +++ b/docs/QuotaCheck.md @@ -89,7 +89,7 @@ The final table lists regions with available quota. You can select any of these **To check quota for the deployment** ```sh - curl -L -o quota_check_params.sh "https://raw.githubusercontent.com/microsoft/Container-Migration-Solution-Accelerator/main/scripts/quota_check_params.sh" + curl -L -o quota_check_params.sh "https://raw.githubusercontent.com/microsoft/Container-Migration-Solution-Accelerator/main/infra/scripts/pre-provision/quota_check_params.sh" chmod +x quota_check_params.sh ./quota_check_params.sh ``` diff --git a/infra/avm/main.bicep b/infra/avm/main.bicep new file mode 100644 index 00000000..6dd04d15 --- /dev/null +++ b/infra/avm/main.bicep @@ -0,0 +1,510 @@ +// ========== main.bicep (AVM flavor) ========== // +// Uses Azure Verified Modules (br/public:avm/...) via toolkit wrappers. +targetScope = 'resourceGroup' + +// ============================================================================== +// Parameters +// ============================================================================== + +// ── Core ── + +@minLength(3) +@maxLength(16) +@description('Required. A unique application/solution name for all resources in this deployment.') +param solutionName string = 'containermig' + +@maxLength(5) +@description('Optional. A unique text suffix appended to resource names for uniqueness.') +param solutionUniqueText string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) + +@description('Optional. Primary Azure region for resource deployment. Defaults to resource group location.') +param location string = '' + +@allowed([ + 'australiaeast' + 'eastus' + 'eastus2' + 'francecentral' + 'japaneast' + 'norwayeast' + 'southindia' + 'swedencentral' + 'uksouth' + 'westus' + 'westus3' +]) +@metadata({ + azd: { + type: 'location' + usageName: [ + 'OpenAI.GlobalStandard.gpt-5.1,500' + ] + } +}) +@description('Required. Location for AI Foundry and model deployments.') +param azureAiServiceLocation string + +@description('Optional. Secondary location for Cosmos DB resources.') +param cosmosLocation string = 'eastus2' + +// ── AI Configuration ── + +@allowed([ + 'Standard' + 'GlobalStandard' +]) +@description('Optional. GPT model deployment type.') +param deploymentType string = 'GlobalStandard' + +@description('Optional. Name of the GPT model to deploy.') +param gptModelName string = 'gpt-5.1' + +@description('Optional. Version of the GPT model.') +param gptModelVersion string = '2025-11-13' + +@description('Optional. GPT model deployment capacity (tokens per minute in thousands).') +param gptDeploymentCapacity int = 500 + +@description('Optional. Name of the embedding model to deploy.') +param embeddingModel string = 'text-embedding-3-large' + +@description('Optional. Version of the embedding model.') +param embeddingModelVersion string = '1' + +@allowed([ + 'Standard' + 'GlobalStandard' +]) +@description('Optional. Embedding model deployment type.') +param embeddingDeploymentType string = 'GlobalStandard' + +@description('Optional. Embedding model deployment capacity.') +param embeddingDeploymentCapacity int = 500 + +// ── Container Apps Configuration ── + +@description('Optional. The endpoint (excluding https://) of an existing container registry.') +param containerRegistryEndpoint string = 'containermigrationacr.azurecr.io' + +@description('Optional. The image tag to use for container images.') +param imageTag string = 'latest_v2' + +// ── Identity ── + +@description('Optional. Resource ID of an existing Foundry project.') +param existingFoundryProjectResourceId string = '' + +@description('Optional. Existing Log Analytics Workspace Resource ID.') +param existingLogAnalyticsWorkspaceId string = '' + +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// ============================================================================== +// Variables +// ============================================================================== + +var solutionLocation = empty(location) ? resourceGroup().location : location + +var solutionSuffix = toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueText}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) + +var deployerInfo = deployer() + +var createdBy = contains(deployerInfo, 'userPrincipalName') + ? split(deployerInfo.userPrincipalName, '@')[0] + : deployerInfo.objectId + +var existingTags = resourceGroup().tags ?? {} +var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) + +var aiModelDeployments = [ + { + name: gptModelName + model: gptModelName + sku: { + name: deploymentType + capacity: gptDeploymentCapacity + } + version: gptModelVersion + raiPolicyName: 'Microsoft.Default' + } + { + name: embeddingModel + model: embeddingModel + sku: { + name: embeddingDeploymentType + capacity: embeddingDeploymentCapacity + } + version: embeddingModelVersion + raiPolicyName: 'Microsoft.Default' + } +] + +var cosmosDatabaseName = 'migration_db' +var cosmosContainers = [ + { name: 'processes', partitionKeyPath: '/_partitionKey' } + { name: 'agent_telemetry', partitionKeyPath: '/_partitionKey' } + { name: 'processcontrol', partitionKeyPath: '/_partitionKey' } + { name: 'files', partitionKeyPath: '/_partitionKey' } + { name: 'process_statuses', partitionKeyPath: '/_partitionKey' } +] + +var processBlobContainerName = 'processes' +var processQueueName = 'processes-queue' + +// ========== Resource Group Tag ========== // +resource resourceGroupTags 'Microsoft.Resources/tags@2023-07-01' = { + name: 'default' + properties: { + tags: union( + existingTags, + tags, + { + TemplateName: 'Container Migration' + CreatedBy: createdBy + DeploymentName: deployment().name + Type: 'Non-WAF' + } + ) + } +} + +// ========== Monitoring (Log Analytics) ========== // + +module log_analytics './modules/monitoring/log-analytics.bicep' = if (!useExistingLogAnalytics) { + name: take('module.log-analytics.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + enableTelemetry: enableTelemetry + } +} + +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics + ? existingLogAnalyticsWorkspaceId + : log_analytics!.outputs.resourceId + +// ========== AI Foundry and related resources ========== // + +module ai_foundry_project './modules/ai/ai-foundry-project.bicep' = if (empty(existingFoundryProjectResourceId)) { + name: take('module.ai-foundry-project.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: azureAiServiceLocation + enableTelemetry: enableTelemetry + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Unified AI Foundry resource name vars ========== // +var useExistingAIProject = !empty(existingFoundryProjectResourceId) +var aiFoundryResourceName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[8] : ai_foundry_project!.outputs.name +var aiProjectResourceName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[10] : ai_foundry_project!.outputs.projectName +var aiServiceSubscription = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[2] : subscription().subscriptionId +var aiServiceResourceGroup = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[4] : resourceGroup().name + +module existing_project_setup './modules/ai/existing-project-setup.bicep' = if (useExistingAIProject) { + name: take('module.existing-project-setup.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + name: aiFoundryResourceName + projectName: aiProjectResourceName + } +} + +module foundry_storage_connection './modules/ai/ai-foundry-connection.bicep' = { + name: take('module.foundry-storage-conn.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + solutionName: solutionSuffix + aiServicesAccountName: aiFoundryResourceName + projectName: aiProjectResourceName + category: 'AzureBlob' + target: storage_account.outputs.blobEndpoint + authType: 'AAD' + metadata: { + ResourceId: storage_account.outputs.resourceId + AccountName: storage_account.outputs.name + ContainerName: 'default' + } + } +} + +@batchSize(1) +module model_deployments './modules/ai/ai-foundry-model-deployment.bicep' = [for (deployment, i) in aiModelDeployments: { + name: take('module.model-deployment-${i}.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + aiServicesAccountName: aiFoundryResourceName + deploymentName: deployment.name + modelName: deployment.model + modelVersion: deployment.version + raiPolicyName: deployment.raiPolicyName + skuName: deployment.sku.name + skuCapacity: deployment.sku.capacity + } +}] + +var aiFoundryEndpoint = useExistingAIProject ? existing_project_setup!.outputs.aiFoundryEndpoint : ai_foundry_project!.outputs.endpoint +var projectEndpoint = useExistingAIProject ? existing_project_setup!.outputs.projectEndpoint : ai_foundry_project!.outputs.projectEndpoint +var aiFoundryResourceId = !useExistingAIProject ? ai_foundry_project!.outputs.resourceId : '' +var aiProjectPrincipalId = useExistingAIProject ? existing_project_setup!.outputs.aiProjectPrincipalId : ai_foundry_project!.outputs.projectIdentityPrincipalId + +// ========== Storage Account module ========== // +module storage_account './modules/data/storage-account.bicep' = { + name: take('module.storage-account.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + tags: tags + containers: [ + { name: 'data', publicAccess: 'None' } + ] + enableTelemetry: enableTelemetry + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Cosmos DB module ========== // +module cosmosDBModule './modules/data/cosmos-db.bicep' = { + name: take('module.cosmos-db.${solutionName}', 64) + params: { + solutionName: solutionSuffix + name: 'cosmos-${solutionSuffix}' + location: cosmosLocation + databaseName: cosmosDatabaseName + containers: cosmosContainers + enableTelemetry: enableTelemetry + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Container App Environment ========== // +module containerAppEnv './modules/compute/container-app-environment.bicep' = { + name: take('module.container-app-env.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + enableTelemetry: enableTelemetry + } +} + +// ========== Container Apps ========== // +var backendContainerAppName = take('ca-backend-api-${solutionSuffix}', 32) +var processorContainerAppName = take('ca-processor-${solutionSuffix}', 32) +var frontEndContainerAppName = take('ca-frontend-${solutionSuffix}', 32) + +// ========== Backend API Container App ========== // +module ca_backend_api './modules/compute/container-app.bicep' = { + name: take('module.ca-backend-api.${solutionName}', 64) + params: { + name: backendContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: true + ingressTargetPort: 80 + enableTelemetry: enableTelemetry + containers: [ + { + name: 'backend-api' + image: '${containerRegistryEndpoint}/backend-api:${imageTag}' + env: [ + { name: 'AZURE_OPENAI_ENDPOINT', value: aiFoundryEndpoint } + { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT_NAME', value: gptModelName } + { name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME', value: embeddingModel } + { name: 'AZURE_OPENAI_API_VERSION', value: '2025-03-01-preview' } + { name: 'COSMOS_DB_ACCOUNT_URL', value: cosmosDBModule.outputs.endpoint } + { name: 'COSMOS_DB_DATABASE_NAME', value: cosmosDatabaseName } + { name: 'COSMOS_DB_CONTAINER_NAME', value: 'agent_telemetry' } + { name: 'COSMOS_DB_CONTROL_CONTAINER_NAME', value: 'processcontrol' } + { name: 'COSMOS_DB_PROCESS_CONTAINER', value: 'processes' } + { name: 'COSMOS_DB_PROCESS_LOG_CONTAINER', value: 'agent_telemetry' } + { name: 'STORAGE_ACCOUNT_BLOB_URL', value: storage_account.outputs.blobEndpoint } + { name: 'STORAGE_ACCOUNT_NAME', value: storage_account.outputs.name } + { name: 'STORAGE_ACCOUNT_PROCESS_CONTAINER', value: processBlobContainerName } + { name: 'STORAGE_ACCOUNT_PROCESS_QUEUE', value: processQueueName } + { name: 'STORAGE_ACCOUNT_QUEUE_URL', value: '${storage_account.outputs.serviceEndpoints.queue}' } + { name: 'GLOBAL_LLM_SERVICE', value: 'AzureOpenAI' } + { name: 'PROCESSOR_CONTROL_URL', value: 'https://${processorContainerAppName}.internal.${containerAppEnv.outputs.defaultDomain}' } + { name: 'APP_ENV', value: 'Prod' } + ] + resources: { + cpu: json('1') + memory: '2.0Gi' + } + } + ] + corsPolicy: { + allowedOrigins: ['*'] + allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] + allowedHeaders: ['Authorization', 'Content-Type', '*'] + } + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Frontend Container App ========== // +module ca_frontend './modules/compute/container-app.bicep' = { + name: take('module.ca-frontend.${solutionName}', 64) + params: { + name: frontEndContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: true + ingressTargetPort: 3000 + enableTelemetry: enableTelemetry + containers: [ + { + name: 'frontend' + image: '${containerRegistryEndpoint}/frontend:${imageTag}' + env: [ + { name: 'API_URL', value: 'https://${ca_backend_api.outputs.fqdn}' } + { name: 'APP_ENV', value: 'prod' } + { name: 'REACT_APP_MSAL_POST_REDIRECT_URL', value: '/' } + { name: 'REACT_APP_MSAL_REDIRECT_URL', value: '/' } + { name: 'ALLOWED_ORIGINS', value: 'https://${frontEndContainerAppName}.${containerAppEnv.outputs.defaultDomain}' } + ] + resources: { + cpu: json('1') + memory: '2.0Gi' + } + } + ] + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Processor Container App ========== // +module ca_processor './modules/compute/container-app.bicep' = { + name: take('module.ca-processor.${solutionName}', 64) + params: { + name: processorContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: false + ingressTargetPort: 8080 + ingressAllowInsecure: true + enableTelemetry: enableTelemetry + containers: [ + { + name: 'processor' + image: '${containerRegistryEndpoint}/processor:${imageTag}' + env: [ + { name: 'AZURE_OPENAI_ENDPOINT', value: aiFoundryEndpoint } + { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT_NAME', value: gptModelName } + { name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME', value: embeddingModel } + { name: 'AZURE_OPENAI_API_VERSION', value: '2025-03-01-preview' } + { name: 'COSMOS_DB_ACCOUNT_URL', value: cosmosDBModule.outputs.endpoint } + { name: 'COSMOS_DB_DATABASE_NAME', value: cosmosDatabaseName } + { name: 'COSMOS_DB_CONTAINER_NAME', value: 'agent_telemetry' } + { name: 'COSMOS_DB_CONTROL_CONTAINER_NAME', value: 'processcontrol' } + { name: 'COSMOS_DB_PROCESS_CONTAINER', value: 'processes' } + { name: 'COSMOS_DB_PROCESS_LOG_CONTAINER', value: 'agent_telemetry' } + { name: 'STORAGE_ACCOUNT_BLOB_URL', value: storage_account.outputs.blobEndpoint } + { name: 'STORAGE_ACCOUNT_NAME', value: storage_account.outputs.name } + { name: 'STORAGE_ACCOUNT_PROCESS_CONTAINER', value: processBlobContainerName } + { name: 'STORAGE_ACCOUNT_PROCESS_QUEUE', value: processQueueName } + { name: 'STORAGE_ACCOUNT_QUEUE_URL', value: '${storage_account.outputs.serviceEndpoints.queue}' } + { name: 'GLOBAL_LLM_SERVICE', value: 'AzureOpenAI' } + { name: 'CONTROL_API_ENABLED', value: '1' } + { name: 'CONTROL_API_PORT', value: '8080' } + ] + resources: { + cpu: json('2') + memory: '4.0Gi' + } + } + ] + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Role Assignments ========== // +module role_assignments './modules/identity/role-assignments.bicep' = { + name: take('module.role-assignments.${solutionName}', 64) + params: { + solutionName: solutionSuffix + useExistingAIProject: useExistingAIProject + existingFoundryProjectResourceId: existingFoundryProjectResourceId + aiFoundryResourceId: !useExistingAIProject ? aiFoundryResourceId : '' + aiSearchResourceId: '' + storageAccountResourceId: storage_account.outputs.resourceId + aiProjectPrincipalId: aiProjectPrincipalId + aiSearchPrincipalId: '' + backendAppServicePrincipalId: ca_backend_api.outputs.principalId + cosmosDbAccountName: cosmosDBModule.outputs.name + existingAiProjectPrincipalId: !empty(existingFoundryProjectResourceId) ? existing_project_setup!.outputs.aiProjectPrincipalId : '' + } + scope: resourceGroup(resourceGroup().name) +} + +// ============================================================================== +// Outputs +// ============================================================================== + +@description('Solution suffix used for naming resources') +output SOLUTION_NAME string = solutionSuffix + +@description('Name of the deployed resource group') +output RESOURCE_GROUP_NAME string = resourceGroup().name + +@description('The name of the web app container app.') +output CONTAINER_WEB_APP_NAME string = ca_frontend.outputs.name + +@description('The FQDN of the web app container app.') +output CONTAINER_WEB_APP_FQDN string = ca_frontend.outputs.fqdn + +@description('The name of the API container app.') +output CONTAINER_API_APP_NAME string = ca_backend_api.outputs.name + +@description('The FQDN of the API container app.') +output CONTAINER_API_APP_FQDN string = ca_backend_api.outputs.fqdn + +@description('Azure OpenAI service endpoint URL') +output AZURE_OPENAI_ENDPOINT string = aiFoundryEndpoint + +@description('GPT model deployment name') +output AZURE_ENV_GPT_MODEL_NAME string = gptModelName + +@description('Embedding model deployment name') +output AZURE_ENV_EMBEDDING_DEPLOYMENT_NAME string = embeddingModel + +@description('Cosmos DB account name') +output AZURE_COSMOSDB_ACCOUNT string = cosmosDBModule.outputs.name + +@description('Cosmos DB database name') +output AZURE_COSMOSDB_DATABASE string = cosmosDatabaseName + +@description('The Azure subscription ID.') +output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId + +@description('The Azure resource group name.') +output AZURE_RESOURCE_GROUP string = resourceGroup().name + +@description('Azure AI Agent service endpoint URL') +output AZURE_AI_AGENT_ENDPOINT string = projectEndpoint diff --git a/infra/avm/main.json b/infra/avm/main.json new file mode 100644 index 00000000..0040de6d --- /dev/null +++ b/infra/avm/main.json @@ -0,0 +1,28838 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "17833939918723632483" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "defaultValue": "containermig", + "minLength": 3, + "maxLength": 16, + "metadata": { + "description": "Required. A unique application/solution name for all resources in this deployment." + } + }, + "solutionUniqueText": { + "type": "string", + "defaultValue": "[substring(uniqueString(subscription().id, resourceGroup().name, parameters('solutionName')), 0, 5)]", + "maxLength": 5, + "metadata": { + "description": "Optional. A unique text suffix appended to resource names for uniqueness." + } + }, + "location": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Primary Azure region for resource deployment. Defaults to resource group location." + } + }, + "azureAiServiceLocation": { + "type": "string", + "allowedValues": [ + "australiaeast", + "eastus", + "eastus2", + "francecentral", + "japaneast", + "norwayeast", + "southindia", + "swedencentral", + "uksouth", + "westus", + "westus3" + ], + "metadata": { + "azd": { + "type": "location", + "usageName": [ + "OpenAI.GlobalStandard.gpt-5.1,500" + ] + }, + "description": "Required. Location for AI Foundry and model deployments." + } + }, + "cosmosLocation": { + "type": "string", + "defaultValue": "eastus2", + "metadata": { + "description": "Optional. Secondary location for Cosmos DB resources." + } + }, + "deploymentType": { + "type": "string", + "defaultValue": "GlobalStandard", + "allowedValues": [ + "Standard", + "GlobalStandard" + ], + "metadata": { + "description": "Optional. GPT model deployment type." + } + }, + "gptModelName": { + "type": "string", + "defaultValue": "gpt-5.1", + "metadata": { + "description": "Optional. Name of the GPT model to deploy." + } + }, + "gptModelVersion": { + "type": "string", + "defaultValue": "2025-11-13", + "metadata": { + "description": "Optional. Version of the GPT model." + } + }, + "gptDeploymentCapacity": { + "type": "int", + "defaultValue": 500, + "metadata": { + "description": "Optional. GPT model deployment capacity (tokens per minute in thousands)." + } + }, + "embeddingModel": { + "type": "string", + "defaultValue": "text-embedding-3-large", + "metadata": { + "description": "Optional. Name of the embedding model to deploy." + } + }, + "embeddingModelVersion": { + "type": "string", + "defaultValue": "1", + "metadata": { + "description": "Optional. Version of the embedding model." + } + }, + "embeddingDeploymentType": { + "type": "string", + "defaultValue": "GlobalStandard", + "allowedValues": [ + "Standard", + "GlobalStandard" + ], + "metadata": { + "description": "Optional. Embedding model deployment type." + } + }, + "embeddingDeploymentCapacity": { + "type": "int", + "defaultValue": 500, + "metadata": { + "description": "Optional. Embedding model deployment capacity." + } + }, + "containerRegistryEndpoint": { + "type": "string", + "defaultValue": "containermigrationacr.azurecr.io", + "metadata": { + "description": "Optional. The endpoint (excluding https://) of an existing container registry." + } + }, + "imageTag": { + "type": "string", + "defaultValue": "latest_v2", + "metadata": { + "description": "Optional. The image tag to use for container images." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of an existing Foundry project." + } + }, + "existingLogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Existing Log Analytics Workspace Resource ID." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The tags to apply to all deployed Azure resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "solutionLocation": "[if(empty(parameters('location')), resourceGroup().location, parameters('location'))]", + "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueText')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", + "deployerInfo": "[deployer()]", + "createdBy": "[if(contains(variables('deployerInfo'), 'userPrincipalName'), split(variables('deployerInfo').userPrincipalName, '@')[0], variables('deployerInfo').objectId)]", + "existingTags": "[coalesce(resourceGroup().tags, createObject())]", + "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", + "aiModelDeployments": [ + { + "name": "[parameters('gptModelName')]", + "model": "[parameters('gptModelName')]", + "sku": { + "name": "[parameters('deploymentType')]", + "capacity": "[parameters('gptDeploymentCapacity')]" + }, + "version": "[parameters('gptModelVersion')]", + "raiPolicyName": "Microsoft.Default" + }, + { + "name": "[parameters('embeddingModel')]", + "model": "[parameters('embeddingModel')]", + "sku": { + "name": "[parameters('embeddingDeploymentType')]", + "capacity": "[parameters('embeddingDeploymentCapacity')]" + }, + "version": "[parameters('embeddingModelVersion')]", + "raiPolicyName": "Microsoft.Default" + } + ], + "cosmosDatabaseName": "migration_db", + "cosmosContainers": [ + { + "name": "processes", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "agent_telemetry", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "processcontrol", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "files", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "process_statuses", + "partitionKeyPath": "/_partitionKey" + } + ], + "processBlobContainerName": "processes", + "processQueueName": "processes-queue", + "useExistingAIProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", + "aiServiceSubscription": "[if(variables('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], subscription().subscriptionId)]", + "aiServiceResourceGroup": "[if(variables('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], resourceGroup().name)]", + "backendContainerAppName": "[take(format('ca-backend-api-{0}', variables('solutionSuffix')), 32)]", + "processorContainerAppName": "[take(format('ca-processor-{0}', variables('solutionSuffix')), 32)]", + "frontEndContainerAppName": "[take(format('ca-frontend-{0}', variables('solutionSuffix')), 32)]" + }, + "resources": [ + { + "type": "Microsoft.Resources/tags", + "apiVersion": "2023-07-01", + "name": "default", + "properties": { + "tags": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration', 'CreatedBy', variables('createdBy'), 'DeploymentName', deployment().name, 'Type', 'Non-WAF'))]" + } + }, + { + "condition": "[not(variables('useExistingLogAnalytics'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.log-analytics.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "16251925117755840545" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name suffix used to derive the resource name." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('log-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Optional. Override name for the Log Analytics workspace. Defaults to log-{solutionName}." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the resource." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to apply to the resource." + } + }, + "retentionInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "Retention period in days. WAF recommends 365." + } + }, + "skuName": { + "type": "string", + "defaultValue": "PerGB2018", + "metadata": { + "description": "SKU name for the workspace." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "publicNetworkAccessForIngestion": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access for ingestion." + } + }, + "publicNetworkAccessForQuery": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access for query." + } + }, + "enableReplication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable workspace replication for redundancy." + } + }, + "replicationLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Replication location (paired region)." + } + }, + "dailyQuotaGb": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Daily quota in GB. WAF recommends 150 GB/day as starting point." + } + }, + "dataSources": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Data sources for VM monitoring (Windows events, perf counters)." + } + } + }, + "resources": { + "workspace": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.operational-insights.workspace.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "dataRetention": { + "value": "[parameters('retentionInDays')]" + }, + "skuName": { + "value": "[parameters('skuName')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "features": { + "value": { + "enableLogAccessUsingOnlyResourcePermissions": true + } + }, + "diagnosticSettings": { + "value": [ + { + "useThisWorkspace": true + } + ] + }, + "publicNetworkAccessForIngestion": { + "value": "[parameters('publicNetworkAccessForIngestion')]" + }, + "publicNetworkAccessForQuery": { + "value": "[parameters('publicNetworkAccessForQuery')]" + }, + "dailyQuotaGb": "[if(not(empty(parameters('dailyQuotaGb'))), createObject('value', parameters('dailyQuotaGb')), createObject('value', null()))]", + "replication": "[if(parameters('enableReplication'), createObject('value', createObject('enabled', true(), 'location', parameters('replicationLocation'))), createObject('value', null()))]", + "dataSources": "[if(not(empty(parameters('dataSources'))), createObject('value', parameters('dataSources')), createObject('value', null()))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14099489006827800075" + }, + "name": "Log Analytics Workspaces", + "description": "This module deploys a Log Analytics Workspace." + }, + "definitions": { + "diagnosticSettingType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "useThisWorkspace": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Instead of using an external reference, use the deployed instance as the target for its diagnostic settings. If set to `true`, the `workspaceResourceId` property is ignored." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "gallerySolutionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + } + }, + "plan": { + "$ref": "#/definitions/solutionPlanType", + "metadata": { + "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the gallery solutions to be created in the log analytics workspace." + } + }, + "storageInsightsConfigType": { + "type": "object", + "properties": { + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the storage account to be linked." + } + }, + "containers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the blob containers that the workspace should read." + } + }, + "tables": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of tables to be read by the workspace." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the storage insights configuration." + } + }, + "linkedServiceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the linked service. E.g., 'Automation' for an automation account, or 'Cluster' for a Log Analytics Cluster." + } + }, + "resourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require read access (e.g., Automation Accounts)." + } + }, + "writeAccessResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace. This should be used for linking resources which require write access (e.g., Log Analytics Clusters)." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the linked service." + } + }, + "linkedStorageAccountType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the link." + } + }, + "storageAccountIds": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. Linked storage accounts resources Ids." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the linked storage account." + } + }, + "savedSearchType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the saved search." + } + }, + "etag": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. The category of the saved search. This helps the user to find a saved search faster." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name for the search." + } + }, + "functionAlias": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The function alias if query serves as a function." + } + }, + "functionParameters": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: 'param-name1:type1 = default_value1, param-name2:type2 = default_value2'. For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." + } + }, + "query": { + "type": "string", + "metadata": { + "description": "Required. The query expression for the saved search." + } + }, + "tags": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The tags attached to the saved search." + } + }, + "version": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The version number of the query language. The current version is 2 and is the default." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the saved search." + } + }, + "dataExportType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data export." + } + }, + "destination": { + "$ref": "#/definitions/destinationType", + "nullable": true, + "metadata": { + "description": "Optional. The destination of the data export." + } + }, + "enable": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the data export." + } + }, + "tableNames": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of table names to export." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the data export." + } + }, + "dataSourceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data source." + } + }, + "kind": { + "type": "string", + "metadata": { + "description": "Required. The kind of data source." + } + }, + "linkedResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the resource that will be linked to the workspace." + } + }, + "eventLogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the event log to configure when kind is WindowsEvent." + } + }, + "eventTypes": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. The event types to configure when kind is WindowsEvent." + } + }, + "objectName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "instanceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "intervalSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "performanceCounters": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." + } + }, + "counterName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." + } + }, + "state": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." + } + }, + "syslogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. System log to configure when kind is LinuxSyslog." + } + }, + "syslogSeverities": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-07-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the data source." + } + }, + "tableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } + }, + "plan": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The plan for the table." + } + }, + "restoredLogs": { + "$ref": "#/definitions/restoredLogsType", + "nullable": true, + "metadata": { + "description": "Optional. The restored logs for the table." + } + }, + "schema": { + "$ref": "#/definitions/schemaType", + "nullable": true, + "metadata": { + "description": "Optional. The schema for the table." + } + }, + "searchResults": { + "$ref": "#/definitions/searchResultsType", + "nullable": true, + "metadata": { + "description": "Optional. The search results for the table." + } + }, + "retentionInDays": { + "type": "int", + "nullable": true, + "minValue": 4, + "maxValue": 730, + "metadata": { + "description": "Optional. The retention in days for the table. Don't provide to use the default workspace retention." + } + }, + "totalRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 4, + "maxValue": 2555, + "metadata": { + "description": "Optional. The total retention in days for the table. Don't provide use the default table retention." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The role assignments for the table." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Properties of the custom table." + } + }, + "workspaceFeaturesType": { + "type": "object", + "properties": { + "disableLocalAuth": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Disable Non-EntraID based Auth. Default is true." + } + }, + "enableDataExport": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag that indicate if data should be exported." + } + }, + "enableLogAccessUsingOnlyResourcePermissions": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable log access using only resource permissions. Default is false." + } + }, + "immediatePurgeDataOn30Days": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag that describes if we want to remove the data after 30 days." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Features of the workspace." + } + }, + "workspaceReplicationType": { + "type": "object", + "properties": { + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether the replication is enabled or not. When true, workspace configuration and data is replicated to the specified location." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The location to which the workspace is replicated. Required if replication is enabled." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Replication properties of the workspace." + } + }, + "_1.columnType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The column name." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "boolean", + "dateTime", + "dynamic", + "guid", + "int", + "long", + "real", + "string" + ], + "metadata": { + "description": "Required. The column type." + } + }, + "dataTypeHint": { + "type": "string", + "allowedValues": [ + "armPath", + "guid", + "ip", + "uri" + ], + "nullable": true, + "metadata": { + "description": "Optional. The column data type logical hint." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The column description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Column display name." + } + } + }, + "metadata": { + "description": "The parameters of the table column.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "destinationType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The destination resource ID." + } + }, + "metaData": { + "type": "object", + "properties": { + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination metadata." + } + } + }, + "metadata": { + "description": "The data export destination properties.", + "__bicep_imported_from!": { + "sourceTemplate": "data-export/main.bicep" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "managedIdentityAllType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "restoredLogsType": { + "type": "object", + "properties": { + "sourceTable": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table to restore data from." + } + }, + "startRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the restore from (UTC)." + } + }, + "endRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the restore by (UTC)." + } + } + }, + "metadata": { + "description": "The parameters of the restore operation that initiated the table.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "schemaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The table name." + } + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.columnType" + }, + "metadata": { + "description": "Required. A list of table custom columns." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table display name." + } + } + }, + "metadata": { + "description": "The table schema.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "searchResultsType": { + "type": "object", + "properties": { + "query": { + "type": "string", + "metadata": { + "description": "Required. The search job query." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The search description." + } + }, + "limit": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Limit the search job to return up to specified number of rows." + } + }, + "startSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the search from (UTC)." + } + }, + "endSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the search by (UTC)." + } + } + }, + "metadata": { + "description": "The parameters of the search job that initiated the table.", + "__bicep_imported_from!": { + "sourceTemplate": "table/main.bicep" + } + } + }, + "solutionPlanType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + } + }, + "product": { + "type": "string", + "metadata": { + "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/res/operations-management/solution:0.3.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Log Analytics workspace." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "skuName": { + "type": "string", + "defaultValue": "PerGB2018", + "allowedValues": [ + "CapacityReservation", + "Free", + "LACluster", + "PerGB2018", + "PerNode", + "Premium", + "Standalone", + "Standard" + ], + "metadata": { + "description": "Optional. The name of the SKU. Must be 'LACluster' to be linked to a Log Analytics cluster." + } + }, + "skuCapacityReservationLevel": { + "type": "int", + "defaultValue": 100, + "minValue": 100, + "maxValue": 5000, + "metadata": { + "description": "Optional. The capacity reservation level in GB for this workspace, when CapacityReservation sku is selected. Must be in increments of 100 between 100 and 5000." + } + }, + "storageInsightsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/storageInsightsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of storage accounts to be read by the workspace." + } + }, + "linkedServices": { + "type": "array", + "items": { + "$ref": "#/definitions/linkedServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of services to be linked." + } + }, + "linkedStorageAccounts": { + "type": "array", + "items": { + "$ref": "#/definitions/linkedStorageAccountType" + }, + "nullable": true, + "metadata": { + "description": "Conditional. List of Storage Accounts to be linked. Required if 'forceCmkForQuery' is set to 'true' and 'savedSearches' is not empty." + } + }, + "savedSearches": { + "type": "array", + "items": { + "$ref": "#/definitions/savedSearchType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Kusto Query Language searches to save." + } + }, + "dataExports": { + "type": "array", + "items": { + "$ref": "#/definitions/dataExportType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW data export instances to be deployed." + } + }, + "dataSources": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSourceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW data sources to configure." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. LAW custom tables to be deployed." + } + }, + "gallerySolutions": { + "type": "array", + "items": { + "$ref": "#/definitions/gallerySolutionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of gallerySolutions to be created in the log analytics workspace." + } + }, + "onboardWorkspaceToSentinel": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Onboard the Log Analytics Workspace to Sentinel. Requires 'SecurityInsights' solution to be in gallerySolutions." + } + }, + "dataRetention": { + "type": "int", + "defaultValue": 365, + "minValue": 0, + "maxValue": 730, + "metadata": { + "description": "Optional. Number of days data will be retained for." + } + }, + "dailyQuotaGb": { + "type": "string", + "defaultValue": "-1", + "metadata": { + "description": "Optional. The workspace daily quota for ingestion in GB. Supports decimal values. Example: '0.5' for 0.5 GB, '2' for 2 GB. Default is '-1' (no limit)." + } + }, + "defaultDataCollectionRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the default Data Collection Rule to use for this workspace. Note: the default DCR is not applicable on workspace creation and the workspace must be listed as a destination in the DCR." + } + }, + "publicNetworkAccessForIngestion": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled", + "SecuredByPerimeter" + ], + "metadata": { + "description": "Optional. The network access type for accessing Log Analytics ingestion." + } + }, + "publicNetworkAccessForQuery": { + "type": "string", + "defaultValue": "Enabled", + "allowedValues": [ + "Enabled", + "Disabled", + "SecuredByPerimeter" + ], + "metadata": { + "description": "Optional. The network access type for accessing Log Analytics query." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource. Only one type of identity is supported: system-assigned or user-assigned, but not both." + } + }, + "features": { + "$ref": "#/definitions/workspaceFeaturesType", + "nullable": true, + "metadata": { + "description": "Optional. The workspace features." + } + }, + "replication": { + "$ref": "#/definitions/workspaceReplicationType", + "nullable": true, + "metadata": { + "description": "Optional. The workspace replication properties." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "forceCmkForQuery": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether customer managed storage is mandatory for query management." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces@2025-07-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), 'SystemAssigned', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Security Admin": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fb1c8493-542b-48eb-b624-b4c8fea62acd')]", + "Security Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '39bc4728-0917-49c7-9d2c-d95423bc2eb4')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.15.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "logAnalyticsWorkspace": { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "features": { + "searchVersion": 1, + "enableLogAccessUsingOnlyResourcePermissions": "[coalesce(tryGet(parameters('features'), 'enableLogAccessUsingOnlyResourcePermissions'), false())]", + "disableLocalAuth": "[coalesce(tryGet(parameters('features'), 'disableLocalAuth'), true())]", + "enableDataExport": "[tryGet(parameters('features'), 'enableDataExport')]", + "immediatePurgeDataOn30Days": "[tryGet(parameters('features'), 'immediatePurgeDataOn30Days')]" + }, + "sku": { + "name": "[parameters('skuName')]", + "capacityReservationLevel": "[if(equals(parameters('skuName'), 'CapacityReservation'), parameters('skuCapacityReservationLevel'), null())]" + }, + "retentionInDays": "[parameters('dataRetention')]", + "workspaceCapping": { + "dailyQuotaGb": "[json(parameters('dailyQuotaGb'))]" + }, + "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]", + "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]", + "forceCmkForQuery": "[parameters('forceCmkForQuery')]", + "replication": "[parameters('replication')]", + "defaultDataCollectionRuleResourceId": "[parameters('defaultDataCollectionRuleResourceId')]" + }, + "identity": "[variables('identity')]" + }, + "logAnalyticsWorkspace_diagnosticSettings": { + "copy": { + "name": "logAnalyticsWorkspace_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[if(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'useThisWorkspace'), false()), resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId'))]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_sentinelOnboarding": { + "condition": "[and(not(empty(filter(coalesce(parameters('gallerySolutions'), createArray()), lambda('item', startsWith(lambdaVariables('item').name, 'SecurityInsights'))))), parameters('onboardWorkspaceToSentinel'))]", + "type": "Microsoft.SecurityInsights/onboardingStates", + "apiVersion": "2025-09-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "default", + "properties": {}, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_roleAssignments": { + "copy": { + "name": "logAnalyticsWorkspace_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_storageInsightConfigs": { + "copy": { + "name": "logAnalyticsWorkspace_storageInsightConfigs", + "count": "[length(coalesce(parameters('storageInsightsConfigs'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-StorageInsightsConfig-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'containers')]" + }, + "tables": { + "value": "[tryGet(coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()], 'tables')]" + }, + "storageAccountResourceId": { + "value": "[coalesce(parameters('storageInsightsConfigs'), createArray())[copyIndex()].storageAccountResourceId]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "140290971998938797" + }, + "name": "Log Analytics Workspace Storage Insight Configs", + "description": "This module deploys a Log Analytics Workspace Storage Insight Config." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('{0}-stinsconfig', last(split(parameters('storageAccountResourceId'), '/')))]", + "metadata": { + "description": "Optional. The name of the storage insights config." + } + }, + "storageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Azure Resource Manager ID of the storage account resource." + } + }, + "containers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the blob containers that the workspace should read." + } + }, + "tables": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The names of the Azure tables that the workspace should read." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-07-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" + }, + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "storageinsightconfig": { + "type": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "containers": "[parameters('containers')]", + "tables": "[parameters('tables')]", + "storageAccount": { + "id": "[parameters('storageAccountResourceId')]", + "key": "[listKeys('storageAccount', '2025-06-01').keys[0].value]" + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage insights configuration." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/storageInsightConfigs', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the storage insight configuration is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the storage insights configuration." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_linkedServices": { + "copy": { + "name": "logAnalyticsWorkspace_linkedServices", + "count": "[length(coalesce(parameters('linkedServices'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-LinkedService-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('linkedServices'), createArray())[copyIndex()].name]" + }, + "resourceId": { + "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'resourceId')]" + }, + "writeAccessResourceId": { + "value": "[tryGet(coalesce(parameters('linkedServices'), createArray())[copyIndex()], 'writeAccessResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14482465616812596213" + }, + "name": "Log Analytics Workspace Linked Services", + "description": "This module deploys a Log Analytics Workspace Linked Service." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the link." + } + }, + "resourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require read access." + } + }, + "writeAccessResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the resource that will be linked to the workspace. This should be used for linking resources which require write access." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-07-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "linkedService": { + "type": "Microsoft.OperationalInsights/workspaces/linkedServices", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resourceId": "[parameters('resourceId')]", + "writeAccessResourceId": "[parameters('writeAccessResourceId')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed linked service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed linked service." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedServices', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the linked service is deployed." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_linkedStorageAccounts": { + "copy": { + "name": "logAnalyticsWorkspace_linkedStorageAccounts", + "count": "[length(coalesce(parameters('linkedStorageAccounts'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-LinkedStorageAccount-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].name]" + }, + "storageAccountIds": { + "value": "[coalesce(parameters('linkedStorageAccounts'), createArray())[copyIndex()].storageAccountIds]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14864721709229272590" + }, + "name": "Log Analytics Workspace Linked Storage Accounts", + "description": "This module deploys a Log Analytics Workspace Linked Storage Account." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "allowedValues": [ + "Query", + "Alerts", + "CustomLogs", + "AzureWatson" + ], + "metadata": { + "description": "Required. Name of the link." + } + }, + "storageAccountIds": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. Linked storage accounts resources Ids." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "linkedStorageAccount": { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "properties": { + "storageAccountIds": "[parameters('storageAccountIds')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed linked storage account." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed linked storage account." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/linkedStorageAccounts', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the linked storage account is deployed." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_savedSearches": { + "copy": { + "name": "logAnalyticsWorkspace_savedSearches", + "count": "[length(coalesce(parameters('savedSearches'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-SavedSearch-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[format('{0}{1}', coalesce(parameters('savedSearches'), createArray())[copyIndex()].name, uniqueString(subscription().id, resourceGroup().id))]" + }, + "etag": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'etag')]" + }, + "displayName": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].displayName]" + }, + "category": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].category]" + }, + "query": { + "value": "[coalesce(parameters('savedSearches'), createArray())[copyIndex()].query]" + }, + "functionAlias": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionAlias')]" + }, + "functionParameters": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'functionParameters')]" + }, + "tags": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'tags')]" + }, + "version": { + "value": "[tryGet(coalesce(parameters('savedSearches'), createArray())[copyIndex()], 'version')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17904092372918022238" + }, + "name": "Log Analytics Workspace Saved Searches", + "description": "This module deploys a Log Analytics Workspace Saved Search." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the saved search." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name for the search." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. Query category." + } + }, + "query": { + "type": "string", + "metadata": { + "description": "Required. Kusto Query to be stored." + } + }, + "tags": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-07-01#properties/properties/properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + }, + "functionAlias": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The function alias if query serves as a function." + } + }, + "functionParameters": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The optional function parameters if query serves as a function. Value should be in the following format: \"param-name1:type1 = default_value1, param-name2:type2 = default_value2\". For more examples and proper syntax please refer to /azure/kusto/query/functions/user-defined-functions." + } + }, + "version": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The version number of the query language." + } + }, + "etag": { + "type": "string", + "defaultValue": "*", + "metadata": { + "description": "Optional. The ETag of the saved search. To override an existing saved search, use \"*\" or specify the current Etag." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "savedSearch": { + "type": "Microsoft.OperationalInsights/workspaces/savedSearches", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "properties": { + "etag": "[parameters('etag')]", + "tags": "[coalesce(parameters('tags'), createArray())]", + "displayName": "[parameters('displayName')]", + "category": "[parameters('category')]", + "query": "[parameters('query')]", + "functionAlias": "[parameters('functionAlias')]", + "functionParameters": "[parameters('functionParameters')]", + "version": "[parameters('version')]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed saved search." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the saved search is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed saved search." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace", + "logAnalyticsWorkspace_linkedStorageAccounts" + ] + }, + "logAnalyticsWorkspace_dataExports": { + "copy": { + "name": "logAnalyticsWorkspace_dataExports", + "count": "[length(coalesce(parameters('dataExports'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-DataExport-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "workspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('dataExports'), createArray())[copyIndex()].name]" + }, + "destination": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'destination')]" + }, + "enable": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'enable')]" + }, + "tableNames": { + "value": "[tryGet(coalesce(parameters('dataExports'), createArray())[copyIndex()], 'tableNames')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17943947755417749524" + }, + "name": "Log Analytics Workspace Data Exports", + "description": "This module deploys a Log Analytics Workspace Data Export." + }, + "definitions": { + "destinationType": { + "type": "object", + "properties": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The destination resource ID." + } + }, + "metaData": { + "type": "object", + "properties": { + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Allows to define an Event Hub name. Not applicable when destination is Storage Account." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The destination metadata." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The data export destination properties." + } + } + }, + "parameters": { + "name": { + "type": "string", + "minLength": 4, + "maxLength": 63, + "metadata": { + "description": "Required. The data export rule name." + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + } + }, + "destination": { + "$ref": "#/definitions/destinationType", + "nullable": true, + "metadata": { + "description": "Optional. Destination properties." + } + }, + "enable": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Active when enabled." + } + }, + "tableNames": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. An array of tables to export, for example: ['Heartbeat', 'SecurityEvent']." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('workspaceName')]" + }, + "dataExport": { + "type": "Microsoft.OperationalInsights/workspaces/dataExports", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "properties": { + "destination": "[parameters('destination')]", + "enable": "[parameters('enable')]", + "tableNames": "[parameters('tableNames')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the data export." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the data export." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataExports', parameters('workspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the data export was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_dataSources": { + "copy": { + "name": "logAnalyticsWorkspace_dataSources", + "count": "[length(coalesce(parameters('dataSources'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-DataSource-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].name]" + }, + "kind": { + "value": "[coalesce(parameters('dataSources'), createArray())[copyIndex()].kind]" + }, + "linkedResourceId": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'linkedResourceId')]" + }, + "eventLogName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventLogName')]" + }, + "eventTypes": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'eventTypes')]" + }, + "objectName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'objectName')]" + }, + "instanceName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'instanceName')]" + }, + "intervalSeconds": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'intervalSeconds')]" + }, + "counterName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'counterName')]" + }, + "state": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'state')]" + }, + "syslogName": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogName')]" + }, + "syslogSeverities": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'syslogSeverities')]" + }, + "performanceCounters": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'performanceCounters')]" + }, + "tags": { + "value": "[tryGet(coalesce(parameters('dataSources'), createArray())[copyIndex()], 'tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "15360290236166491819" + }, + "name": "Log Analytics Workspace Datasources", + "description": "This module deploys a Log Analytics Workspace Data Source." + }, + "parameters": { + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Log Analytics workspace. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the data source." + } + }, + "kind": { + "type": "string", + "defaultValue": "AzureActivityLog", + "allowedValues": [ + "AzureActivityLog", + "WindowsEvent", + "WindowsPerformanceCounter", + "IISLogs", + "LinuxSyslog", + "LinuxSyslogCollection", + "LinuxPerformanceObject", + "LinuxPerformanceCollection" + ], + "metadata": { + "description": "Optional. The kind of the data source." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-07-01#properties/tags" + }, + "description": "Optional. Tags to configure in the resource." + }, + "nullable": true + }, + "linkedResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the resource to be linked." + } + }, + "eventLogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Windows event log name to configure when kind is WindowsEvent." + } + }, + "eventTypes": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Windows event types to configure when kind is WindowsEvent." + } + }, + "objectName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the object to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "instanceName": { + "type": "string", + "defaultValue": "*", + "metadata": { + "description": "Optional. Name of the instance to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "intervalSeconds": { + "type": "int", + "defaultValue": 60, + "metadata": { + "description": "Optional. Interval in seconds to configure when kind is WindowsPerformanceCounter or LinuxPerformanceObject." + } + }, + "performanceCounters": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. List of counters to configure when the kind is LinuxPerformanceObject." + } + }, + "counterName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Counter name to configure when kind is WindowsPerformanceCounter." + } + }, + "state": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. State to configure when kind is IISLogs or LinuxSyslogCollection or LinuxPerformanceCollection." + } + }, + "syslogName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. System log to configure when kind is LinuxSyslog." + } + }, + "syslogSeverities": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Severities to configure when kind is LinuxSyslog." + } + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "dataSource": { + "type": "Microsoft.OperationalInsights/workspaces/dataSources", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", + "kind": "[parameters('kind')]", + "tags": "[parameters('tags')]", + "properties": { + "linkedResourceId": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'AzureActivityLog')), parameters('linkedResourceId'), null())]", + "eventLogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventLogName'), null())]", + "eventTypes": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsEvent')), parameters('eventTypes'), null())]", + "objectName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('objectName'), null())]", + "instanceName": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('instanceName'), null())]", + "intervalSeconds": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'WindowsPerformanceCounter'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('intervalSeconds'), null())]", + "counterName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'WindowsPerformanceCounter')), parameters('counterName'), null())]", + "state": "[if(and(not(empty(parameters('kind'))), or(or(equals(parameters('kind'), 'IISLogs'), equals(parameters('kind'), 'LinuxSyslogCollection')), equals(parameters('kind'), 'LinuxPerformanceCollection'))), parameters('state'), null())]", + "syslogName": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxSyslog')), parameters('syslogName'), null())]", + "syslogSeverities": "[if(and(not(empty(parameters('kind'))), or(equals(parameters('kind'), 'LinuxSyslog'), equals(parameters('kind'), 'LinuxPerformanceObject'))), parameters('syslogSeverities'), null())]", + "performanceCounters": "[if(and(not(empty(parameters('kind'))), equals(parameters('kind'), 'LinuxPerformanceObject')), parameters('performanceCounters'), null())]" + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed data source." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/dataSources', parameters('logAnalyticsWorkspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the data source is deployed." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed data source." + }, + "value": "[parameters('name')]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_tables": { + "copy": { + "name": "logAnalyticsWorkspace_tables", + "count": "[length(coalesce(parameters('tables'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-Table-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "workspaceName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" + }, + "plan": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'plan')]" + }, + "schema": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'schema')]" + }, + "retentionInDays": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'retentionInDays')]" + }, + "totalRetentionInDays": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'totalRetentionInDays')]" + }, + "restoredLogs": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'restoredLogs')]" + }, + "searchResults": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'searchResults')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'roleAssignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "18383178824663161801" + }, + "name": "Log Analytics Workspace Tables", + "description": "This module deploys a Log Analytics Workspace Table." + }, + "definitions": { + "restoredLogsType": { + "type": "object", + "properties": { + "sourceTable": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table to restore data from." + } + }, + "startRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the restore from (UTC)." + } + }, + "endRestoreTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the restore by (UTC)." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the restore operation that initiated the table." + } + }, + "schemaType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The table name." + } + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/columnType" + }, + "metadata": { + "description": "Required. A list of table custom columns." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The table display name." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The table schema." + } + }, + "columnType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The column name." + } + }, + "type": { + "type": "string", + "allowedValues": [ + "boolean", + "dateTime", + "dynamic", + "guid", + "int", + "long", + "real", + "string" + ], + "metadata": { + "description": "Required. The column type." + } + }, + "dataTypeHint": { + "type": "string", + "allowedValues": [ + "armPath", + "guid", + "ip", + "uri" + ], + "nullable": true, + "metadata": { + "description": "Optional. The column data type logical hint." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The column description." + } + }, + "displayName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Column display name." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the table column." + } + }, + "searchResultsType": { + "type": "object", + "properties": { + "query": { + "type": "string", + "metadata": { + "description": "Required. The search job query." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The search description." + } + }, + "limit": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Limit the search job to return up to specified number of rows." + } + }, + "startSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to start the search from (UTC)." + } + }, + "endSearchTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The timestamp to end the search by (UTC)." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The parameters of the search job that initiated the table." + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } + }, + "workspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent workspaces. Required if the template is used in a standalone deployment." + } + }, + "plan": { + "type": "string", + "defaultValue": "Analytics", + "allowedValues": [ + "Basic", + "Analytics" + ], + "metadata": { + "description": "Optional. Instruct the system how to handle and charge the logs ingested to this table." + } + }, + "restoredLogs": { + "$ref": "#/definitions/restoredLogsType", + "nullable": true, + "metadata": { + "description": "Optional. Restore parameters." + } + }, + "retentionInDays": { + "type": "int", + "nullable": true, + "minValue": 4, + "maxValue": 730, + "metadata": { + "description": "Optional. The table retention in days, between 4 and 730. Don't provide to use the default workspace retention." + } + }, + "schema": { + "$ref": "#/definitions/schemaType", + "nullable": true, + "metadata": { + "description": "Optional. Table's schema." + } + }, + "searchResults": { + "$ref": "#/definitions/searchResultsType", + "nullable": true, + "metadata": { + "description": "Optional. Parameters of the search job that initiated this table." + } + }, + "totalRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 4, + "maxValue": 2555, + "metadata": { + "description": "Optional. The table total retention in days, between 4 and 2555. Don't provide use the default table retention." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Log Analytics Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '92aaf0da-9dab-42b6-94a3-d43ce8d16293')]", + "Log Analytics Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '73c42c96-874c-492b-b04d-ab87d138a893')]", + "Monitoring Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '749f88d5-cbae-40b8-bcfc-e573ddc772fa')]", + "Monitoring Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '43d0d8ad-25c7-4714-9337-8ba259a9fe05')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "workspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-07-01", + "name": "[parameters('workspaceName')]" + }, + "table": { + "type": "Microsoft.OperationalInsights/workspaces/tables", + "apiVersion": "2025-07-01", + "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", + "properties": { + "plan": "[parameters('plan')]", + "restoredLogs": "[parameters('restoredLogs')]", + "retentionInDays": "[coalesce(parameters('retentionInDays'), -1)]", + "schema": "[parameters('schema')]", + "searchResults": "[parameters('searchResults')]", + "totalRetentionInDays": "[coalesce(parameters('totalRetentionInDays'), -1)]" + } + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}/tables/{1}', parameters('workspaceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "table" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the table." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('workspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the table was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + }, + "logAnalyticsWorkspace_solutions": { + "copy": { + "name": "logAnalyticsWorkspace_solutions", + "count": "[length(coalesce(parameters('gallerySolutions'), createArray()))]" + }, + "condition": "[not(empty(parameters('gallerySolutions')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-LAW-Solution-{1}', uniqueString(subscription().id, resourceGroup().id, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].name]" + }, + "location": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceName": { + "value": "[parameters('name')]" + }, + "plan": { + "value": "[coalesce(parameters('gallerySolutions'), createArray())[copyIndex()].plan]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.32.4.45862", + "templateHash": "10255889523646649592" + }, + "name": "Operations Management Solutions", + "description": "This module deploys an Operations Management Solution.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "solutionPlanType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the solution to be created.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, it can be anything.\nThe solution type is case-sensitive.\nIf not provided, the value of the `name` parameter will be used." + } + }, + "product": { + "type": "string", + "metadata": { + "description": "Required. The product name of the deployed solution.\nFor Microsoft published gallery solution it should be `OMSGallery/{solutionType}`, for example `OMSGallery/AntiMalware`.\nFor a third party solution, it can be anything.\nThis is case sensitive." + } + }, + "publisher": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`, which is the default value." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the solution.\nFor solutions authored by Microsoft, the name must be in the pattern: `SolutionType(WorkspaceName)`, for example: `AntiMalware(contoso-Logs)`.\nFor solutions authored by third parties, the name should be in the pattern: `SolutionType[WorkspaceName]`, for example `MySolution[contoso-Logs]`.\nThe solution type is case-sensitive." + } + }, + "plan": { + "$ref": "#/definitions/solutionPlanType", + "metadata": { + "description": "Required. Plan for solution object supported by the OperationsManagement resource provider." + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Log Analytics workspace where the solution will be deployed/enabled." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.operationsmanagement-solution.{0}.{1}', replace('0.3.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "logAnalyticsWorkspace": { + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-06-01", + "name": "[parameters('logAnalyticsWorkspaceName')]" + }, + "solution": { + "type": "Microsoft.OperationsManagement/solutions", + "apiVersion": "2015-11-01-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "properties": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]" + }, + "plan": { + "name": "[coalesce(tryGet(parameters('plan'), 'name'), parameters('name'))]", + "promotionCode": "", + "product": "[parameters('plan').product]", + "publisher": "[coalesce(tryGet(parameters('plan'), 'publisher'), 'Microsoft')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed solution." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed solution." + }, + "value": "[resourceId('Microsoft.OperationsManagement/solutions', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the solution is deployed." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('solution', '2015-11-01-preview', 'full').location]" + } + } + } + }, + "dependsOn": [ + "logAnalyticsWorkspace" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed log analytics workspace." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed log analytics workspace." + }, + "value": "[resourceGroup().name]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed log analytics workspace." + }, + "value": "[parameters('name')]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "The ID associated with the workspace." + }, + "value": "[reference('logAnalyticsWorkspace').customerId]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('logAnalyticsWorkspace', '2025-07-01', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('logAnalyticsWorkspace', '2025-07-01', 'full'), 'identity'), 'principalId')]" + }, + "primarySharedKey": { + "type": "securestring", + "metadata": { + "description": "The primary shared key of the log analytics workspace." + }, + "value": "[listKeys('logAnalyticsWorkspace', '2025-07-01').primarySharedKey]" + }, + "secondarySharedKey": { + "type": "securestring", + "metadata": { + "description": "The secondary shared key of the log analytics workspace." + }, + "value": "[listKeys('logAnalyticsWorkspace', '2025-07-01').secondarySharedKey]" + } + } + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Log Analytics workspace." + }, + "value": "[reference('workspace').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace." + }, + "value": "[reference('workspace').outputs.name.value]" + }, + "location": { + "type": "string", + "metadata": { + "description": "Location of the workspace." + }, + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Log Analytics workspace customer ID." + }, + "value": "[reference('workspace').outputs.logAnalyticsWorkspaceId.value]" + } + } + } + } + }, + { + "condition": "[empty(parameters('existingFoundryProjectResourceId'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[parameters('azureAiServiceLocation')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "11271058625644540786" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Required. Solution name suffix used to generate resource names." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('aif-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Optional. Override name for the AI Services account. Defaults to aif-{solutionName}." + } + }, + "projectName": { + "type": "string", + "defaultValue": "[format('proj-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Optional. Override name for the AI Foundry project. Defaults to proj-{solutionName}." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Required. Azure region for the resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources." + } + }, + "skuName": { + "type": "string", + "defaultValue": "S0", + "metadata": { + "description": "Optional. SKU name for the AI Services account." + } + }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether to disable local (key-based) authentication." + } + }, + "allowProjectManagement": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether to allow project management (AI Foundry hub)." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Optional. Public network access setting." + } + }, + "identityType": { + "type": "string", + "defaultValue": "SystemAssigned", + "allowedValues": [ + "SystemAssigned", + "UserAssigned", + "SystemAssigned, UserAssigned", + "None" + ], + "metadata": { + "description": "Optional. Managed identity type for the resources." + } + }, + "networkAclsDefaultAction": { + "type": "string", + "defaultValue": "Allow", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Optional. Network ACLs default action." + } + }, + "diagnosticSettings": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Diagnostic settings for the resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "roleAssignments": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create on the AI Services account." + } + } + }, + "resources": { + "aiServicesResource": { + "existing": true, + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-12-01", + "name": "[parameters('name')]", + "dependsOn": [ + "aiServicesAccount" + ] + }, + "aiProject": { + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-12-01", + "name": "[format('{0}/{1}', parameters('name'), parameters('projectName'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "AIServices", + "identity": { + "type": "[parameters('identityType')]" + }, + "properties": {}, + "dependsOn": [ + "aiServicesAccount" + ] + }, + "aiServicesAccount": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.cognitive-services.account.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "sku": { + "value": "[parameters('skuName')]" + }, + "kind": { + "value": "AIServices" + }, + "disableLocalAuth": { + "value": "[parameters('disableLocalAuth')]" + }, + "allowProjectManagement": { + "value": "[parameters('allowProjectManagement')]" + }, + "customSubDomainName": { + "value": "[parameters('name')]" + }, + "networkAcls": { + "value": { + "defaultAction": "[parameters('networkAclsDefaultAction')]", + "virtualNetworkRules": [], + "ipRules": [] + } + }, + "publicNetworkAccess": { + "value": "[parameters('publicNetworkAccess')]" + }, + "managedIdentities": { + "value": { + "systemAssigned": true + } + }, + "diagnosticSettings": { + "value": "[parameters('diagnosticSettings')]" + }, + "deployments": { + "value": [] + }, + "roleAssignments": { + "value": "[parameters('roleAssignments')]" + }, + "privateEndpoints": { + "value": [] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "8642151282041103672" + }, + "name": "Cognitive Services", + "description": "This module deploys a Cognitive Service." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoint output." + } + }, + "deploymentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of cognitive service account deployment." + } + }, + "model": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account deployment model." + } + }, + "format": { + "type": "string", + "metadata": { + "description": "Required. The format of Cognitive Services account deployment model." + } + }, + "version": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. The version of Cognitive Services account deployment model. Required if the model does not have a default version." + } + } + }, + "metadata": { + "description": "Required. Properties of Cognitive Services account deployment model." + } + }, + "sku": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource model definition representing SKU." + } + }, + "capacity": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The capacity of the resource model definition representing SKU." + } + }, + "tier": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The tier of the resource model definition representing SKU." + } + }, + "size": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The size of the resource model definition representing SKU." + } + }, + "family": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The family of the resource model definition representing SKU." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource model definition representing SKU." + } + }, + "raiPolicyName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of RAI policy." + } + }, + "versionUpgradeOption": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version upgrade option." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account deployment." + } + }, + "endpointType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Type of the endpoint." + } + }, + "endpoint": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The endpoint URI." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cognitive services account endpoint." + } + }, + "secretsExportConfigurationType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The key vault name where to store the keys and connection strings generated by the modules." + } + }, + "accessKey1Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name for the accessKey1 secret to create." + } + }, + "accessKey2Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name for the accessKey2 secret to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of the secrets exported to the provided Key Vault." + } + }, + "commitmentPlanType": { + "type": "object", + "properties": { + "autoRenew": { + "type": "bool", + "metadata": { + "description": "Required. Whether the plan should auto-renew at the end of the current commitment period." + } + }, + "current": { + "type": "object", + "properties": { + "count": { + "type": "int", + "metadata": { + "description": "Required. The number of committed instances (e.g., number of containers or cores)." + } + }, + "tier": { + "type": "string", + "metadata": { + "description": "Required. The tier of the commitment plan (e.g., T1, T2)." + } + } + }, + "metadata": { + "description": "Required. The current commitment configuration." + } + }, + "hostingModel": { + "type": "string", + "metadata": { + "description": "Required. The hosting model for the commitment plan. (e.g., DisconnectedContainer, ConnectedContainer, ProvisionedWeb, Web)." + } + }, + "planType": { + "type": "string", + "metadata": { + "description": "Required. The plan type indicating which capability the plan applies to (e.g., NTTS, STT, CUSTOMSTT, ADDON)." + } + }, + "commitmentPlanGuid": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of an existing commitment plan to update. Set to null to create a new plan." + } + }, + "next": { + "type": "object", + "properties": { + "count": { + "type": "int", + "metadata": { + "description": "Required. The number of committed instances for the next period." + } + }, + "tier": { + "type": "string", + "metadata": { + "description": "Required. The tier for the next commitment period." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The configuration of the next commitment period, if scheduled." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a disconnected container commitment plan." + } + }, + "networkInjectionType": { + "type": "object", + "properties": { + "scenario": { + "type": "string", + "allowedValues": [ + "agent", + "none" + ], + "metadata": { + "description": "Required. The scenario for the network injection." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. The Resource ID of the subnet on the Virtual Network on which to inject." + } + }, + "useMicrosoftManagedNetwork": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether to use Microsoft Managed Network. Defaults to false." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "Type for network configuration in AI Foundry where virtual network injection occurs to secure scenarios like Agents entirely within a private network." + } + }, + "_1.secretSetOutputType": { + "type": "object", + "properties": { + "secretResourceId": { + "type": "string", + "metadata": { + "description": "The resourceId of the exported secret." + } + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The secret URI of the exported secret." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "_2.lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.privateEndpointIpConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "customerManagedKeyType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, the deployment will use the latest version available at deployment time." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type does not support auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "managedIdentityAllType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "privateEndpointSingleServiceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private Endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the Private Endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subresource to deploy the Private Endpoint for. For example \"vault\" for a Key Vault Private Endpoint." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_2.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS Zone Group to configure for the Private Endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/_2.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_2.privateEndpointIpConfigurationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the Private Endpoint. This will be used to map to the first-party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the Private Endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the Private Endpoint." + } + }, + "lock": { + "$ref": "#/definitions/_2.lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/_2.roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags" + }, + "description": "Optional. Tags to be applied on all resources/Resource Groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can be assumed (i.e., for services that only have one Private Endpoint type like 'vault' for key vault).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_1.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of Cognitive Services account." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "AIServices", + "AnomalyDetector", + "CognitiveServices", + "ComputerVision", + "ContentModerator", + "ContentSafety", + "ConversationalLanguageUnderstanding", + "CustomVision.Prediction", + "CustomVision.Training", + "Face", + "FormRecognizer", + "HealthInsights", + "ImmersiveReader", + "Internal.AllInOne", + "LUIS", + "LUIS.Authoring", + "LanguageAuthoring", + "MetricsAdvisor", + "OpenAI", + "Personalizer", + "QnAMaker.v2", + "SpeechServices", + "TextAnalytics", + "TextTranslation" + ], + "metadata": { + "description": "Required. Kind of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region." + } + }, + "sku": { + "type": "string", + "defaultValue": "S0", + "allowedValues": [ + "C2", + "C3", + "C4", + "F0", + "F1", + "S", + "S0", + "S1", + "S10", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S8", + "S9", + "DC0" + ], + "metadata": { + "description": "Optional. SKU of the Cognitive Services account. Use 'Get-AzCognitiveServicesAccountSku' to determine a valid combinations of 'kind' and 'SKU' for your Azure region." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "publicNetworkAccess": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set." + } + }, + "customSubDomainName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Conditional. Subdomain name used for token-based authentication. Required if 'networkAcls' or 'privateEndpoints' are set." + } + }, + "networkAcls": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. A collection of rules governing the accessibility from specific network locations." + } + }, + "networkInjections": { + "$ref": "#/definitions/networkInjectionType", + "nullable": true, + "metadata": { + "description": "Optional. Specifies in AI Foundry where virtual network injection occurs to secure scenarios like Agents entirely within a private network." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointSingleServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "allowedFqdnList": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. List of allowed FQDN." + } + }, + "apiProperties": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The API properties for special APIs." + } + }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allow only Azure AD authentication. Should be enabled for security reasons." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "dynamicThrottlingEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The flag to enable dynamic throttling." + } + }, + "migrationToken": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. Resource migration token." + } + }, + "restore": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Restore a soft-deleted cognitive service at deployment time. Will fail if no such soft-deleted resource exists." + } + }, + "restrictOutboundNetworkAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Restrict outbound network access." + } + }, + "userOwnedStorage": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.CognitiveServices/accounts@2025-04-01-preview#properties/properties/properties/userOwnedStorage" + }, + "description": "Optional. The storage accounts for this resource." + }, + "nullable": true + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "deployments": { + "type": "array", + "items": { + "$ref": "#/definitions/deploymentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of deployments about cognitive service accounts to create." + } + }, + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Key vault reference and secret settings for the module's secrets export." + } + }, + "allowProjectManagement": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable project management feature for AI Foundry." + } + }, + "commitmentPlans": { + "type": "array", + "items": { + "$ref": "#/definitions/commitmentPlanType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Commitment plans to deploy for the cognitive services account." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned, UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "Cognitive Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68')]", + "Cognitive Services Custom Vision Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c1ff6cc2-c111-46fe-8896-e0ef812ad9f3')]", + "Cognitive Services Custom Vision Deployment": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5c4089e1-6d96-4d2f-b296-c1bc7137275f')]", + "Cognitive Services Custom Vision Labeler": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '88424f51-ebe7-446f-bc41-7fa16989e96c')]", + "Cognitive Services Custom Vision Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '93586559-c37d-4a6b-ba08-b9f0940c2d73')]", + "Cognitive Services Custom Vision Trainer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a5ae4ab-0d65-4eeb-be61-29fc9b54394b')]", + "Cognitive Services Data Reader (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b59867f0-fa02-499b-be73-45a86b5b3e1c')]", + "Cognitive Services Face Recognizer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '9894cab4-e18a-44aa-828b-cb588cd6f2d7')]", + "Cognitive Services Immersive Reader User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b2de6794-95db-4659-8781-7e080d3f2b9d')]", + "Cognitive Services Language Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f07febfe-79bc-46b1-8b37-790e26e6e498')]", + "Cognitive Services Language Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7628b7b8-a8b2-4cdc-b46f-e9b35248918e')]", + "Cognitive Services Language Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2310ca1-dc64-4889-bb49-c8e0fa3d47a8')]", + "Cognitive Services LUIS Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f72c8140-2111-481c-87ff-72b910f6e3f8')]", + "Cognitive Services LUIS Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18e81cdc-4e98-4e29-a639-e7d10c5a6226')]", + "Cognitive Services LUIS Writer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '6322a993-d5c9-4bed-b113-e49bbea25b27')]", + "Cognitive Services Metrics Advisor Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'cb43c632-a144-4ec5-977c-e80c4affc34a')]", + "Cognitive Services Metrics Advisor User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '3b20f47b-3825-43cb-8114-4bd2201156a8')]", + "Cognitive Services OpenAI Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')]", + "Cognitive Services OpenAI User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", + "Cognitive Services QnA Maker Editor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f4cc2bf9-21be-47a1-bdf1-5c5804381025')]", + "Cognitive Services QnA Maker Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '466ccd10-b268-4a11-b098-b4849f024126')]", + "Cognitive Services Speech Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0e75ca1e-0464-4b4d-8b93-68208a576181')]", + "Cognitive Services Speech User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f2dc8367-1007-4938-bd23-fe263f013447')]", + "Cognitive Services User": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')]", + "Azure AI Developer": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]", + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]" + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))), and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2025-05-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.cognitiveservices-account.{0}.{1}', replace('0.14.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2025-05-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2025-01-31-preview", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]" + }, + "cognitiveService": { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-06-01", + "name": "[parameters('name')]", + "kind": "[parameters('kind')]", + "identity": "[variables('identity')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('sku')]" + }, + "properties": { + "allowProjectManagement": "[parameters('allowProjectManagement')]", + "customSubDomainName": "[parameters('customSubDomainName')]", + "networkAcls": "[if(not(empty(coalesce(parameters('networkAcls'), createObject()))), createObject('defaultAction', tryGet(parameters('networkAcls'), 'defaultAction'), 'virtualNetworkRules', coalesce(tryGet(parameters('networkAcls'), 'virtualNetworkRules'), createArray()), 'ipRules', coalesce(tryGet(parameters('networkAcls'), 'ipRules'), createArray())), null())]", + "networkInjections": "[if(not(empty(parameters('networkInjections'))), createArray(createObject('scenario', tryGet(parameters('networkInjections'), 'scenario'), 'subnetArmId', tryGet(parameters('networkInjections'), 'subnetResourceId'), 'useMicrosoftManagedNetwork', coalesce(tryGet(parameters('networkInjections'), 'useMicrosoftManagedNetwork'), false()))), null())]", + "publicNetworkAccess": "[if(not(equals(parameters('publicNetworkAccess'), null())), parameters('publicNetworkAccess'), if(not(empty(parameters('networkAcls'))), 'Enabled', 'Disabled'))]", + "allowedFqdnList": "[parameters('allowedFqdnList')]", + "apiProperties": "[parameters('apiProperties')]", + "disableLocalAuth": "[parameters('disableLocalAuth')]", + "encryption": "[if(not(empty(parameters('customerManagedKey'))), createObject('keySource', 'Microsoft.KeyVault', 'keyVaultProperties', createObject('identityClientId', if(not(empty(coalesce(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), ''))), reference('cMKUserAssignedIdentity').clientId, null()), 'keyVaultUri', if(not(variables('isHSMManagedCMK')), reference('cMKKeyVault').vaultUri, format('https://{0}.managedhsm.azure.net/', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')))), 'keyName', parameters('customerManagedKey').keyName, 'keyVersion', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(not(variables('isHSMManagedCMK')), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')), fail('Managed HSM CMK encryption requires specifying the ''keyVersion''.'))))), null())]", + "migrationToken": "[parameters('migrationToken')]", + "restore": "[parameters('restore')]", + "restrictOutboundNetworkAccess": "[parameters('restrictOutboundNetworkAccess')]", + "userOwnedStorage": "[if(not(empty(parameters('userOwnedStorage'))), parameters('userOwnedStorage'), null())]", + "dynamicThrottlingEnabled": "[parameters('dynamicThrottlingEnabled')]" + }, + "dependsOn": [ + "cMKKeyVault", + "cMKKeyVault::cMKKey", + "cMKUserAssignedIdentity" + ] + }, + "cognitiveService_deployments": { + "copy": { + "name": "cognitiveService_deployments", + "count": "[length(coalesce(parameters('deployments'), createArray()))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('name'), coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'name'), format('{0}-deployments', parameters('name'))))]", + "properties": { + "model": "[coalesce(parameters('deployments'), createArray())[copyIndex()].model]", + "raiPolicyName": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'raiPolicyName')]", + "versionUpgradeOption": "[tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'versionUpgradeOption')]" + }, + "sku": "[coalesce(tryGet(coalesce(parameters('deployments'), createArray())[copyIndex()], 'sku'), createObject('name', parameters('sku'), 'capacity', tryGet(parameters('sku'), 'capacity'), 'tier', tryGet(parameters('sku'), 'tier'), 'size', tryGet(parameters('sku'), 'size'), 'family', tryGet(parameters('sku'), 'family')))]", + "dependsOn": [ + "cognitiveService" + ] + }, + "cognitiveService_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "cognitiveService" + ] + }, + "cognitiveService_commitmentPlans": { + "copy": { + "name": "cognitiveService_commitmentPlans", + "count": "[length(coalesce(parameters('commitmentPlans'), createArray()))]" + }, + "type": "Microsoft.CognitiveServices/accounts/commitmentPlans", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('name'), format('{0}-{1}', coalesce(parameters('commitmentPlans'), createArray())[copyIndex()].hostingModel, coalesce(parameters('commitmentPlans'), createArray())[copyIndex()].planType))]", + "properties": "[coalesce(parameters('commitmentPlans'), createArray())[copyIndex()]]", + "dependsOn": [ + "cognitiveService" + ] + }, + "cognitiveService_diagnosticSettings": { + "copy": { + "name": "cognitiveService_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "cognitiveService" + ] + }, + "cognitiveService_roleAssignments": { + "copy": { + "name": "cognitiveService_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "cognitiveService" + ] + }, + "cognitiveService_privateEndpoints": { + "copy": { + "name": "cognitiveService_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '/')), coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account'), copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), 'groupIds', createArray(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'service'), 'account')), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a private dns zone group." + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "description": "The type of a private DNS zone group configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + }, + "nullable": true + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + }, + "nullable": true + }, + "customDnsConfigs": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, + "description": "Optional. Custom DNS configurations." + }, + "nullable": true + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, + "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." + }, + "nullable": true + }, + "privateLinkServiceConnections": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-10-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.5.1644", + "templateHash": "24141742673128945" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-10-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]" + } + } + } + ] + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "cognitiveService" + ] + }, + "secretsExport": { + "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('cognitiveService', '2025-06-01').key1)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('cognitiveService', '2025-06-01').key2)), createArray()))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "13968722110082077308" + } + }, + "definitions": { + "secretSetOutputType": { + "type": "object", + "properties": { + "secretResourceId": { + "type": "string", + "metadata": { + "description": "The resourceId of the exported secret." + } + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The secret URI of the exported secret." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "secretToSetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret to set." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret to set." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the secret to set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Key Vault to set the ecrets in." + } + }, + "secretsToSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretToSetType" + }, + "metadata": { + "description": "Required. The secrets to set in the Key Vault." + } + } + }, + "resources": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2025-05-01", + "name": "[parameters('keyVaultName')]" + }, + "secrets": { + "copy": { + "name": "secrets", + "count": "[length(parameters('secretsToSet'))]" + }, + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2025-05-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]", + "properties": { + "value": "[parameters('secretsToSet')[copyIndex()].value]" + } + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetOutputType" + }, + "metadata": { + "description": "The references to the secrets exported to the provided Key Vault." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]", + "input": { + "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]", + "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]", + "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]" + } + } + } + } + } + }, + "dependsOn": [ + "cognitiveService" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the cognitive services account." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cognitive services account." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the cognitive services account was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "The service endpoint of the cognitive services account." + }, + "value": "[reference('cognitiveService').endpoint]" + }, + "endpoints": { + "$ref": "#/definitions/endpointType", + "metadata": { + "description": "All endpoints available for the cognitive services account, types depends on the cognitive service kind." + }, + "value": "[reference('cognitiveService').endpoints]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('cognitiveService', '2025-06-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('cognitiveService', '2025-06-01', 'full').location]" + }, + "exportedSecrets": { + "$ref": "#/definitions/secretsOutputType", + "metadata": { + "description": "A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name." + }, + "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the congitive services account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('cognitiveService_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "primaryKey": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "The primary access key." + }, + "value": "[if(not(parameters('disableLocalAuth')), listKeys('cognitiveService', '2025-06-01').key1, null())]" + }, + "secondaryKey": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "The secondary access key." + }, + "value": "[if(not(parameters('disableLocalAuth')), listKeys('cognitiveService', '2025-06-01').key2, null())]" + } + } + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services account." + }, + "value": "[reference('aiServicesAccount').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the AI Services account." + }, + "value": "[reference('aiServicesAccount').outputs.name.value]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "Endpoint of the AI Services account." + }, + "value": "[reference('aiServicesAccount').outputs.endpoint.value]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID of the AI Services account." + }, + "value": "[reference('aiServicesAccount').outputs.systemAssignedMIPrincipalId.value]" + }, + "projectResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Foundry project." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName'))]" + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Name of the AI Foundry project." + }, + "value": "[parameters('projectName')]" + }, + "projectEndpoint": { + "type": "string", + "metadata": { + "description": "AI Foundry project endpoint." + }, + "value": "[reference('aiProject').endpoints['AI Foundry API']]" + }, + "projectIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID of the project." + }, + "value": "[reference('aiProject', '2025-12-01', 'full').identity.principalId]" + } + } + } + } + }, + { + "condition": "[variables('useExistingAIProject')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)]", + "subscriptionId": "[variables('aiServiceSubscription')]", + "resourceGroup": "[variables('aiServiceResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[8]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value))]", + "projectName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[10]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectName.value))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "3857831373642305916" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the existing Cognitive Services account." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Required. The name of the existing AI project." + } + } + }, + "resources": [], + "outputs": { + "aiFoundryPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the AI Foundry system-assigned managed identity (empty if none)." + }, + "value": "[coalesce(tryGet(reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2025-12-01', 'full').identity, 'principalId'), '')]" + }, + "aiProjectPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the AI Project system-assigned managed identity (empty if none)." + }, + "value": "[coalesce(tryGet(reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName')), '2025-12-01', 'full').identity, 'principalId'), '')]" + }, + "aiServicesAccountName": { + "type": "string", + "metadata": { + "description": "The name of the AI Services account." + }, + "value": "[parameters('name')]" + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "The name of the AI project." + }, + "value": "[parameters('projectName')]" + }, + "aiFoundryEndpoint": { + "type": "string", + "metadata": { + "description": "The endpoint URL for the Azure OpenAI service." + }, + "value": "[format('https://{0}.openai.azure.com/', parameters('name'))]" + }, + "projectEndpoint": { + "type": "string", + "metadata": { + "description": "The endpoint URL for the AI Foundry project." + }, + "value": "[format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('name'), parameters('projectName'))]" + }, + "aiFoundryResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the AI Services account." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.foundry-storage-conn.{0}', parameters('solutionName')), 64)]", + "subscriptionId": "[variables('aiServiceSubscription')]", + "resourceGroup": "[variables('aiServiceResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "aiServicesAccountName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[8]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value))]", + "projectName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[10]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectName.value))]", + "category": { + "value": "AzureBlob" + }, + "target": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.blobEndpoint.value]" + }, + "authType": { + "value": "AAD" + }, + "metadata": { + "value": { + "ResourceId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]", + "AccountName": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]", + "ContainerName": "default" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "17598394785963531330" + } + }, + "parameters": { + "aiServicesAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the parent AI Services account." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry project." + } + }, + "solutionName": { + "type": "string", + "metadata": { + "description": "Required. Solution name suffix used to generate the connection name." + } + }, + "connectionName": { + "type": "string", + "defaultValue": "[toLower(format('{0}-connection-{1}', parameters('category'), parameters('solutionName')))]", + "metadata": { + "description": "Optional. Connection name. Defaults to lowercase category with solution suffix." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. Connection category (e.g., CognitiveSearch, AzureBlob, AppInsights, RemoteTool)." + } + }, + "target": { + "type": "string", + "metadata": { + "description": "Required. Connection target (URL or resource ID)." + } + }, + "authType": { + "type": "string", + "metadata": { + "description": "Required. Authentication type (e.g., AAD, ApiKey, ProjectManagedIdentity)." + } + }, + "isSharedToAll": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the connection is shared to all project users." + } + }, + "isDefault": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether this is the default connection for its category." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Connection metadata object." + } + }, + "credentialsKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Credentials key (for ApiKey auth type)." + } + } + }, + "variables": { + "baseProperties": { + "category": "[parameters('category')]", + "target": "[parameters('target')]", + "authType": "[parameters('authType')]", + "isSharedToAll": "[parameters('isSharedToAll')]", + "metadata": "[parameters('metadata')]" + }, + "optionalDefault": "[if(parameters('isDefault'), createObject('isDefault', true()), createObject())]", + "optionalCredentials": "[if(not(empty(parameters('credentialsKey'))), createObject('credentials', createObject('key', parameters('credentialsKey'))), createObject())]" + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/projects/connections", + "apiVersion": "2025-12-01", + "name": "[format('{0}/{1}/{2}', parameters('aiServicesAccountName'), parameters('projectName'), parameters('connectionName'))]", + "properties": "[union(variables('baseProperties'), variables('optionalDefault'), variables('optionalCredentials'))]" + } + ], + "outputs": { + "connectionName": { + "type": "string", + "metadata": { + "description": "Connection name." + }, + "value": "[parameters('connectionName')]" + }, + "connectionId": { + "type": "string", + "metadata": { + "description": "Connection resource ID." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects/connections', parameters('aiServicesAccountName'), parameters('projectName'), parameters('connectionName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "copy": { + "name": "model_deployments", + "count": "[length(variables('aiModelDeployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.model-deployment-{0}.{1}', copyIndex(), parameters('solutionName')), 64)]", + "subscriptionId": "[variables('aiServiceSubscription')]", + "resourceGroup": "[variables('aiServiceResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesAccountName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[8]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value))]", + "deploymentName": { + "value": "[variables('aiModelDeployments')[copyIndex()].name]" + }, + "modelName": { + "value": "[variables('aiModelDeployments')[copyIndex()].model]" + }, + "modelVersion": { + "value": "[variables('aiModelDeployments')[copyIndex()].version]" + }, + "raiPolicyName": { + "value": "[variables('aiModelDeployments')[copyIndex()].raiPolicyName]" + }, + "skuName": { + "value": "[variables('aiModelDeployments')[copyIndex()].sku.name]" + }, + "skuCapacity": { + "value": "[variables('aiModelDeployments')[copyIndex()].sku.capacity]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "8590492508744195961" + } + }, + "parameters": { + "aiServicesAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the parent AI Services account." + } + }, + "deploymentName": { + "type": "string", + "metadata": { + "description": "Required. Name for this model deployment." + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "Optional. Model format (e.g., OpenAI)." + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Required. Model name (e.g., gpt-4o, text-embedding-ada-002)." + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Model version. Empty string means latest." + } + }, + "raiPolicyName": { + "type": "string", + "defaultValue": "Microsoft.Default", + "metadata": { + "description": "Optional. RAI policy name." + } + }, + "skuName": { + "type": "string", + "metadata": { + "description": "Required. SKU name (e.g., Standard, GlobalStandard)." + } + }, + "skuCapacity": { + "type": "int", + "metadata": { + "description": "Required. SKU capacity (tokens per minute in thousands)." + } + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-12-01", + "name": "[format('{0}/{1}', parameters('aiServicesAccountName'), parameters('deploymentName'))]", + "properties": { + "model": { + "format": "[parameters('modelFormat')]", + "name": "[parameters('modelName')]", + "version": "[if(not(empty(parameters('modelVersion'))), parameters('modelVersion'), null())]" + }, + "raiPolicyName": "[parameters('raiPolicyName')]" + }, + "sku": { + "name": "[parameters('skuName')]", + "capacity": "[parameters('skuCapacity')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the deployed model." + }, + "value": "[parameters('deploymentName')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the model deployment." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts/deployments', parameters('aiServicesAccountName'), parameters('deploymentName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.storage-account.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "containers": { + "value": [ + { + "name": "data", + "publicAccess": "None" + } + ] + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "14306345095861729821" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name suffix used to derive the resource name." + } + }, + "name": { + "type": "string", + "defaultValue": "[take(format('st{0}', toLower(replace(parameters('solutionName'), '-', ''))), 24)]", + "metadata": { + "description": "Name of the storage account." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the resource." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to apply to the resource." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard_LRS", + "metadata": { + "description": "Storage account SKU." + } + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2", + "metadata": { + "description": "Storage account kind." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "Hot", + "allowedValues": [ + "Hot", + "Cool" + ], + "metadata": { + "description": "Access tier." + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Allow blob public access." + } + }, + "allowSharedKeyAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Allow shared key access." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "containers": { + "type": "array", + "defaultValue": [ + { + "name": "default", + "publicAccess": "None" + } + ], + "metadata": { + "description": "Blob containers to create." + } + }, + "diagnosticSettings": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Diagnostic settings for monitoring." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting." + } + }, + "networkAcls": { + "type": "object", + "defaultValue": { + "defaultAction": "Allow", + "bypass": "AzureServices" + }, + "metadata": { + "description": "Network ACLs for the storage account." + } + }, + "enablePrivateNetworking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to enable private networking." + } + }, + "privateEndpointSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Subnet resource ID for the private endpoint." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Private DNS zone resource IDs for Storage (blob)." + } + }, + "roleAssignments": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of role assignments to create on the Storage Account." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneResourceIds'))]", + "input": { + "name": "[format('dns-zone-{0}', copyIndex('privateDnsZoneConfigs'))]", + "privateDnsZoneResourceId": "[parameters('privateDnsZoneResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + ] + }, + "resources": { + "storage": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.storage.storage-account.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "skuName": { + "value": "[parameters('skuName')]" + }, + "kind": { + "value": "[parameters('kind')]" + }, + "accessTier": { + "value": "[parameters('accessTier')]" + }, + "allowBlobPublicAccess": { + "value": "[parameters('allowBlobPublicAccess')]" + }, + "allowSharedKeyAccess": { + "value": "[parameters('allowSharedKeyAccess')]" + }, + "minimumTlsVersion": { + "value": "TLS1_2" + }, + "supportsHttpsTrafficOnly": { + "value": true + }, + "requireInfrastructureEncryption": { + "value": true + }, + "publicNetworkAccess": { + "value": "[parameters('publicNetworkAccess')]" + }, + "networkAcls": { + "value": "[parameters('networkAcls')]" + }, + "blobServices": { + "value": { + "copy": [ + { + "name": "containers", + "count": "[length(parameters('containers'))]", + "input": { + "name": "[parameters('containers')[copyIndex('containers')].name]", + "publicAccess": "[parameters('containers')[copyIndex('containers')].publicAccess]" + } + } + ], + "diagnosticSettings": "[if(not(empty(parameters('diagnosticSettings'))), parameters('diagnosticSettings'), createArray())]" + } + }, + "diagnosticSettings": "[if(not(empty(parameters('diagnosticSettings'))), createObject('value', parameters('diagnosticSettings')), createObject('value', createArray()))]", + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', parameters('name')), 'customNetworkInterfaceName', format('nic-{0}', parameters('name')), 'subnetResourceId', parameters('privateEndpointSubnetId'), 'service', 'blob', 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', variables('privateDnsZoneConfigs'))))), createObject('value', createArray()))]", + "roleAssignments": "[if(not(empty(parameters('roleAssignments'))), createObject('value', parameters('roleAssignments')), 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.41.2.15936", + "templateHash": "1254456195180100771" + }, + "name": "Storage Accounts", + "description": "This module deploys a Storage Account." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses of the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoints output." + } + }, + "networkAclsType": { + "type": "object", + "properties": { + "resourceAccessRules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "metadata": { + "description": "Required. The ID of the tenant in which the resource resides in." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the target service. Can also contain a wildcard, if multiple services e.g. in a resource group should be included." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Sets the resource access rules. Array entries must consist of \"tenantId\" and \"resourceId\" fields only." + } + }, + "bypass": { + "type": "string", + "allowedValues": [ + "AzureServices", + "AzureServices, Logging", + "AzureServices, Logging, Metrics", + "AzureServices, Metrics", + "Logging", + "Logging, Metrics", + "Metrics", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Possible values are any combination of Logging,Metrics,AzureServices (For example, \"Logging, Metrics\"), or None to bypass none of those traffics." + } + }, + "virtualNetworkRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the virtual network rules." + } + }, + "ipRules": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Optional. Sets the IP ACL rules." + } + }, + "defaultAction": { + "type": "string", + "allowedValues": [ + "Allow", + "Deny" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the default action of allow or deny when no other rules match." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the network configuration." + } + }, + "secretsExportConfigurationType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The key vault name where to store the keys and connection strings generated by the modules." + } + }, + "accessKey1Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The accessKey1 secret name to create." + } + }, + "connectionString1Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The connectionString1 secret name to create." + } + }, + "accessKey2Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The accessKey2 secret name to create." + } + }, + "connectionString2Name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The connectionString2 secret name to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of the exported secrets." + } + }, + "localUserType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the local user used for SFTP Authentication." + } + }, + "hasSharedKey": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key." + } + }, + "hasSshKey": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key." + } + }, + "hasSshPassword": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password." + } + }, + "homeDirectory": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The local user home directory." + } + }, + "permissionScopes": { + "type": "array", + "items": { + "$ref": "#/definitions/permissionScopeType" + }, + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/sshAuthorizedKeyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a local user." + } + }, + "blobServiceType": { + "type": "object", + "properties": { + "automaticSnapshotPolicyEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Automatic Snapshot is enabled if set to true." + } + }, + "changeFeedEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service." + } + }, + "changeFeedRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 146000, + "metadata": { + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed." + } + }, + "containerDeleteRetentionPolicyEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled." + } + }, + "containerDeleteRetentionPolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted item should be retained." + } + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/blobCorsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "defaultServiceVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions." + } + }, + "deleteRetentionPolicyEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The blob service properties for blob soft delete." + } + }, + "deleteRetentionPolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted blob should be retained." + } + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "isVersioningEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Use versioning to automatically maintain previous versions of your blobs. Cannot be enabled for ADLS Gen2 storage accounts." + } + }, + "versionDeletePolicyDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Number of days to keep a version before deleting. If set, a lifecycle management policy will be created to handle deleting previous versions." + } + }, + "lastAccessTimeTrackingPolicyEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled." + } + }, + "restorePolicyEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled." + } + }, + "restorePolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "metadata": { + "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days." + } + }, + "containers": { + "type": "array", + "items": { + "$ref": "#/definitions/containerType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Blob containers to create." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a blob service." + } + }, + "fileServiceType": { + "type": "object", + "properties": { + "protocolSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/protocolSettings" + }, + "description": "Optional. Protocol settings for file service." + }, + "nullable": true + }, + "shareDeleteRetentionPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/shareDeleteRetentionPolicy" + }, + "description": "Optional. The service properties for soft delete." + }, + "nullable": true + }, + "shares": { + "type": "array", + "items": { + "$ref": "#/definitions/fileShareType" + }, + "nullable": true, + "metadata": { + "description": "Optional. File shares to create." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/fileCorsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a file service." + } + }, + "queueServiceType": { + "type": "object", + "properties": { + "queues": { + "type": "array", + "items": { + "$ref": "#/definitions/queueType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Queues to create." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/queueCorsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a queue service." + } + }, + "tableServiceType": { + "type": "object", + "properties": { + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Tables to create." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/tableCorsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a table service." + } + }, + "objectReplicationPolicyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the object replication policy. If not provided, a GUID will be generated." + } + }, + "destinationStorageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the destination storage account." + } + }, + "enableMetrics": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Indicates whether metrics are enabled for the object replication policy." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/objectReplicationPolicyRuleType" + }, + "metadata": { + "description": "Required. The storage account object replication rules." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of an object replication policy." + } + }, + "_1.immutabilityPolicyType": { + "type": "object", + "properties": { + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. Defaults to false." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive. Defaults to false." + } + } + }, + "metadata": { + "description": "The type for an immutability policy.", + "__bicep_imported_from!": { + "sourceTemplate": "blob-service/container/main.bicep" + } + } + }, + "_2.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.privateEndpointIpConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "_2.secretSetOutputType": { + "type": "object", + "properties": { + "secretResourceId": { + "type": "string", + "metadata": { + "description": "The resourceId of the exported secret." + } + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The secret URI of the exported secret." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "blobCorsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "description": "The type for a cors rule.", + "__bicep_imported_from!": { + "sourceTemplate": "blob-service/main.bicep", + "originalIdentifier": "corsRuleType" + } + } + }, + "containerType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Storage Container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Block override of encryption scope from the container default." + } + }, + "enableNfsV3AllSquash": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable NFSv3 all squash on blob container." + } + }, + "enableNfsV3RootSquash": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable NFSv3 root squash on blob container." + } + }, + "immutableStorageWithVersioningEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process." + } + }, + "immutabilityPolicy": { + "$ref": "#/definitions/_1.immutabilityPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. Configure immutability policy." + } + }, + "metadata": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. A name-value pair to associate with the container as metadata." + }, + "nullable": true + }, + "publicAccess": { + "type": "string", + "allowedValues": [ + "Blob", + "Container", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "description": "The type of a storage container.", + "__bicep_imported_from!": { + "sourceTemplate": "blob-service/main.bicep" + } + } + }, + "customerManagedKeyWithAutoRotateType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + }, + "keyVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The version of the customer managed key to reference for encryption. If not provided, using version as per 'autoRotationEnabled' setting." + } + }, + "autoRotationEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable auto-rotating to the latest key version. Default is `true`. If set to `false`, the latest key version at the time of the deployment is used." + } + }, + "userAssignedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User assigned identity to use when fetching the customer managed key. Required if no system assigned identity is available for use." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if the resource type supports auto-rotation of the customer-managed key.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "diagnosticSettingMetricsOnlyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "fileCorsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "description": "The type for a cors rule.", + "__bicep_imported_from!": { + "sourceTemplate": "file-service/main.bicep", + "originalIdentifier": "corsRuleType" + } + } + }, + "fileShareType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share." + } + }, + "accessTier": { + "type": "string", + "allowedValues": [ + "Cool", + "Hot", + "Premium", + "TransactionOptimized" + ], + "nullable": true, + "metadata": { + "description": "Optional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool." + } + }, + "enabledProtocols": { + "type": "string", + "allowedValues": [ + "NFS", + "SMB" + ], + "nullable": true, + "metadata": { + "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share." + } + }, + "rootSquash": { + "type": "string", + "allowedValues": [ + "AllSquash", + "NoRootSquash", + "RootSquash" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares." + } + }, + "shareQuota": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)." + } + }, + "provisionedBandwidthMibps": { + "type": "int", + "nullable": true, + "maxValue": 10340, + "metadata": { + "description": "Optional. The provisioned bandwidth of the share, in mebibytes per second. Only applicable to FileStorage storage accounts (premium file shares). Must be between 0 and 10340." + } + }, + "provisionedIops": { + "type": "int", + "nullable": true, + "maxValue": 102400, + "metadata": { + "description": "Optional. The provisioned IOPS of the share. Only applicable to FileStorage storage accounts (premium file shares). Must be between 0 and 102400." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "description": "The type for a file share.", + "__bicep_imported_from!": { + "sourceTemplate": "file-service/main.bicep" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "managedIdentityAllType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "objectReplicationPolicyRuleType": { + "type": "object", + "properties": { + "ruleId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the source container." + } + }, + "destinationContainerName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used." + } + }, + "filters": { + "type": "object", + "properties": { + "prefixMatch": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The prefix to match for the replication policy rule." + } + }, + "minCreationTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The minimum creation time to match for the replication policy rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The filters for the object replication policy rule." + } + } + }, + "metadata": { + "description": "The type of an object replication policy rule.", + "__bicep_imported_from!": { + "sourceTemplate": "object-replication-policy/policy/main.bicep" + } + } + }, + "permissionScopeType": { + "type": "object", + "properties": { + "permissions": { + "type": "string", + "metadata": { + "description": "Required. The permissions for the local user. Possible values include: Read (r), Write (w), Delete (d), List (l), and Create (c)." + } + }, + "resourceName": { + "type": "string", + "metadata": { + "description": "Required. The name of resource, normally the container name or the file share name, used by the local user." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service used by the local user, e.g. blob, file." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "local-user/main.bicep" + } + } + }, + "privateEndpointMultiServiceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\" for a Storage Account's Private Endpoints." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_2.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/_2.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_2.privateEndpointIpConfigurationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags" + }, + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can NOT be assumed (i.e., for services that have more than one subresource, like Storage Account with Blob (blob, table, queue, file, ...).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "queueCorsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "description": "The type for a cors rule.", + "__bicep_imported_from!": { + "sourceTemplate": "queue-service/main.bicep", + "originalIdentifier": "corsRuleType" + } + } + }, + "queueType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the queue." + } + }, + "metadata": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. Metadata to set on the queue." + }, + "nullable": true + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "description": "The type for a queue.", + "__bicep_imported_from!": { + "sourceTemplate": "queue-service/main.bicep" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "secretsOutputType": { + "type": "object", + "properties": {}, + "additionalProperties": { + "$ref": "#/definitions/_2.secretSetOutputType", + "metadata": { + "description": "An exported secret's references." + } + }, + "metadata": { + "description": "A map of the exported secrets", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "sshAuthorizedKeyType": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "securestring", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "local-user/main.bicep" + } + } + }, + "tableCorsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "description": "The type for a cors rule.", + "__bicep_imported_from!": { + "sourceTemplate": "table-service/main.bicep", + "originalIdentifier": "corsRuleType" + } + } + }, + "tableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "description": "The type for a table.", + "__bicep_imported_from!": { + "sourceTemplate": "table-service/main.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. Name of the Storage Account. Must be lower-case." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "extendedLocationZone": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Extended Zone location (ex 'losangeles'). When supplied, the storage account will be created in the specified zone under the parent location. The extended zone must be available in the supplied parent location." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2", + "allowedValues": [ + "Storage", + "StorageV2", + "BlobStorage", + "FileStorage", + "BlockBlobStorage" + ], + "metadata": { + "description": "Optional. Type of Storage Account to create." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard_GRS", + "allowedValues": [ + "Standard_LRS", + "Standard_ZRS", + "Standard_GRS", + "Standard_GZRS", + "Standard_RAGRS", + "Standard_RAGZRS", + "StandardV2_LRS", + "StandardV2_ZRS", + "StandardV2_GRS", + "StandardV2_GZRS", + "Premium_LRS", + "Premium_ZRS", + "PremiumV2_LRS", + "PremiumV2_ZRS" + ], + "metadata": { + "description": "Optional. Storage Account Sku Name - note: certain V2 SKUs require the use of: kind = FileStorage." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "Hot", + "allowedValues": [ + "Premium", + "Hot", + "Cool", + "Cold" + ], + "metadata": { + "description": "Conditional. Required if the Storage Account kind is set to BlobStorage. The access tier is used for billing. The \"Premium\" access tier is the default value for premium block blobs storage account type and it cannot be changed for the premium block blobs storage account type." + } + }, + "largeFileSharesState": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "metadata": { + "description": "Optional. Allow large file shares if set to 'Enabled'. It cannot be disabled once it is enabled. Only supported on locally redundant and zone redundant file shares. It cannot be set on FileStorage storage accounts (storage accounts for premium file shares)." + } + }, + "azureFilesIdentityBasedAuthentication": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts@2025-01-01#properties/properties/properties/azureFilesIdentityBasedAuthentication" + }, + "description": "Optional. Provides the identity based authentication settings for Azure Files." + }, + "nullable": true + }, + "defaultToOAuthAuthentication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. A boolean flag which indicates whether the default authentication is OAuth or not." + } + }, + "allowSharedKeyAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether the storage account permits requests to be authorized with the account access key via Shared Key. If false, then all requests, including shared access signatures, must be authorized with Azure Active Directory (Azure AD). The default value is null, which is equivalent to true." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointMultiServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is recommended to use private endpoints whenever possible." + } + }, + "managementPolicyRules": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/managementPolicies@2025-01-01#properties/properties/properties/policy/properties/rules" + }, + "description": "Optional. The Storage Account ManagementPolicies Rules." + }, + "nullable": true + }, + "networkAcls": { + "$ref": "#/definitions/networkAclsType", + "nullable": true, + "metadata": { + "description": "Optional. Networks ACLs, this value contains IPs to whitelist and/or Subnet information. If in use, bypass needs to be supplied. For security reasons, it is recommended to set the DefaultAction Deny." + } + }, + "requireInfrastructureEncryption": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. A Boolean indicating whether or not the service applies a secondary layer of encryption with platform managed keys for data at rest. For security reasons, it is recommended to set it to true." + } + }, + "allowCrossTenantReplication": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Allow or disallow cross AAD tenant object replication." + } + }, + "customDomainName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Sets the custom domain name assigned to the storage account. Name is the CNAME source." + } + }, + "customDomainUseSubDomainName": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether indirect CName validation is enabled. This should only be set on updates." + } + }, + "dnsEndpointType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "AzureDnsZone", + "Standard" + ], + "metadata": { + "description": "Optional. Allows you to specify the type of endpoint. Set this to AzureDNSZone to create a large number of accounts in a single subscription, which creates accounts in an Azure DNS Zone and the endpoint URL will have an alphanumeric DNS Zone identifier." + } + }, + "blobServices": { + "$ref": "#/definitions/blobServiceType", + "defaultValue": "[if(not(equals(parameters('kind'), 'FileStorage')), createObject('containerDeleteRetentionPolicyEnabled', true(), 'containerDeleteRetentionPolicyDays', 7, 'deleteRetentionPolicyEnabled', true(), 'deleteRetentionPolicyDays', 6), createObject())]", + "metadata": { + "description": "Optional. Blob service and containers to deploy." + } + }, + "fileServices": { + "$ref": "#/definitions/fileServiceType", + "defaultValue": {}, + "metadata": { + "description": "Optional. File service and shares to deploy." + } + }, + "queueServices": { + "$ref": "#/definitions/queueServiceType", + "defaultValue": {}, + "metadata": { + "description": "Optional. Queue service and queues to create." + } + }, + "tableServices": { + "$ref": "#/definitions/tableServiceType", + "defaultValue": {}, + "metadata": { + "description": "Optional. Table service and tables to create." + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether public access is enabled for all blobs or containers in the storage account. For security reasons, it is recommended to set it to false." + } + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "TLS1_2", + "allowedValues": [ + "TLS1_2", + "TLS1_3" + ], + "metadata": { + "description": "Optional. Set the minimum TLS version on request to storage. The TLS versions 1.0 and 1.1 are deprecated and not supported anymore." + } + }, + "enableHierarchicalNamespace": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Conditional. If true, enables Hierarchical Namespace for the storage account. Required if enableSftp or enableNfsV3 is set to true." + } + }, + "enableSftp": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables Secure File Transfer Protocol for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "localUsers": { + "type": "array", + "items": { + "$ref": "#/definitions/localUserType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Local users to deploy for SFTP authentication." + } + }, + "isLocalUserEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables local users feature, if set to true." + } + }, + "enableNfsV3": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, enables NFS 3.0 support for the storage account. Requires enableHierarchicalNamespace to be true." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingMetricsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts@2025-01-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "allowedCopyScope": { + "type": "string", + "nullable": true, + "allowedValues": [ + "AAD", + "PrivateLink" + ], + "metadata": { + "description": "Optional. Restrict copy to and from Storage Accounts within an AAD tenant or with Private Links to the same VNet." + } + }, + "publicNetworkAccess": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Enabled", + "Disabled", + "SecuredByPerimeter" + ], + "metadata": { + "description": "Optional. Whether or not public network access is allowed for this resource. For security reasons it should be disabled. If not specified, it will be disabled by default if private endpoints are set and networkAcls are not set." + } + }, + "supportsHttpsTrafficOnly": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Allows HTTPS traffic only to storage service if sets to true." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyWithAutoRotateType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition." + } + }, + "sasExpirationPeriod": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The SAS expiration period. DD.HH:MM:SS." + } + }, + "sasExpirationAction": { + "type": "string", + "defaultValue": "Log", + "allowedValues": [ + "Block", + "Log" + ], + "metadata": { + "description": "Optional. The SAS expiration action. Allowed values are Block and Log." + } + }, + "keyType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Account", + "Service" + ], + "metadata": { + "description": "Optional. The keyType to use with Queue & Table services." + } + }, + "secretsExportConfiguration": { + "$ref": "#/definitions/secretsExportConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. Key vault reference and secret settings for the module's secrets export." + } + }, + "immutableStorageWithVersioning": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts@2025-01-01#properties/properties/properties/immutableStorageWithVersioning" + }, + "description": "Optional. The property is immutable and can only be set to true at the account creation time. When set to true, it enables object level immutability for all the new containers in the account by default. Cannot be enabled for ADLS Gen2 storage accounts." + }, + "nullable": true + }, + "objectReplicationPolicies": { + "type": "array", + "items": { + "$ref": "#/definitions/objectReplicationPolicyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Object replication policies for the storage account." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "immutabilityValidation": "[if(and(equals(parameters('enableHierarchicalNamespace'), true()), not(empty(parameters('immutableStorageWithVersioning')))), fail('Configuration error: Immutable storage with versioning cannot be enabled when hierarchical namespace is enabled.'), null())]", + "supportsBlobService": "[or(or(or(equals(parameters('kind'), 'BlockBlobStorage'), equals(parameters('kind'), 'BlobStorage')), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "supportsFileService": "[or(or(equals(parameters('kind'), 'FileStorage'), equals(parameters('kind'), 'StorageV2')), equals(parameters('kind'), 'Storage'))]", + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "Storage File Data Privileged Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '69566ab7-960f-475b-8e7c-b3118f30c6bd')]", + "Storage File Data Privileged Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b8eda974-7b85-4f76-af95-65846b26df6d')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "formattedManagementPolicies": "[union(coalesce(parameters('managementPolicyRules'), createArray()), if(and(and(not(empty(parameters('blobServices'))), coalesce(tryGet(parameters('blobServices'), 'isVersioningEnabled'), false())), not(equals(tryGet(parameters('blobServices'), 'versionDeletePolicyDays'), null()))), createArray(createObject('name', 'DeletePreviousVersions (auto-created)', 'enabled', true(), 'type', 'Lifecycle', 'definition', createObject('actions', createObject('version', createObject('delete', createObject('daysAfterCreationGreaterThan', parameters('blobServices').versionDeletePolicyDays))), 'filters', createObject('blobTypes', createArray('blockBlob', 'appendBlob'))))), createArray()))]", + "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]" + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(and(not(variables('isHSMManagedCMK')), not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId')))), and(not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))), not(empty(tryGet(parameters('customerManagedKey'), 'keyName')))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[format('{0}/{1}', last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')), tryGet(parameters('customerManagedKey'), 'keyName'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.32.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[and(not(variables('isHSMManagedCMK')), not(empty(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2025-05-01", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), '/'))]" + }, + "cMKUserAssignedIdentity": { + "condition": "[not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId')))]", + "existing": true, + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2024-11-30", + "subscriptionId": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))]" + }, + "storageAccount": { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "extendedLocation": "[if(not(empty(parameters('extendedLocationZone'))), createObject('name', parameters('extendedLocationZone'), 'type', 'EdgeZone'), null())]", + "kind": "[parameters('kind')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "identity": "[variables('identity')]", + "tags": "[parameters('tags')]", + "properties": "[shallowMerge(createArray(createObject('allowSharedKeyAccess', parameters('allowSharedKeyAccess'), 'defaultToOAuthAuthentication', parameters('defaultToOAuthAuthentication'), 'allowCrossTenantReplication', parameters('allowCrossTenantReplication'), 'allowedCopyScope', parameters('allowedCopyScope'), 'customDomain', createObject('name', parameters('customDomainName'), 'useSubDomainName', parameters('customDomainUseSubDomainName')), 'dnsEndpointType', parameters('dnsEndpointType'), 'isLocalUserEnabled', parameters('isLocalUserEnabled'), 'encryption', union(createObject('keySource', if(not(empty(parameters('customerManagedKey'))), 'Microsoft.Keyvault', 'Microsoft.Storage'), 'services', createObject('blob', if(variables('supportsBlobService'), createObject('enabled', true()), null()), 'file', if(variables('supportsFileService'), createObject('enabled', true()), null()), 'table', createObject('enabled', true(), 'keyType', parameters('keyType')), 'queue', createObject('enabled', true(), 'keyType', parameters('keyType'))), 'keyvaultproperties', if(not(empty(parameters('customerManagedKey'))), createObject('keyname', parameters('customerManagedKey').keyName, 'keyvaulturi', if(not(variables('isHSMManagedCMK')), reference('cMKKeyVault').vaultUri, format('https://{0}.managedhsm.azure.net/', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')))), 'keyversion', if(not(empty(tryGet(parameters('customerManagedKey'), 'keyVersion'))), parameters('customerManagedKey').keyVersion, if(coalesce(tryGet(parameters('customerManagedKey'), 'autoRotationEnabled'), true()), null(), if(not(variables('isHSMManagedCMK')), last(split(reference('cMKKeyVault::cMKKey').keyUriWithVersion, '/')), fail('Managed HSM CMK encryption requires either specifying the ''keyVersion'' or omitting the ''autoRotationEnabled'' property. Setting ''autoRotationEnabled'' to false without a ''keyVersion'' is not allowed.'))))), null()), 'identity', createObject('userAssignedIdentity', if(not(empty(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'))), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[2], split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', last(split(tryGet(parameters('customerManagedKey'), 'userAssignedIdentityResourceId'), '/'))), null()))), if(parameters('requireInfrastructureEncryption'), createObject('requireInfrastructureEncryption', if(not(equals(parameters('kind'), 'Storage')), parameters('requireInfrastructureEncryption'), null())), createObject())), 'accessTier', if(and(not(equals(parameters('kind'), 'Storage')), not(equals(parameters('kind'), 'BlockBlobStorage'))), parameters('accessTier'), null()), 'sasPolicy', if(not(empty(parameters('sasExpirationPeriod'))), createObject('expirationAction', parameters('sasExpirationAction'), 'sasExpirationPeriod', parameters('sasExpirationPeriod')), null()), 'supportsHttpsTrafficOnly', parameters('supportsHttpsTrafficOnly'), 'isSftpEnabled', parameters('enableSftp'), 'isNfsV3Enabled', if(parameters('enableNfsV3'), parameters('enableNfsV3'), ''), 'largeFileSharesState', if(or(equals(parameters('skuName'), 'Standard_LRS'), equals(parameters('skuName'), 'Standard_ZRS')), parameters('largeFileSharesState'), null()), 'minimumTlsVersion', parameters('minimumTlsVersion'), 'networkAcls', if(not(empty(parameters('networkAcls'))), union(createObject('resourceAccessRules', tryGet(parameters('networkAcls'), 'resourceAccessRules'), 'defaultAction', coalesce(tryGet(parameters('networkAcls'), 'defaultAction'), 'Deny'), 'virtualNetworkRules', tryGet(parameters('networkAcls'), 'virtualNetworkRules'), 'ipRules', tryGet(parameters('networkAcls'), 'ipRules')), if(contains(parameters('networkAcls'), 'bypass'), createObject('bypass', tryGet(parameters('networkAcls'), 'bypass')), createObject())), createObject('bypass', 'AzureServices', 'defaultAction', 'Deny')), 'allowBlobPublicAccess', parameters('allowBlobPublicAccess'), 'publicNetworkAccess', if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(and(not(empty(parameters('privateEndpoints'))), empty(parameters('networkAcls'))), 'Disabled', null()))), if(not(empty(parameters('azureFilesIdentityBasedAuthentication'))), createObject('azureFilesIdentityBasedAuthentication', parameters('azureFilesIdentityBasedAuthentication')), createObject()), if(not(equals(parameters('enableHierarchicalNamespace'), null())), createObject('isHnsEnabled', parameters('enableHierarchicalNamespace')), createObject()), createObject('immutableStorageWithVersioning', parameters('immutableStorageWithVersioning'))))]", + "dependsOn": [ + "cMKKeyVault", + "cMKKeyVault::cMKKey" + ] + }, + "storageAccount_diagnosticSettings": { + "copy": { + "name": "storageAccount_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_roleAssignments": { + "copy": { + "name": "storageAccount_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_privateEndpoints": { + "copy": { + "name": "storageAccount_privateEndpoints", + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-sa-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.Storage/storageAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a private dns zone group." + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "description": "The type of a private DNS zone group configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + }, + "nullable": true + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + }, + "nullable": true + }, + "customDnsConfigs": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, + "description": "Optional. Custom DNS configurations." + }, + "nullable": true + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, + "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." + }, + "nullable": true + }, + "privateLinkServiceConnections": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-10-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.5.1644", + "templateHash": "24141742673128945" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-10-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]" + } + } + } + ] + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_managementPolicies": { + "condition": "[not(empty(coalesce(variables('formattedManagementPolicies'), createArray())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-ManagementPolicies', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "rules": { + "value": "[variables('formattedManagementPolicies')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "4000605059554016072" + }, + "name": "Storage Account Management Policies", + "description": "This module deploys a Storage Account Management Policy." + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "rules": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/managementPolicies@2025-06-01#properties/properties/properties/policy/properties/rules" + }, + "description": "Required. The Storage Account ManagementPolicies Rules." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-mgmtpolicy.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + { + "type": "Microsoft.Storage/storageAccounts/managementPolicies", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "properties": { + "policy": { + "rules": "[parameters('rules')]" + } + } + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed management policy." + }, + "value": "default" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed management policy." + }, + "value": "default" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed management policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount", + "storageAccount_blobServices" + ] + }, + "storageAccount_localUsers": { + "copy": { + "name": "storageAccount_localUsers", + "count": "[length(coalesce(parameters('localUsers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-LocalUsers-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].name]" + }, + "hasSshKey": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].hasSshKey]" + }, + "hasSshPassword": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].hasSshPassword]" + }, + "permissionScopes": { + "value": "[coalesce(parameters('localUsers'), createArray())[copyIndex()].permissionScopes]" + }, + "hasSharedKey": { + "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'hasSharedKey')]" + }, + "homeDirectory": { + "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'homeDirectory')]" + }, + "sshAuthorizedKeys": { + "value": "[tryGet(coalesce(parameters('localUsers'), createArray())[copyIndex()], 'sshAuthorizedKeys')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "1801226901235196767" + }, + "name": "Storage Account Local Users", + "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication." + }, + "definitions": { + "sshAuthorizedKeyType": { + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Description used to store the function/usage of the key." + } + }, + "key": { + "type": "securestring", + "metadata": { + "description": "Required. SSH public key base64 encoded. The format should be: '{keyType} {keyData}', e.g. ssh-rsa AAAABBBB." + } + } + }, + "metadata": { + "__bicep_export!": true + } + }, + "permissionScopeType": { + "type": "object", + "properties": { + "permissions": { + "type": "string", + "metadata": { + "description": "Required. The permissions for the local user. Possible values include: Read (r), Write (w), Delete (d), List (l), and Create (c)." + } + }, + "resourceName": { + "type": "string", + "metadata": { + "description": "Required. The name of resource, normally the container name or the file share name, used by the local user." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The service used by the local user, e.g. blob, file." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the local user used for SFTP Authentication." + } + }, + "hasSharedKey": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether shared key exists. Set it to false to remove existing shared key." + } + }, + "hasSshKey": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH key exists. Set it to false to remove existing SSH key." + } + }, + "hasSshPassword": { + "type": "bool", + "metadata": { + "description": "Required. Indicates whether SSH password exists. Set it to false to remove existing SSH password." + } + }, + "homeDirectory": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The local user home directory." + } + }, + "permissionScopes": { + "type": "array", + "items": { + "$ref": "#/definitions/permissionScopeType" + }, + "metadata": { + "description": "Required. The permission scopes of the local user." + } + }, + "sshAuthorizedKeys": { + "type": "array", + "items": { + "$ref": "#/definitions/sshAuthorizedKeyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The local user SSH authorized keys for SFTP." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-localuser.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('storageAccountName')]" + }, + "localUsers": { + "type": "Microsoft.Storage/storageAccounts/localUsers", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "hasSharedKey": "[parameters('hasSharedKey')]", + "hasSshKey": "[parameters('hasSshKey')]", + "hasSshPassword": "[parameters('hasSshPassword')]", + "homeDirectory": "[parameters('homeDirectory')]", + "permissionScopes": "[parameters('permissionScopes')]", + "sshAuthorizedKeys": "[parameters('sshAuthorizedKeys')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed local user." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed local user." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed local user." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/localUsers', parameters('storageAccountName'), parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_blobServices": { + "condition": "[not(empty(parameters('blobServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-BlobServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "containers": { + "value": "[tryGet(parameters('blobServices'), 'containers')]" + }, + "automaticSnapshotPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'automaticSnapshotPolicyEnabled')]" + }, + "changeFeedEnabled": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedEnabled')]" + }, + "changeFeedRetentionInDays": { + "value": "[tryGet(parameters('blobServices'), 'changeFeedRetentionInDays')]" + }, + "containerDeleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyEnabled')]" + }, + "containerDeleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyDays')]" + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'containerDeleteRetentionPolicyAllowPermanentDelete')]" + }, + "corsRules": { + "value": "[tryGet(parameters('blobServices'), 'corsRules')]" + }, + "defaultServiceVersion": { + "value": "[tryGet(parameters('blobServices'), 'defaultServiceVersion')]" + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyAllowPermanentDelete')]" + }, + "deleteRetentionPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyEnabled')]" + }, + "deleteRetentionPolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'deleteRetentionPolicyDays')]" + }, + "isVersioningEnabled": { + "value": "[tryGet(parameters('blobServices'), 'isVersioningEnabled')]" + }, + "lastAccessTimeTrackingPolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'lastAccessTimeTrackingPolicyEnabled')]" + }, + "restorePolicyEnabled": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyEnabled')]" + }, + "restorePolicyDays": { + "value": "[tryGet(parameters('blobServices'), 'restorePolicyDays')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('blobServices'), 'diagnosticSettings')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "468225492069709453" + }, + "name": "Storage Account blob Services", + "description": "This module deploys a Storage Account Blob Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "containerType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Storage Container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Block override of encryption scope from the container default." + } + }, + "enableNfsV3AllSquash": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable NFSv3 all squash on blob container." + } + }, + "enableNfsV3RootSquash": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable NFSv3 root squash on blob container." + } + }, + "immutableStorageWithVersioningEnabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process." + } + }, + "immutabilityPolicy": { + "$ref": "#/definitions/immutabilityPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. Configure immutability policy." + } + }, + "metadata": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. A name-value pair to associate with the container as metadata." + }, + "nullable": true + }, + "publicAccess": { + "type": "string", + "allowedValues": [ + "Blob", + "Container", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a storage container." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "immutabilityPolicyType": { + "type": "object", + "properties": { + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. Defaults to false." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive. Defaults to false." + } + } + }, + "metadata": { + "description": "The type for an immutability policy.", + "__bicep_imported_from!": { + "sourceTemplate": "container/main.bicep" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "automaticSnapshotPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Automatic Snapshot is enabled if set to true." + } + }, + "changeFeedEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for change feed events. Indicates whether change feed event logging is enabled for the Blob service." + } + }, + "changeFeedRetentionInDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 146000, + "metadata": { + "description": "Optional. Indicates whether change feed event logging is enabled for the Blob service. Indicates the duration of changeFeed retention in days. If left blank, it indicates an infinite retention of the change feed." + } + }, + "containerDeleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for container soft delete. Indicates whether DeleteRetentionPolicy is enabled." + } + }, + "containerDeleteRetentionPolicyDays": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted item should be retained." + } + }, + "containerDeleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "defaultServiceVersion": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Indicates the default version to use for requests to the Blob service if an incoming request's version is not specified. Possible values include version 2008-10-27 and all more recent versions." + } + }, + "deleteRetentionPolicyEnabled": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. The blob service properties for blob soft delete." + } + }, + "deleteRetentionPolicyDays": { + "type": "int", + "defaultValue": 7, + "minValue": 1, + "maxValue": 365, + "metadata": { + "description": "Optional. Indicates the number of days that the deleted blob should be retained." + } + }, + "deleteRetentionPolicyAllowPermanentDelete": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property when set to true allows deletion of the soft deleted blob versions and snapshots. This property cannot be used with blob restore policy. This property only applies to blob service and does not apply to containers or file share." + } + }, + "isVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Use versioning to automatically maintain previous versions of your blobs. Cannot be enabled for ADLS Gen2 storage accounts." + } + }, + "lastAccessTimeTrackingPolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service property to configure last access time based tracking policy. When set to true last access time based tracking is enabled." + } + }, + "restorePolicyEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. The blob service properties for blob restore policy. If point-in-time restore is enabled, then versioning, change feed, and blob soft delete must also be enabled." + } + }, + "restorePolicyDays": { + "type": "int", + "defaultValue": 7, + "minValue": 1, + "metadata": { + "description": "Optional. How long this blob can be restored. It should be less than DeleteRetentionPolicy days." + } + }, + "containers": { + "type": "array", + "items": { + "$ref": "#/definitions/containerType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Blob containers to create." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "enableReferencedModulesTelemetry": false, + "name": "default" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-01-01", + "name": "[parameters('storageAccountName')]" + }, + "blobServices": { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "automaticSnapshotPolicyEnabled": "[parameters('automaticSnapshotPolicyEnabled')]", + "changeFeed": "[if(parameters('changeFeedEnabled'), createObject('enabled', true(), 'retentionInDays', parameters('changeFeedRetentionInDays')), null())]", + "containerDeleteRetentionPolicy": { + "enabled": "[parameters('containerDeleteRetentionPolicyEnabled')]", + "days": "[parameters('containerDeleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(equals(parameters('containerDeleteRetentionPolicyEnabled'), true()), parameters('containerDeleteRetentionPolicyAllowPermanentDelete'), null())]" + }, + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]", + "defaultServiceVersion": "[parameters('defaultServiceVersion')]", + "deleteRetentionPolicy": { + "enabled": "[parameters('deleteRetentionPolicyEnabled')]", + "days": "[parameters('deleteRetentionPolicyDays')]", + "allowPermanentDelete": "[if(and(parameters('deleteRetentionPolicyEnabled'), parameters('deleteRetentionPolicyAllowPermanentDelete')), true(), null())]" + }, + "isVersioningEnabled": "[parameters('isVersioningEnabled')]", + "lastAccessTimeTrackingPolicy": "[if(and(not(equals(reference('storageAccount', '2025-01-01', 'full').kind, 'Storage')), empty(tryGet(reference('storageAccount', '2025-01-01', 'full'), 'extendedLocation'))), createObject('enable', parameters('lastAccessTimeTrackingPolicyEnabled'), 'name', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 'AccessTimeTracking', null()), 'trackingGranularityInDays', if(equals(parameters('lastAccessTimeTrackingPolicyEnabled'), true()), 1, null())), null())]", + "restorePolicy": "[if(parameters('restorePolicyEnabled'), createObject('enabled', true(), 'days', parameters('restorePolicyDays')), null())]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "blobServices_diagnosticSettings": { + "copy": { + "name": "blobServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "blobServices" + ] + }, + "blobServices_container": { + "copy": { + "name": "blobServices_container", + "count": "[length(coalesce(parameters('containers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Container-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "blobServiceName": { + "value": "[variables('name')]" + }, + "name": { + "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" + }, + "defaultEncryptionScope": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultEncryptionScope')]" + }, + "denyEncryptionScopeOverride": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'denyEncryptionScopeOverride')]" + }, + "enableNfsV3AllSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3AllSquash')]" + }, + "enableNfsV3RootSquash": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'enableNfsV3RootSquash')]" + }, + "immutableStorageWithVersioningEnabled": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutableStorageWithVersioningEnabled')]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'metadata')]" + }, + "publicAccess": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'publicAccess')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "immutabilityPolicy": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'immutabilityPolicy')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "273904034769611992" + }, + "name": "Storage Account Blob Containers", + "description": "This module deploys a Storage Account Blob Container." + }, + "definitions": { + "immutabilityPolicyType": { + "type": "object", + "properties": { + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. Defaults to false." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive. Defaults to false." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an immutability policy." + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "blobServiceName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the parent Blob Service. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the Storage Container to deploy." + } + }, + "defaultEncryptionScope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Default the container to use specified encryption scope for all writes." + } + }, + "denyEncryptionScopeOverride": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Block override of encryption scope from the container default." + } + }, + "enableNfsV3AllSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 all squash on blob container." + } + }, + "enableNfsV3RootSquash": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enable NFSv3 root squash on blob container." + } + }, + "immutableStorageWithVersioningEnabled": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This is an immutable property, when set to true it enables object level immutability at the container level. The property is immutable and can only be set to true at the container creation time. Existing containers must undergo a migration process." + } + }, + "immutabilityPolicy": { + "$ref": "#/definitions/immutabilityPolicyType", + "nullable": true, + "metadata": { + "description": "Optional. Configure immutability policy." + } + }, + "metadata": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/blobServices/containers@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. A name-value pair to associate with the container as metadata." + }, + "defaultValue": {} + }, + "publicAccess": { + "type": "string", + "defaultValue": "None", + "allowedValues": [ + "Container", + "Blob", + "None" + ], + "metadata": { + "description": "Optional. Specifies whether data in the container may be accessed publicly and the level of access." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Blob Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", + "Storage Blob Data Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "Storage Blob Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "Storage Blob Delegator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db58b8e5-c6ad-4a2a-8342-4190687cbf4a')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "storageAccount::blobServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('blobServiceName'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-blobcontainer.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-01-01", + "name": "[parameters('storageAccountName')]" + }, + "container": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "properties": { + "defaultEncryptionScope": "[parameters('defaultEncryptionScope')]", + "denyEncryptionScopeOverride": "[parameters('denyEncryptionScopeOverride')]", + "enableNfsV3AllSquash": "[if(equals(parameters('enableNfsV3AllSquash'), true()), parameters('enableNfsV3AllSquash'), null())]", + "enableNfsV3RootSquash": "[if(equals(parameters('enableNfsV3RootSquash'), true()), parameters('enableNfsV3RootSquash'), null())]", + "immutableStorageWithVersioning": "[if(parameters('immutableStorageWithVersioningEnabled'), createObject('enabled', parameters('immutableStorageWithVersioningEnabled')), null())]", + "metadata": "[parameters('metadata')]", + "publicAccess": "[parameters('publicAccess')]" + } + }, + "container_roleAssignments": { + "copy": { + "name": "container_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "container" + ] + }, + "container_immutabilityPolicy": { + "condition": "[not(empty(coalesce(parameters('immutabilityPolicy'), createObject())))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('{0}-ImmutPol', deployment().name), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "containerName": { + "value": "[parameters('name')]" + }, + "immutabilityPeriodSinceCreationInDays": { + "value": "[tryGet(parameters('immutabilityPolicy'), 'immutabilityPeriodSinceCreationInDays')]" + }, + "allowProtectedAppendWrites": { + "value": "[tryGet(parameters('immutabilityPolicy'), 'allowProtectedAppendWrites')]" + }, + "allowProtectedAppendWritesAll": { + "value": "[tryGet(parameters('immutabilityPolicy'), 'allowProtectedAppendWritesAll')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "15304742179563677019" + }, + "name": "Storage Account Blob Container Immutability Policies", + "description": "This module deploys a Storage Account Blob Container Immutability Policy." + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent container to apply the policy to. Required if the template is used in a standalone deployment." + } + }, + "immutabilityPeriodSinceCreationInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "Optional. The immutability period for the blobs in the container since the policy creation, in days." + } + }, + "allowProtectedAppendWrites": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to an append blob while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive. Defaults to false." + } + }, + "allowProtectedAppendWritesAll": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. This property can only be changed for unlocked time-based retention policies. When enabled, new blocks can be written to both \"Append and Block Blobs\" while maintaining immutability protection and compliance. Only new blocks can be added and any existing blocks cannot be modified or deleted. This property cannot be changed with ExtendImmutabilityPolicy API. The \"allowProtectedAppendWrites\" and \"allowProtectedAppendWritesAll\" properties are mutually exclusive. Defaults to false." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-containerimmutpolicy.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]", + "properties": { + "immutabilityPeriodSinceCreationInDays": "[parameters('immutabilityPeriodSinceCreationInDays')]", + "allowProtectedAppendWrites": "[parameters('allowProtectedAppendWrites')]", + "allowProtectedAppendWritesAll": "[parameters('allowProtectedAppendWritesAll')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed immutability policy." + }, + "value": "default" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed immutability policy." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed immutability policy." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "container" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed container." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed container." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed container." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "blobServices" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed blob service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the deployed blob service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_fileServices": { + "condition": "[not(empty(parameters('fileServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-FileServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('fileServices'), 'diagnosticSettings')]" + }, + "protocolSettings": { + "value": "[tryGet(parameters('fileServices'), 'protocolSettings')]" + }, + "shareDeleteRetentionPolicy": { + "value": "[tryGet(parameters('fileServices'), 'shareDeleteRetentionPolicy')]" + }, + "shares": { + "value": "[tryGet(parameters('fileServices'), 'shares')]" + }, + "corsRules": { + "value": "[tryGet(parameters('fileServices'), 'corsRules')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "17583198711200998285" + }, + "name": "Storage Account File Share Services", + "description": "This module deploys a Storage Account File Share Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "fileShareType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share." + } + }, + "accessTier": { + "type": "string", + "allowedValues": [ + "Cool", + "Hot", + "Premium", + "TransactionOptimized" + ], + "nullable": true, + "metadata": { + "description": "Optional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool." + } + }, + "enabledProtocols": { + "type": "string", + "allowedValues": [ + "NFS", + "SMB" + ], + "nullable": true, + "metadata": { + "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share." + } + }, + "rootSquash": { + "type": "string", + "allowedValues": [ + "AllSquash", + "NoRootSquash", + "RootSquash" + ], + "nullable": true, + "metadata": { + "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares." + } + }, + "shareQuota": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)." + } + }, + "provisionedBandwidthMibps": { + "type": "int", + "nullable": true, + "maxValue": 10340, + "metadata": { + "description": "Optional. The provisioned bandwidth of the share, in mebibytes per second. Only applicable to FileStorage storage accounts (premium file shares). Must be between 0 and 10340." + } + }, + "provisionedIops": { + "type": "int", + "nullable": true, + "maxValue": 102400, + "metadata": { + "description": "Optional. The provisioned IOPS of the share. Only applicable to FileStorage storage accounts (premium file shares). Must be between 0 and 102400." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a file share." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the file service." + } + }, + "protocolSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/protocolSettings" + }, + "description": "Optional. Protocol settings for file service." + }, + "defaultValue": {} + }, + "shareDeleteRetentionPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/fileServices@2024-01-01#properties/properties/properties/shareDeleteRetentionPolicy" + }, + "description": "Optional. The service properties for soft delete." + }, + "defaultValue": { + "enabled": true, + "days": 7 + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "shares": { + "type": "array", + "items": { + "$ref": "#/definitions/fileShareType" + }, + "nullable": true, + "metadata": { + "description": "Optional. File shares to create." + } + } + }, + "variables": { + "enableReferencedModulesTelemetry": false + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('storageAccountName')]" + }, + "fileServices": { + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]", + "protocolSettings": "[parameters('protocolSettings')]", + "shareDeleteRetentionPolicy": "[parameters('shareDeleteRetentionPolicy')]" + } + }, + "fileServices_diagnosticSettings": { + "copy": { + "name": "fileServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('storageAccountName'), parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "fileServices" + ] + }, + "fileServices_shares": { + "copy": { + "name": "fileServices_shares", + "count": "[length(coalesce(parameters('shares'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-FileShare-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "fileServicesName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" + }, + "accessTier": { + "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2025-06-01', 'full').kind, 'FileStorage'), if(startsWith(reference('storageAccount', '2025-06-01', 'full').sku.name, 'PremiumV2_'), null(), 'Premium'), 'TransactionOptimized'))]" + }, + "enabledProtocols": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'enabledProtocols')]" + }, + "rootSquash": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'rootSquash')]" + }, + "shareQuota": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'shareQuota')]" + }, + "provisionedBandwidthMibps": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'provisionedBandwidthMibps')]" + }, + "provisionedIops": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'provisionedIops')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "10353179772982843397" + }, + "name": "Storage Account File Shares", + "description": "This module deploys a Storage Account File Share." + }, + "definitions": { + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "fileServicesName": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Conditional. The name of the parent file service. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share to create." + } + }, + "accessTier": { + "type": "string", + "nullable": true, + "allowedValues": [ + "Premium", + "Hot", + "Cool", + "TransactionOptimized" + ], + "metadata": { + "description": "Conditional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized, Hot, and Cool." + } + }, + "shareQuota": { + "type": "int", + "defaultValue": 5120, + "metadata": { + "description": "Optional. The maximum size of the share, in gigabytes. Must be greater than 0, and less than or equal to 5120 (5TB). For Large File Shares, the maximum size is 102400 (100TB)." + } + }, + "enabledProtocols": { + "type": "string", + "defaultValue": "SMB", + "allowedValues": [ + "NFS", + "SMB" + ], + "metadata": { + "description": "Optional. The authentication protocol that is used for the file share. Can only be specified when creating a share." + } + }, + "rootSquash": { + "type": "string", + "defaultValue": "NoRootSquash", + "allowedValues": [ + "AllSquash", + "NoRootSquash", + "RootSquash" + ], + "metadata": { + "description": "Optional. Permissions for NFS file shares are enforced by the client OS rather than the Azure Files service. Toggling the root squash behavior reduces the rights of the root user for NFS shares." + } + }, + "provisionedBandwidthMibps": { + "type": "int", + "nullable": true, + "minValue": 0, + "maxValue": 10340, + "metadata": { + "description": "Optional. The provisioned bandwidth of the share, in mebibytes per second. Only applicable to FileStorage storage accounts (premium file shares). Must be between 0 and 10340." + } + }, + "provisionedIops": { + "type": "int", + "nullable": true, + "minValue": 0, + "maxValue": 102400, + "metadata": { + "description": "Optional. The provisioned IOPS of the share. Only applicable to FileStorage storage accounts (premium file shares). Must be between 0 and 102400." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage File Data SMB Share Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb')]", + "Storage File Data SMB Share Elevated Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a7264617-510b-434b-a828-9731dc254ea7')]", + "Storage File Data SMB Share Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'aba4ae5f-2193-4029-9191-0cb91df5e314')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::fileService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/fileServices", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('fileServicesName'))]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-fileshare.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-01-01", + "name": "[parameters('storageAccountName')]" + }, + "fileShare": { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]", + "properties": { + "accessTier": "[parameters('accessTier')]", + "shareQuota": "[parameters('shareQuota')]", + "rootSquash": "[if(equals(parameters('enabledProtocols'), 'NFS'), parameters('rootSquash'), null())]", + "enabledProtocols": "[parameters('enabledProtocols')]", + "provisionedBandwidthMibps": "[if(equals(reference('storageAccount', '2025-01-01', 'full').kind, 'FileStorage'), parameters('provisionedBandwidthMibps'), null())]", + "provisionedIops": "[if(equals(reference('storageAccount', '2025-01-01', 'full').kind, 'FileStorage'), parameters('provisionedIops'), null())]" + }, + "dependsOn": [ + "storageAccount" + ] + }, + "fileShare_roleAssignments": { + "copy": { + "name": "fileShare_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Share-Rbac-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "scope": { + "value": "[replace(resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name')), '/shares/', '/fileshares/')]" + }, + "name": { + "value": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]" + }, + "roleDefinitionId": { + "value": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]" + }, + "principalId": { + "value": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "principalType": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]" + }, + "condition": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]" + }, + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), createObject('value', coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0')), createObject('value', null()))]", + "delegatedManagedIdentityResourceId": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "description": { + "value": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "scope": { + "type": "string", + "metadata": { + "description": "Required. The scope to deploy the role assignment to." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The role definition Id to assign." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User", + "" + ], + "defaultValue": "", + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"" + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "defaultValue": "2.0", + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[parameters('scope')]", + "name": "[parameters('name')]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "principalId": "[parameters('principalId')]", + "description": "[parameters('description')]", + "principalType": "[if(not(empty(parameters('principalType'))), parameters('principalType'), null())]", + "condition": "[if(not(empty(parameters('condition'))), parameters('condition'), null())]", + "conditionVersion": "[if(and(not(empty(parameters('conditionVersion'))), not(empty(parameters('condition')))), parameters('conditionVersion'), null())]", + "delegatedManagedIdentityResourceId": "[if(not(empty(parameters('delegatedManagedIdentityResourceId'))), parameters('delegatedManagedIdentityResourceId'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "fileShare" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', parameters('storageAccountName'), parameters('fileServicesName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "fileServices", + "storageAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed file share service." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed file share service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/fileServices', parameters('storageAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed file share service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_queueServices": { + "condition": "[not(empty(parameters('queueServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-QueueServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('queueServices'), 'diagnosticSettings')]" + }, + "queues": { + "value": "[tryGet(parameters('queueServices'), 'queues')]" + }, + "corsRules": { + "value": "[tryGet(parameters('queueServices'), 'corsRules')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "9644461291744477521" + }, + "name": "Storage Account Queue Services", + "description": "This module deploys a Storage Account Queue Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "queueType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the queue." + } + }, + "metadata": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. Metadata to set on the queue." + }, + "nullable": true + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a queue." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "queues": { + "type": "array", + "items": { + "$ref": "#/definitions/queueType" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Queues to create." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default", + "enableReferencedModulesTelemetry": false + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('storageAccountName')]" + }, + "queueServices": { + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]" + } + }, + "queueServices_diagnosticSettings": { + "copy": { + "name": "queueServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "queueServices" + ] + }, + "queueServices_queues": { + "copy": { + "name": "queueServices_queues", + "count": "[length(coalesce(parameters('queues'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Queue-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "name": { + "value": "[coalesce(parameters('queues'), createArray())[copyIndex()].name]" + }, + "metadata": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'metadata')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('queues'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "12812824360066955039" + }, + "name": "Storage Account Queues", + "description": "This module deploys a Storage Account Queue." + }, + "definitions": { + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the storage queue to deploy." + } + }, + "metadata": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/queueServices/queues@2024-01-01#properties/properties/properties/metadata" + }, + "description": "Optional. A name-value pair that represents queue metadata." + }, + "defaultValue": {} + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Queue Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '974c5e8b-45b9-4653-ba55-5f855dd0fb88')]", + "Storage Queue Data Message Processor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8a0f0c08-91a1-4084-bc3d-661d67233fed')]", + "Storage Queue Data Message Sender": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a')]", + "Storage Queue Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '19e7f393-937e-4f77-808e-94535e297925')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::queueServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/queueServices", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-queue.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('storageAccountName')]" + }, + "queue": { + "type": "Microsoft.Storage/storageAccounts/queueServices/queues", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "properties": { + "metadata": "[parameters('metadata')]" + } + }, + "queue_roleAssignments": { + "copy": { + "name": "queue_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "queue" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed queue." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed queue." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices/queues', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed queue." + }, + "value": "[resourceGroup().name]" + } + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed queue service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed queue service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/queueServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed queue service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_tableServices": { + "condition": "[not(empty(parameters('tableServices')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-TableServices', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "diagnosticSettings": { + "value": "[tryGet(parameters('tableServices'), 'diagnosticSettings')]" + }, + "tables": { + "value": "[tryGet(parameters('tableServices'), 'tables')]" + }, + "corsRules": { + "value": "[tryGet(parameters('tableServices'), 'corsRules')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "10320403358700650147" + }, + "name": "Storage Account Table Services", + "description": "This module deploys a Storage Account Table Service." + }, + "definitions": { + "corsRuleType": { + "type": "object", + "properties": { + "allowedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of headers allowed to be part of the cross-origin request." + } + }, + "allowedMethods": { + "type": "array", + "allowedValues": [ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "MERGE", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE" + ], + "metadata": { + "description": "Required. A list of HTTP methods that are allowed to be executed by the origin." + } + }, + "allowedOrigins": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of origin domains that will be allowed via CORS, or \"*\" to allow all domains." + } + }, + "exposedHeaders": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of response headers to expose to CORS clients." + } + }, + "maxAgeInSeconds": { + "type": "int", + "metadata": { + "description": "Required. The number of seconds that the client/browser should cache a preflight response." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a cors rule." + } + }, + "tableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the table." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a table." + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Tables to create." + } + }, + "corsRules": { + "type": "array", + "items": { + "$ref": "#/definitions/corsRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The List of CORS rules. You can include up to five CorsRule elements in the request." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "name": "default", + "enableReferencedModulesTelemetry": false + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('storageAccountName')]" + }, + "tableServices": { + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), variables('name'))]", + "properties": { + "cors": "[if(not(equals(parameters('corsRules'), null())), createObject('corsRules', parameters('corsRules')), null())]" + } + }, + "tableServices_diagnosticSettings": { + "copy": { + "name": "tableServices_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccountName'), variables('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', variables('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "tableServices" + ] + }, + "tableServices_tables": { + "copy": { + "name": "tableServices_tables", + "count": "[length(coalesce(parameters('tables'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Table-{1}', deployment().name, copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "11362260974696477885" + }, + "name": "Storage Account Table", + "description": "This module deploys a Storage Account Table." + }, + "definitions": { + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Conditional. The name of the parent Storage Account. Required if the template is used in a standalone deployment." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Reader and Data Access": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'c12c1c16-33a1-487b-954d-41c89c60f349')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "Storage Account Backup Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e5e2a7ff-d759-4cd2-bb51-3152d37e2eb1')]", + "Storage Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '17d1049b-9a84-46fb-8f53-869881c3d3ab')]", + "Storage Account Key Operator Service Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '81a9662b-bebf-436f-a333-f67b29880f12')]", + "Storage Table Data Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", + "Storage Table Data Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '76199698-9eea-4c19-bc75-cec21354c6b6')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + } + }, + "resources": { + "storageAccount::tableServices": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/tableServices", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.storage-table.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-06-01", + "name": "[parameters('storageAccountName')]" + }, + "table": { + "type": "Microsoft.Storage/storageAccounts/tableServices/tables", + "apiVersion": "2025-06-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "table_roleAssignments": { + "copy": { + "name": "table_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "table" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed table." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices/tables', parameters('storageAccountName'), 'default', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed table." + }, + "value": "[resourceGroup().name]" + } + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed table service." + }, + "value": "[variables('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed table service." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccountName'), variables('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed table service." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "secretsExport": { + "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", + "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" + }, + "secretsToSet": { + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'accessKey1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey1Name'), 'value', listKeys('storageAccount', '2025-06-01').keys[0].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString1Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-06-01').keys[0].value, environment().suffixes.storage))), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('storageAccount', '2025-06-01').keys[1].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString2Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-06-01').keys[1].value, environment().suffixes.storage))), createArray()))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "13227497656004178962" + } + }, + "definitions": { + "secretSetOutputType": { + "type": "object", + "properties": { + "secretResourceId": { + "type": "string", + "metadata": { + "description": "The resourceId of the exported secret." + } + }, + "secretUri": { + "type": "string", + "metadata": { + "description": "The secret URI of the exported secret." + } + }, + "secretUriWithVersion": { + "type": "string", + "metadata": { + "description": "The secret URI with version of the exported secret." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the output of the secret set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "secretToSetType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the secret to set." + } + }, + "value": { + "type": "securestring", + "metadata": { + "description": "Required. The value of the secret to set." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for the secret to set via the secrets export feature.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + } + }, + "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": "2024-11-01", + "name": "[parameters('keyVaultName')]" + }, + "secrets": { + "copy": { + "name": "secrets", + "count": "[length(parameters('secretsToSet'))]" + }, + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2024-11-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretsToSet')[copyIndex()].name)]", + "properties": { + "value": "[parameters('secretsToSet')[copyIndex()].value]" + } + } + }, + "outputs": { + "secretsSet": { + "type": "array", + "items": { + "$ref": "#/definitions/secretSetOutputType" + }, + "metadata": { + "description": "The references to the secrets exported to the provided Key Vault." + }, + "copy": { + "count": "[length(range(0, length(coalesce(parameters('secretsToSet'), createArray()))))]", + "input": { + "secretResourceId": "[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretsToSet')[range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()]].name)]", + "secretUri": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUri]", + "secretUriWithVersion": "[reference(format('secrets[{0}]', range(0, length(coalesce(parameters('secretsToSet'), createArray())))[copyIndex()])).secretUriWithVersion]" + } + } + } + } + } + }, + "dependsOn": [ + "storageAccount" + ] + }, + "storageAccount_objectReplicationPolicies": { + "copy": { + "name": "storageAccount_objectReplicationPolicies", + "count": "[length(coalesce(parameters('objectReplicationPolicies'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Storage-ObjRepPolicy-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('name')]" + }, + "destinationAccountResourceId": { + "value": "[coalesce(parameters('objectReplicationPolicies'), createArray())[copyIndex()].destinationStorageAccountResourceId]" + }, + "enableMetrics": { + "value": "[coalesce(tryGet(coalesce(parameters('objectReplicationPolicies'), createArray())[copyIndex()], 'enableMetrics'), false())]" + }, + "rules": { + "value": "[tryGet(coalesce(parameters('objectReplicationPolicies'), createArray())[copyIndex()], 'rules')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "1894366578172550759" + }, + "name": "Storage Account Object Replication Policy", + "description": "This module deploys a Storage Account Object Replication Policy for both the source account and destination account." + }, + "definitions": { + "objectReplicationPolicyRuleType": { + "type": "object", + "properties": { + "ruleId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the source container." + } + }, + "destinationContainerName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used." + } + }, + "filters": { + "type": "object", + "properties": { + "prefixMatch": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The prefix to match for the replication policy rule." + } + }, + "minCreationTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The minimum creation time to match for the replication policy rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The filters for the object replication policy rule." + } + } + }, + "metadata": { + "description": "The type of an object replication policy rule.", + "__bicep_imported_from!": { + "sourceTemplate": "policy/main.bicep" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the policy." + } + }, + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. The name of the parent Storage Account." + } + }, + "destinationAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the destination storage account for replication." + } + }, + "enableMetrics": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether metrics are enabled for the object replication policy." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/objectReplicationPolicyRuleType" + }, + "metadata": { + "description": "Required. Rules for the object replication policy." + } + } + }, + "variables": { + "destAccountResourceIdParts": "[split(parameters('destinationAccountResourceId'), '/')]", + "destAccountName": "[if(not(empty(variables('destAccountResourceIdParts'))), last(variables('destAccountResourceIdParts')), parameters('destinationAccountResourceId'))]", + "destAccountSubscription": "[if(greater(length(variables('destAccountResourceIdParts')), 2), variables('destAccountResourceIdParts')[2], subscription().subscriptionId)]", + "destAccountResourceGroupName": "[if(greater(length(variables('destAccountResourceIdParts')), 4), variables('destAccountResourceIdParts')[4], resourceGroup().name)]" + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-01-01", + "name": "[parameters('storageAccountName')]" + }, + "destinationPolicy": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('{0}-ObjRep-Policy-dest-{1}', deployment().name, variables('destAccountName')), 64)]", + "subscriptionId": "[variables('destAccountSubscription')]", + "resourceGroup": "[variables('destAccountResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('name'), 'default')]" + }, + "storageAccountName": { + "value": "[variables('destAccountName')]" + }, + "sourceStorageAccountResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "destinationAccountResourceId": { + "value": "[parameters('destinationAccountResourceId')]" + }, + "enableMetrics": { + "value": "[parameters('enableMetrics')]" + }, + "rules": { + "value": "[parameters('rules')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "14995722372031126283" + }, + "name": "Storage Account Object Replication Policy", + "description": "This module deploys a Storage Account Object Replication Policy for a provided storage account." + }, + "definitions": { + "objectReplicationPolicyRuleType": { + "type": "object", + "properties": { + "ruleId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the source container." + } + }, + "destinationContainerName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used." + } + }, + "filters": { + "type": "object", + "properties": { + "prefixMatch": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The prefix to match for the replication policy rule." + } + }, + "minCreationTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The minimum creation time to match for the replication policy rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The filters for the object replication policy rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of an object replication policy rule." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the policy." + } + }, + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. The name of the Storage Account on which to create the policy." + } + }, + "sourceStorageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the source storage account for replication." + } + }, + "destinationAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the destination storage account for replication." + } + }, + "enableMetrics": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether metrics are enabled for the object replication policy." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/objectReplicationPolicyRuleType" + }, + "metadata": { + "description": "Required. Rules for the object replication policy." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-01-01", + "name": "[parameters('storageAccountName')]" + }, + "objectReplicationPolicy": { + "type": "Microsoft.Storage/storageAccounts/objectReplicationPolicies", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "rules", + "count": "[length(parameters('rules'))]", + "input": { + "ruleId": "[tryGet(parameters('rules')[copyIndex('rules')], 'ruleId')]", + "sourceContainer": "[parameters('rules')[copyIndex('rules')].containerName]", + "destinationContainer": "[coalesce(tryGet(parameters('rules')[copyIndex('rules')], 'destinationContainerName'), parameters('rules')[copyIndex('rules')].containerName)]", + "filters": "[if(not(equals(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), null())), createObject('prefixMatch', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'prefixMatch'), 'minCreationTime', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'minCreationTime')), null())]" + } + } + ], + "destinationAccount": "[parameters('destinationAccountResourceId')]", + "metrics": { + "enabled": "[coalesce(parameters('enableMetrics'), false())]" + }, + "sourceAccount": "[parameters('sourceStorageAccountResourceId')]" + } + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource group name of the provisioned resources." + }, + "value": "[resourceGroup().name]" + }, + "objectReplicationPolicyId": { + "type": "string", + "metadata": { + "description": "Resource ID of the created Object Replication Policy." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/objectReplicationPolicies', parameters('storageAccountName'), parameters('name'))]" + }, + "policyId": { + "type": "string", + "metadata": { + "description": "Policy ID of the created Object Replication Policy." + }, + "value": "[reference('objectReplicationPolicy').policyId]" + }, + "rules": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/objectReplicationPolicies@2025-01-01#properties/properties/properties/rules", + "output": true + }, + "description": "Rules created Object Replication Policy." + }, + "value": "[reference('objectReplicationPolicy').rules]" + } + } + } + } + }, + "sourcePolicy": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('{0}-ObjRep-Policy-source-{1}', deployment().name, parameters('storageAccountName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[reference('destinationPolicy').outputs.policyId.value]" + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "sourceStorageAccountResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "destinationAccountResourceId": { + "value": "[parameters('destinationAccountResourceId')]" + }, + "enableMetrics": { + "value": "[parameters('enableMetrics')]" + }, + "rules": { + "copy": [ + { + "name": "value", + "count": "[length(parameters('rules'))]", + "input": "[union(parameters('rules')[copyIndex('value')], createObject('ruleId', reference('destinationPolicy').outputs.rules.value[copyIndex('value')].ruleId))]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.41.2.15936", + "templateHash": "14995722372031126283" + }, + "name": "Storage Account Object Replication Policy", + "description": "This module deploys a Storage Account Object Replication Policy for a provided storage account." + }, + "definitions": { + "objectReplicationPolicyRuleType": { + "type": "object", + "properties": { + "ruleId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The ID of the rule. Auto-generated on destination account. Required for source account." + } + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the source container." + } + }, + "destinationContainerName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the destination container. If not provided, the same name as the source container will be used." + } + }, + "filters": { + "type": "object", + "properties": { + "prefixMatch": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The prefix to match for the replication policy rule." + } + }, + "minCreationTime": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The minimum creation time to match for the replication policy rule." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The filters for the object replication policy rule." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of an object replication policy rule." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the policy." + } + }, + "storageAccountName": { + "type": "string", + "maxLength": 24, + "metadata": { + "description": "Required. The name of the Storage Account on which to create the policy." + } + }, + "sourceStorageAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the source storage account for replication." + } + }, + "destinationAccountResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the destination storage account for replication." + } + }, + "enableMetrics": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Whether metrics are enabled for the object replication policy." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/objectReplicationPolicyRuleType" + }, + "metadata": { + "description": "Required. Rules for the object replication policy." + } + } + }, + "resources": { + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-01-01", + "name": "[parameters('storageAccountName')]" + }, + "objectReplicationPolicy": { + "type": "Microsoft.Storage/storageAccounts/objectReplicationPolicies", + "apiVersion": "2025-01-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "rules", + "count": "[length(parameters('rules'))]", + "input": { + "ruleId": "[tryGet(parameters('rules')[copyIndex('rules')], 'ruleId')]", + "sourceContainer": "[parameters('rules')[copyIndex('rules')].containerName]", + "destinationContainer": "[coalesce(tryGet(parameters('rules')[copyIndex('rules')], 'destinationContainerName'), parameters('rules')[copyIndex('rules')].containerName)]", + "filters": "[if(not(equals(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), null())), createObject('prefixMatch', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'prefixMatch'), 'minCreationTime', tryGet(tryGet(parameters('rules')[copyIndex('rules')], 'filters'), 'minCreationTime')), null())]" + } + } + ], + "destinationAccount": "[parameters('destinationAccountResourceId')]", + "metrics": { + "enabled": "[coalesce(parameters('enableMetrics'), false())]" + }, + "sourceAccount": "[parameters('sourceStorageAccountResourceId')]" + } + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource group name of the provisioned resources." + }, + "value": "[resourceGroup().name]" + }, + "objectReplicationPolicyId": { + "type": "string", + "metadata": { + "description": "Resource ID of the created Object Replication Policy." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts/objectReplicationPolicies', parameters('storageAccountName'), parameters('name'))]" + }, + "policyId": { + "type": "string", + "metadata": { + "description": "Policy ID of the created Object Replication Policy." + }, + "value": "[reference('objectReplicationPolicy').policyId]" + }, + "rules": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Storage/storageAccounts/objectReplicationPolicies@2025-01-01#properties/properties/properties/rules", + "output": true + }, + "description": "Rules created Object Replication Policy." + }, + "value": "[reference('objectReplicationPolicy').rules]" + } + } + } + }, + "dependsOn": [ + "destinationPolicy" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "Resource group name of the provisioned resources." + }, + "value": "[resourceGroup().name]" + }, + "objectReplicationPolicyId": { + "type": "string", + "metadata": { + "description": "Resource ID of the created Object Replication Policy in the source account." + }, + "value": "[reference('sourcePolicy').outputs.objectReplicationPolicyId.value]" + }, + "policyId": { + "type": "string", + "metadata": { + "description": "Policy ID of the created Object Replication Policy in the source account." + }, + "value": "[reference('sourcePolicy').outputs.policyId.value]" + } + } + } + }, + "dependsOn": [ + "storageAccount", + "storageAccount_blobServices" + ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed storage account." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed storage account." + }, + "value": "[parameters('name')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group of the deployed storage account." + }, + "value": "[resourceGroup().name]" + }, + "primaryBlobEndpoint": { + "type": "string", + "metadata": { + "description": "The primary blob endpoint reference if blob services are deployed." + }, + "value": "[if(and(not(empty(parameters('blobServices'))), contains(parameters('blobServices'), 'containers')), reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('name')), '2019-04-01').primaryEndpoints.blob, '')]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('storageAccount', '2025-06-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('storageAccount', '2025-06-01', 'full').location]" + }, + "serviceEndpoints": { + "type": "object", + "metadata": { + "description": "All service endpoints of the deployed storage account, Note Standard_LRS and Standard_ZRS accounts only have a blob service endpoint." + }, + "value": "[reference('storageAccount').primaryEndpoints]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the Storage Account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('storageAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "exportedSecrets": { + "$ref": "#/definitions/secretsOutputType", + "metadata": { + "description": "A hashtable of references to the secrets exported to the provided Key Vault. The key of each reference is each secret's name." + }, + "value": "[if(not(equals(parameters('secretsExportConfiguration'), null())), toObject(reference('secretsExport').outputs.secretsSet.value, lambda('secret', last(split(lambdaVariables('secret').secretResourceId, '/'))), lambda('secret', lambdaVariables('secret'))), createObject())]" + }, + "primaryAccessKey": { + "type": "securestring", + "metadata": { + "description": "The primary access key of the storage account." + }, + "value": "[listKeys('storageAccount', '2025-06-01').keys[0].value]" + }, + "secondaryAccessKey": { + "type": "securestring", + "metadata": { + "description": "The secondary access key of the storage account." + }, + "value": "[listKeys('storageAccount', '2025-06-01').keys[1].value]" + }, + "primaryConnectionString": { + "type": "securestring", + "metadata": { + "description": "The primary connection string of the storage account." + }, + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-06-01').keys[0].value, environment().suffixes.storage)]" + }, + "secondaryConnectionString": { + "type": "securestring", + "metadata": { + "description": "The secondary connection string of the storage account." + }, + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-06-01').keys[1].value, environment().suffixes.storage)]" + } + } + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Storage Account." + }, + "value": "[reference('storage').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the Storage Account." + }, + "value": "[reference('storage').outputs.name.value]" + }, + "blobEndpoint": { + "type": "string", + "metadata": { + "description": "Primary blob endpoint." + }, + "value": "[reference('storage').outputs.primaryBlobEndpoint.value]" + }, + "serviceEndpoints": { + "type": "object", + "metadata": { + "description": "Service endpoints." + }, + "value": "[reference('storage').outputs.serviceEndpoints.value]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "name": { + "value": "[format('cosmos-{0}', variables('solutionSuffix'))]" + }, + "location": { + "value": "[parameters('cosmosLocation')]" + }, + "databaseName": { + "value": "[variables('cosmosDatabaseName')]" + }, + "containers": { + "value": "[variables('cosmosContainers')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "9603065237480031972" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name suffix used to derive the resource name." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('cosmos-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Name of the Cosmos DB account." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the resource." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to apply to the resource." + } + }, + "databaseName": { + "type": "string", + "defaultValue": "db_conversation_history", + "metadata": { + "description": "Database name." + } + }, + "containers": { + "type": "array", + "defaultValue": [ + { + "name": "conversations", + "partitionKeyPath": "/userId" + } + ], + "metadata": { + "description": "Container definitions." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "diagnosticSettings": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Diagnostic settings for monitoring." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Public network access setting." + } + }, + "enablePrivateNetworking": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to enable private networking." + } + }, + "privateEndpointSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Subnet resource ID for the private endpoint." + } + }, + "privateDnsZoneResourceIds": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Private DNS zone resource IDs for Cosmos DB." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable zone redundancy." + } + }, + "enableAutomaticFailover": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable automatic failover." + } + }, + "haLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. HA paired region for multi-region failover when redundancy is enabled." + } + } + }, + "variables": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneResourceIds'))]", + "input": { + "name": "[format('dns-zone-{0}', copyIndex('privateDnsZoneConfigs'))]", + "privateDnsZoneResourceId": "[parameters('privateDnsZoneResourceIds')[copyIndex('privateDnsZoneConfigs')]]" + } + } + ] + }, + "resources": { + "cosmosAccount": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.document-db.database-account.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "capabilitiesToAdd": "[if(parameters('zoneRedundant'), createObject('value', createArray()), createObject('value', createArray('EnableServerless')))]", + "sqlDatabases": { + "value": [ + { + "copy": [ + { + "name": "containers", + "count": "[length(parameters('containers'))]", + "input": { + "name": "[parameters('containers')[copyIndex('containers')].name]", + "paths": [ + "[parameters('containers')[copyIndex('containers')].partitionKeyPath]" + ], + "kind": "Hash", + "version": 2 + } + } + ], + "name": "[parameters('databaseName')]" + } + ] + }, + "sqlRoleAssignments": { + "value": [] + }, + "diagnosticSettings": "[if(not(empty(parameters('diagnosticSettings'))), createObject('value', parameters('diagnosticSettings')), createObject('value', createArray()))]", + "networkRestrictions": { + "value": { + "networkAclBypass": "None", + "publicNetworkAccess": "[parameters('publicNetworkAccess')]" + } + }, + "privateEndpoints": "[if(parameters('enablePrivateNetworking'), createObject('value', createArray(createObject('name', format('pep-{0}', parameters('name')), 'customNetworkInterfaceName', format('nic-{0}', parameters('name')), 'subnetResourceId', parameters('privateEndpointSubnetId'), 'service', 'Sql', 'privateDnsZoneGroup', createObject('privateDnsZoneGroupConfigs', variables('privateDnsZoneConfigs'))))), createObject('value', createArray()))]", + "zoneRedundant": { + "value": "[parameters('zoneRedundant')]" + }, + "enableAutomaticFailover": { + "value": "[parameters('enableAutomaticFailover')]" + }, + "failoverLocations": "[if(parameters('zoneRedundant'), createObject('value', createArray(createObject('failoverPriority', 0, 'isZoneRedundant', true(), 'locationName', parameters('location')), createObject('failoverPriority', 1, 'isZoneRedundant', true(), 'locationName', parameters('haLocation')))), createObject('value', createArray(createObject('locationName', parameters('location'), 'failoverPriority', 0, 'isZoneRedundant', false()))))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "1772014800591596213" + }, + "name": "Azure Cosmos DB account", + "description": "This module deploys an Azure Cosmos DB account. The API used for the account is determined by the child resources that are deployed." + }, + "definitions": { + "privateEndpointOutputType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + } + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group ID for the private endpoint group." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "fully-qualified domain name (FQDN) that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "A list of private IP addresses for the private endpoint." + } + } + } + }, + "metadata": { + "description": "The custom DNS configurations of the private endpoint." + } + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The IDs of the network interfaces associated with the private endpoint." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the private endpoint output." + } + }, + "failoverLocationType": { + "type": "object", + "properties": { + "failoverPriority": { + "type": "int", + "metadata": { + "description": "Required. The failover priority of the region. A failover priority of 0 indicates a write region. The maximum value for a failover priority = (total number of regions - 1). Failover priority values must be unique for each of the regions in which the database account exists." + } + }, + "isZoneRedundant": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Flag to indicate whether or not this region is an AvailabilityZone region. Defaults to true." + } + }, + "locationName": { + "type": "string", + "metadata": { + "description": "Required. The name of the region." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the failover location." + } + }, + "sqlRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the Azure Cosmos DB for NoSQL native role-based access control definition." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated Microsoft Entra ID principal to which access is being granted through this role-based access control assignment. The tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for NoSQL native role-based access control assignment." + } + }, + "sqlRoleDefinitionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the role-based access control definition." + } + }, + "roleName": { + "type": "string", + "metadata": { + "description": "Required. A user-friendly name for the role-based access control definition. This must be unique within the database account." + } + }, + "dataActions": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. An array of data actions that are allowed." + } + }, + "assignableScopes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A set of fully-qualified scopes at or below which role-based access control assignments may be created using this definition. This setting allows application of this definition on the entire account or any underlying resource. This setting must have at least one element. Scopes higher than the account level are not enforceable as assignable scopes. Resources referenced in assignable scopes do not need to exist at creation. Defaults to the current account scope." + } + }, + "assignments": { + "type": "array", + "items": { + "$ref": "#/definitions/nestedSqlRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of role-based access control assignments to be created for the definition." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for NoSQL or Table native role-based access control definition." + } + }, + "networkRestrictionType": { + "type": "object", + "properties": { + "ipRules": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A single IPv4 address or a single IPv4 address range in Classless Inter-Domain Routing (CIDR) format. Provided IPs must be well-formatted and cannot be contained in one of the following ranges: `10.0.0.0/8`, `100.64.0.0/10`, `172.16.0.0/12`, `192.168.0.0/16`, since these are not enforceable by the IP address filter. Example of valid inputs: `23.40.210.245` or `23.40.210.0/8`." + } + }, + "networkAclBypass": { + "type": "string", + "allowedValues": [ + "AzureServices", + "None" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specifies the network ACL bypass for Azure services. Default to \"None\"." + } + }, + "publicNetworkAccess": { + "type": "string", + "allowedValues": [ + "Disabled", + "Enabled" + ], + "nullable": true, + "metadata": { + "description": "Optional. Whether requests from the public network are allowed. Default to \"Disabled\"." + } + }, + "virtualNetworkRules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of a subnet." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. List of virtual network access control list (ACL) rules configured for the account." + } + }, + "networkAclBypassResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array that contains the Resource Ids for Network Acl Bypass for the Cosmos DB account." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the network restriction." + } + }, + "gremlinDatabaseType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Gremlin database." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases@2024-11-15#properties/tags" + }, + "description": "Optional. Tags of the Gremlin database resource." + }, + "nullable": true + }, + "graphs": { + "type": "array", + "items": { + "$ref": "#/definitions/graphType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of graphs to deploy in the Gremlin database." + } + }, + "maxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a gremlin databae." + } + }, + "mongoDbType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the mongodb database." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second. Setting throughput at the database level is only recommended for development/test or when workload across all collections in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "collections": { + "type": "array", + "items": { + "$ref": "#/definitions/collectionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Collections in the mongodb database." + } + }, + "autoscaleSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/properties/properties/options/properties/autoscaleSettings" + }, + "description": "Optional. Specifies the Autoscale settings. Note: Either throughput or autoscaleSettings is required, but not both." + }, + "nullable": true + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a mongo databae." + } + }, + "sqlDatabaseType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the SQL database ." + } + }, + "containers": { + "type": "array", + "items": { + "$ref": "#/definitions/containerType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of containers to deploy in the SQL database." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the SQL database resource." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a sql database." + } + }, + "tableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/tables@2025-04-15#properties/tags" + }, + "description": "Optional. Tags for the table." + }, + "nullable": true + }, + "maxThroughput": { + "type": "int", + "nullable": true, + "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`." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a table." + } + }, + "cassandraStandaloneRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique name of the role assignment." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the Azure Cosmos DB for Apache Cassandra native role-based access control definition." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated Microsoft Entra ID principal to which access is being granted through this role-based access control assignment. The tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource path for which access is being granted through this role-based access control assignment. Defaults to the current account." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for Apache Cassandra native role-based access control assignment." + } + }, + "cassandraRoleDefinitionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the role-based access control definition." + } + }, + "roleName": { + "type": "string", + "metadata": { + "description": "Required. A user-friendly name for the role-based access control definition. Must be unique for the database account." + } + }, + "dataActions": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of data actions that are allowed. Note: Valid data action strings are currently undocumented (API version 2025-05-01-preview). Expected to follow format similar to SQL RBAC once documented by Microsoft." + } + }, + "notDataActions": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of data actions that are denied. Note: Unlike SQL RBAC, Cassandra supports deny rules for granular access control. Valid data action strings are currently undocumented (API version 2025-05-01-preview)." + } + }, + "assignableScopes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition." + } + }, + "assignments": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of role-based access control assignments to be created for the definition." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB for Apache Cassandra native role-based access control definition." + } + }, + "cassandraKeyspaceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Cassandra keyspace." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraTableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Cassandra tables to deploy in the keyspace." + } + }, + "views": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraViewType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of Cassandra views (materialized views) to deploy in the keyspace." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level and not at the keyspace level." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `autoscaleSettingsMaxThroughput`. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level and not at the keyspace level." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces@2024-11-15#properties/tags" + }, + "description": "Optional. Tags of the Cassandra keyspace resource." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for an Azure Cosmos DB Cassandra keyspace." + } + }, + "defaultIdentityType": { + "type": "object", + "discriminator": { + "propertyName": "name", + "mapping": { + "FirstPartyIdentity": { + "$ref": "#/definitions/defaultIdentityFirstPartyType" + }, + "SystemAssignedIdentity": { + "$ref": "#/definitions/defaultIdentitySystemAssignedType" + }, + "UserAssignedIdentity": { + "$ref": "#/definitions/defaultIdentityUserAssignedType" + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the default identity." + } + }, + "defaultIdentityFirstPartyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": [ + "FirstPartyIdentity" + ], + "metadata": { + "description": "Required. The type of default identity to use." + } + } + } + }, + "defaultIdentitySystemAssignedType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": [ + "SystemAssignedIdentity" + ], + "metadata": { + "description": "Required. The type of default identity to use." + } + } + } + }, + "defaultIdentityUserAssignedType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "allowedValues": [ + "UserAssignedIdentity" + ], + "metadata": { + "description": "Required. The type of default identity to use." + } + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the user assigned identity to use as the default identity." + } + } + } + }, + "_1.privateEndpointCustomDnsConfigType": { + "type": "object", + "properties": { + "fqdn": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. FQDN that resolves to private endpoint IP address." + } + }, + "ipAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. A list of private IP addresses of the private endpoint." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "_1.privateEndpointIpConfigurationType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the resource that is unique within a resource group." + } + }, + "properties": { + "type": "object", + "properties": { + "groupId": { + "type": "string", + "metadata": { + "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "memberName": { + "type": "string", + "metadata": { + "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to." + } + }, + "privateIPAddress": { + "type": "string", + "metadata": { + "description": "Required. A private IP address obtained from the private endpoint's subnet." + } + } + }, + "metadata": { + "description": "Required. Properties of private endpoint IP configurations." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "_1.privateEndpointPrivateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS Zone Group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + } + }, + "metadata": { + "description": "Required. The private DNS Zone Groups to associate the Private Endpoint. A DNS Zone Group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "cassandraRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the role assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource path for which access is being granted. Defaults to the current account." + } + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "cassandra-role-definition/main.bicep" + } + } + }, + "cassandraTableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + }, + "schema": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/properties/properties/resource/properties/schema" + }, + "description": "Required. Schema definition for the table." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/tags" + }, + "description": "Optional. Tags for the table." + }, + "nullable": true + }, + "defaultTtl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Default TTL (Time To Live) in seconds for data in the table." + } + }, + "analyticalStorageTtl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Analytical TTL for the table." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the table. Cannot be used with throughput." + } + } + }, + "metadata": { + "description": "The type of a Cassandra table.", + "__bicep_imported_from!": { + "sourceTemplate": "cassandra-keyspace/main.bicep", + "originalIdentifier": "tableType" + } + } + }, + "cassandraViewType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the view." + } + }, + "viewDefinition": { + "type": "string", + "metadata": { + "description": "Required. View definition (CQL statement)." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views@2025-05-01-preview#properties/tags" + }, + "description": "Optional. Tags for the view." + }, + "nullable": true + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the view. Cannot be used with throughput." + } + } + }, + "metadata": { + "description": "The type of a Cassandra view (materialized view).", + "__bicep_imported_from!": { + "sourceTemplate": "cassandra-keyspace/main.bicep", + "originalIdentifier": "viewType" + } + } + }, + "collectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the collection." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "indexes": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/indexes" + }, + "description": "Required. Indexes for the collection." + } + }, + "shardKey": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/shardKey" + }, + "description": "Required. ShardKey for the collection." + } + } + }, + "metadata": { + "description": "The type of a collection.", + "__bicep_imported_from!": { + "sourceTemplate": "mongodb-database/main.bicep" + } + } + }, + "containerType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the container." + } + }, + "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." + } + }, + "conflictResolutionPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/conflictResolutionPolicy" + }, + "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." + }, + "nullable": true + }, + "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." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "maxValue": 1000000, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the SQL Database resource." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy" + }, + "description": "Optional. Indexing policy of the container." + }, + "nullable": true + }, + "uniqueKeyPolicyKeys": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/uniqueKeyPolicy/properties/uniqueKeys" + }, + "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 + }, + "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." + } + } + }, + "metadata": { + "description": "The type of a container.", + "__bicep_imported_from!": { + "sourceTemplate": "sql-database/main.bicep" + } + } + }, + "customerManagedKeyAndVaultOnlyType": { + "type": "object", + "properties": { + "keyVaultResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of a key vault to reference a customer managed key for encryption from." + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Required. The name of the customer managed key to use for encryption." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a customer-managed key. To be used if only the key vault & key may be specified.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "diagnosticSettingFullType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "graphType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the graph." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the Gremlin graph resource." + }, + "nullable": true + }, + "indexingPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy" + }, + "description": "Optional. Indexing policy of the graph." + }, + "nullable": true + }, + "partitionKeyPaths": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/partitionKey/properties/paths" + }, + "description": "Optional. List of paths using which data within the container can be partitioned." + }, + "nullable": true + } + }, + "metadata": { + "description": "The type of a graph.", + "__bicep_imported_from!": { + "sourceTemplate": "gremlin-database/main.bicep" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "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.7.0" + } + } + }, + "nestedSqlRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level." + } + } + }, + "metadata": { + "description": "The type for the SQL Role Assignments.", + "__bicep_imported_from!": { + "sourceTemplate": "sql-role-definition/main.bicep", + "originalIdentifier": "sqlRoleAssignmentType" + } + } + }, + "privateEndpointMultiServiceType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private endpoint." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location to deploy the private endpoint to." + } + }, + "privateLinkServiceConnectionName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private link connection to create." + } + }, + "service": { + "type": "string", + "metadata": { + "description": "Required. The subresource to deploy the private endpoint for. For example \"blob\", \"table\", \"queue\" or \"file\" for a Storage Account's Private Endpoints." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "resourceGroupResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the Resource Group the Private Endpoint will be created in. If not specified, the Resource Group of the provided Virtual Network Subnet is used." + } + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/_1.privateEndpointPrivateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "isManualConnection": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. If Manual Private Link Connection is required." + } + }, + "manualConnectionRequestMessage": { + "type": "string", + "nullable": true, + "maxLength": 140, + "metadata": { + "description": "Optional. A message passed to the owner of the remote resource with the manual connection request." + } + }, + "customDnsConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointCustomDnsConfigType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Custom DNS configurations." + } + }, + "ipConfigurations": { + "type": "array", + "items": { + "$ref": "#/definitions/_1.privateEndpointIpConfigurationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-07-01#properties/tags" + }, + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + } + }, + "enableTelemetry": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a private endpoint. To be used if the private endpoint's default service / groupId can NOT be assumed (i.e., for services that have more than one subresource, like Storage Account with Blob (blob, table, queue, file, ...).", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.7.0" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the account." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Defaults to the current resource group scope location. Location for all resources." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts@2024-11-15#properties/tags" + }, + "description": "Optional. Tags for the resource." + }, + "nullable": true + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." + } + }, + "databaseAccountOfferType": { + "type": "string", + "defaultValue": "Standard", + "allowedValues": [ + "Standard" + ], + "metadata": { + "description": "Optional. The offer type for the account. Defaults to \"Standard\"." + } + }, + "failoverLocations": { + "type": "array", + "items": { + "$ref": "#/definitions/failoverLocationType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The set of locations enabled for the account. Defaults to the location where the account is deployed." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Indicates whether the single-region account is zone redundant. Defaults to true. This property is ignored for multi-region accounts." + } + }, + "defaultConsistencyLevel": { + "type": "string", + "defaultValue": "Session", + "allowedValues": [ + "Eventual", + "ConsistentPrefix", + "Session", + "BoundedStaleness", + "Strong" + ], + "metadata": { + "description": "Optional. The default consistency level of the account. Defaults to \"Session\"." + } + }, + "disableLocalAuthentication": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Opt-out of local authentication and ensure that only Microsoft Entra can be used exclusively for authentication. Defaults to true." + } + }, + "enableAnalyticalStorage": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag to indicate whether to enable storage analytics. Defaults to false." + } + }, + "enableAutomaticFailover": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable automatic failover for regions. Defaults to true." + } + }, + "enableFreeTier": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag to indicate whether \"Free Tier\" is enabled. Defaults to false." + } + }, + "enableMultipleWriteLocations": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables the account to write in multiple locations. Periodic backup must be used if enabled. Defaults to false." + } + }, + "disableKeyBasedMetadataWriteAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Disable write operations on metadata resources (databases, containers, throughput) via account keys. Defaults to true." + } + }, + "maxStalenessPrefix": { + "type": "int", + "defaultValue": 100000, + "minValue": 1, + "maxValue": 2147483647, + "metadata": { + "description": "Optional. The maximum stale requests. Required for \"BoundedStaleness\" consistency level. Valid ranges, Single Region: 10 to 1000000. Multi Region: 100000 to 1000000. Defaults to 100000." + } + }, + "maxIntervalInSeconds": { + "type": "int", + "defaultValue": 300, + "minValue": 5, + "maxValue": 86400, + "metadata": { + "description": "Optional. The maximum lag time in minutes. Required for \"BoundedStaleness\" consistency level. Valid ranges, Single Region: 5 to 84600. Multi Region: 300 to 86400. Defaults to 300." + } + }, + "serverVersion": { + "type": "string", + "defaultValue": "4.2", + "allowedValues": [ + "3.2", + "3.6", + "4.0", + "4.2", + "5.0", + "6.0", + "7.0" + ], + "metadata": { + "description": "Optional. Specifies the MongoDB server version to use if using Azure Cosmos DB for MongoDB RU. Defaults to \"4.2\"." + } + }, + "sqlDatabases": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlDatabaseType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for NoSQL." + } + }, + "mongodbDatabases": { + "type": "array", + "items": { + "$ref": "#/definitions/mongoDbType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for MongoDB RU." + } + }, + "gremlinDatabases": { + "type": "array", + "items": { + "$ref": "#/definitions/gremlinDatabaseType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for Apache Gremlin." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration for databases when using Azure Cosmos DB for Table." + } + }, + "cassandraKeyspaces": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraKeyspaceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration for keyspaces when using Azure Cosmos DB for Apache Cassandra." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "totalThroughputLimit": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. The total throughput limit imposed on this account in request units per second (RU/s). Default to unlimited throughput." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of control plane Azure role-based access control assignments." + } + }, + "sqlRoleDefinitions": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlRoleDefinitionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configurations for Azure Cosmos DB for NoSQL native role-based access control definitions. Allows the creations of custom role definitions." + } + }, + "sqlRoleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configurations for Azure Cosmos DB for NoSQL native role-based access control assignments." + } + }, + "cassandraRoleDefinitions": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraRoleDefinitionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configurations for Azure Cosmos DB for Apache Cassandra native role-based access control definitions. Allows the creations of custom role definitions." + } + }, + "cassandraRoleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraStandaloneRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Azure Cosmos DB for Apache Cassandra native data plane role-based access control assignments. Each assignment references a role definition unique identifier and a principal identifier." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingFullType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings for the service." + } + }, + "capabilitiesToAdd": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "allowedValues": [ + "EnableCassandra", + "EnableTable", + "EnableGremlin", + "EnableMongo", + "DisableRateLimitingResponses", + "EnableServerless", + "EnableNoSQLVectorSearch", + "EnableNoSQLFullTextSearch", + "EnableMaterializedViews", + "DeleteAllItemsByPartitionKey" + ], + "metadata": { + "description": "Optional. A list of Azure Cosmos DB specific capabilities for the account." + } + }, + "backupPolicyType": { + "type": "string", + "defaultValue": "Continuous", + "allowedValues": [ + "Periodic", + "Continuous" + ], + "metadata": { + "description": "Optional. Configures the backup mode. Periodic backup must be used if multiple write locations are used. Defaults to \"Continuous\"." + } + }, + "backupPolicyContinuousTier": { + "type": "string", + "defaultValue": "Continuous30Days", + "allowedValues": [ + "Continuous30Days", + "Continuous7Days" + ], + "metadata": { + "description": "Optional. Configuration values to specify the retention period for continuous mode backup. Default to \"Continuous30Days\"." + } + }, + "backupIntervalInMinutes": { + "type": "int", + "defaultValue": 240, + "minValue": 60, + "maxValue": 1440, + "metadata": { + "description": "Optional. An integer representing the interval in minutes between two backups. This setting only applies to the periodic backup type. Defaults to 240." + } + }, + "backupRetentionIntervalInHours": { + "type": "int", + "defaultValue": 8, + "minValue": 2, + "maxValue": 720, + "metadata": { + "description": "Optional. An integer representing the time (in hours) that each backup is retained. This setting only applies to the periodic backup type. Defaults to 8." + } + }, + "backupStorageRedundancy": { + "type": "string", + "defaultValue": "Local", + "allowedValues": [ + "Geo", + "Local", + "Zone" + ], + "metadata": { + "description": "Optional. Setting that indicates the type of backup residency. This setting only applies to the periodic backup type. Defaults to \"Local\"." + } + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointMultiServiceType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Configuration details for private endpoints. For security reasons, it is advised to use private endpoints whenever possible." + } + }, + "networkRestrictions": { + "$ref": "#/definitions/networkRestrictionType", + "defaultValue": { + "ipRules": [], + "virtualNetworkRules": [], + "publicNetworkAccess": "Disabled" + }, + "metadata": { + "description": "Optional. The network configuration of this module. Defaults to `{ ipRules: [], virtualNetworkRules: [], publicNetworkAccess: 'Disabled' }`." + } + }, + "minimumTlsVersion": { + "type": "string", + "defaultValue": "Tls12", + "allowedValues": [ + "Tls12" + ], + "metadata": { + "description": "Optional. Setting that indicates the minimum allowed TLS version. Azure Cosmos DB for MongoDB RU and Apache Cassandra only work with TLS 1.2 or later. Defaults to \"Tls12\" (TLS 1.2)." + } + }, + "enableBurstCapacity": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Flag to indicate enabling/disabling of Burst Capacity feature on the account. Cannot be enabled for serverless accounts." + } + }, + "enableCassandraConnector": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Enables the cassandra connector on the Cosmos DB C* account." + } + }, + "enablePartitionMerge": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag to enable/disable the 'Partition Merge' feature on the account." + } + }, + "enablePerRegionPerPartitionAutoscale": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Flag to enable/disable the 'PerRegionPerPartitionAutoscale' feature on the account." + } + }, + "analyticalStorageConfiguration": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts@2025-04-15#properties/properties/properties/analyticalStorageConfiguration" + }, + "description": "Optional. Analytical storage specific properties." + }, + "nullable": true + }, + "cors": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts@2025-04-15#properties/properties/properties/cors" + }, + "description": "Optional. The CORS policy for the Cosmos DB database account." + }, + "nullable": true + }, + "defaultIdentity": { + "$ref": "#/definitions/defaultIdentityType", + "defaultValue": { + "name": "FirstPartyIdentity" + }, + "metadata": { + "description": "Optional. The default identity for accessing key vault used in features like customer managed keys. Use `FirstPartyIdentity` to use the tenant-level CosmosDB enterprise application. The default identity needs to be explicitly set by the users." + } + }, + "customerManagedKey": { + "$ref": "#/definitions/customerManagedKeyAndVaultOnlyType", + "nullable": true, + "metadata": { + "description": "Optional. The customer managed key definition. If specified, the parameter `defaultIdentity` must be configured as well." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInControlPlaneRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "enableReferencedModulesTelemetry": false, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(variables('formattedUserAssignedIdentities'))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(variables('formattedUserAssignedIdentities'))), 'UserAssigned', null())), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]", + "builtInControlPlaneRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Cosmos DB Account Reader Role": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'fbdf93bf-df7d-467e-a4d2-9458aa1360c8')]", + "Cosmos DB Operator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '230815da-be43-4aae-9cb4-875f7bd000aa')]", + "CosmosBackupOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'db7b14f2-5adf-42da-9f96-f2ee17bab5cb')]", + "CosmosRestoreOperator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5432c526-bc82-444a-b7ba-57c5b0b5b34f')]", + "DocumentDB Account Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5bd9cd88-fe45-4216-938b-f97437e15450')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "isHSMManagedCMK": "[equals(tryGet(split(coalesce(tryGet(parameters('customerManagedKey'), 'keyVaultResourceId'), ''), '/'), 7), 'managedHSMs')]" + }, + "resources": { + "cMKKeyVault::cMKKey": { + "condition": "[and(and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))), and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK'))))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(parameters('customerManagedKey').keyVaultResourceId, '/')[2]]", + "resourceGroup": "[split(parameters('customerManagedKey').keyVaultResourceId, '/')[4]]", + "name": "[format('{0}/{1}', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')), parameters('customerManagedKey').keyName)]" + }, + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-07-01", + "name": "[format('46d3xbcp.res.documentdb-databaseaccount.{0}.{1}', replace('0.19.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "cMKKeyVault": { + "condition": "[and(not(empty(parameters('customerManagedKey'))), not(variables('isHSMManagedCMK')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2024-11-01", + "subscriptionId": "[split(parameters('customerManagedKey').keyVaultResourceId, '/')[2]]", + "resourceGroup": "[split(parameters('customerManagedKey').keyVaultResourceId, '/')[4]]", + "name": "[last(split(parameters('customerManagedKey').keyVaultResourceId, '/'))]" + }, + "databaseAccount": { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-04-15", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "kind": "[if(not(empty(parameters('mongodbDatabases'))), 'MongoDB', 'GlobalDocumentDB')]", + "properties": "[shallowMerge(createArray(createObject('enableBurstCapacity', if(not(contains(coalesce(parameters('capabilitiesToAdd'), createArray()), 'EnableServerless')), parameters('enableBurstCapacity'), false()), 'databaseAccountOfferType', parameters('databaseAccountOfferType'), 'analyticalStorageConfiguration', parameters('analyticalStorageConfiguration'), 'defaultIdentity', if(and(not(empty(parameters('defaultIdentity'))), not(equals(tryGet(parameters('defaultIdentity'), 'name'), 'UserAssignedIdentity'))), parameters('defaultIdentity').name, format('UserAssignedIdentity={0}', tryGet(parameters('defaultIdentity'), 'resourceId'))), 'keyVaultKeyUri', if(not(empty(parameters('customerManagedKey'))), if(not(variables('isHSMManagedCMK')), format('{0}', reference('cMKKeyVault::cMKKey').keyUri), format('https://{0}.managedhsm.azure.net/keys/{1}', last(split(parameters('customerManagedKey').keyVaultResourceId, '/')), parameters('customerManagedKey').keyName)), null()), 'enablePartitionMerge', parameters('enablePartitionMerge'), 'enablePerRegionPerPartitionAutoscale', parameters('enablePerRegionPerPartitionAutoscale'), 'backupPolicy', shallowMerge(createArray(createObject('type', parameters('backupPolicyType')), if(equals(parameters('backupPolicyType'), 'Continuous'), createObject('continuousModeProperties', createObject('tier', parameters('backupPolicyContinuousTier'))), createObject()), if(equals(parameters('backupPolicyType'), 'Periodic'), createObject('periodicModeProperties', createObject('backupIntervalInMinutes', parameters('backupIntervalInMinutes'), 'backupRetentionIntervalInHours', parameters('backupRetentionIntervalInHours'), 'backupStorageRedundancy', parameters('backupStorageRedundancy'))), createObject()))), 'capabilities', map(coalesce(parameters('capabilitiesToAdd'), createArray()), lambda('capability', createObject('name', lambdaVariables('capability'))))), if(not(empty(parameters('cors'))), createObject('cors', parameters('cors')), createObject()), if(contains(coalesce(parameters('capabilitiesToAdd'), createArray()), 'EnableCassandra'), createObject('connectorOffer', if(parameters('enableCassandraConnector'), 'Small', null()), 'enableCassandraConnector', parameters('enableCassandraConnector')), createObject()), createObject('minimalTlsVersion', parameters('minimumTlsVersion'), 'capacity', createObject('totalThroughputLimit', parameters('totalThroughputLimit')), 'publicNetworkAccess', coalesce(tryGet(parameters('networkRestrictions'), 'publicNetworkAccess'), 'Disabled'), 'locations', if(not(empty(parameters('failoverLocations'))), map(parameters('failoverLocations'), lambda('failoverLocation', createObject('failoverPriority', lambdaVariables('failoverLocation').failoverPriority, 'locationName', lambdaVariables('failoverLocation').locationName, 'isZoneRedundant', coalesce(tryGet(lambdaVariables('failoverLocation'), 'isZoneRedundant'), true())))), createArray(createObject('failoverPriority', 0, 'locationName', parameters('location'), 'isZoneRedundant', parameters('zoneRedundant'))))), if(or(or(or(or(not(empty(parameters('sqlDatabases'))), not(empty(parameters('mongodbDatabases')))), not(empty(parameters('gremlinDatabases')))), not(empty(parameters('tables')))), not(empty(parameters('cassandraKeyspaces')))), createObject('consistencyPolicy', shallowMerge(createArray(createObject('defaultConsistencyLevel', parameters('defaultConsistencyLevel')), if(equals(parameters('defaultConsistencyLevel'), 'BoundedStaleness'), createObject('maxStalenessPrefix', parameters('maxStalenessPrefix'), 'maxIntervalInSeconds', parameters('maxIntervalInSeconds')), createObject()))), 'enableMultipleWriteLocations', parameters('enableMultipleWriteLocations'), 'ipRules', map(coalesce(tryGet(parameters('networkRestrictions'), 'ipRules'), createArray()), lambda('ipRule', createObject('ipAddressOrRange', lambdaVariables('ipRule')))), 'virtualNetworkRules', map(coalesce(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules'), createArray()), lambda('rule', createObject('id', lambdaVariables('rule').subnetResourceId, 'ignoreMissingVNetServiceEndpoint', false()))), 'networkAclBypass', coalesce(tryGet(parameters('networkRestrictions'), 'networkAclBypass'), 'None'), 'networkAclBypassResourceIds', tryGet(parameters('networkRestrictions'), 'networkAclBypassResourceIds'), 'isVirtualNetworkFilterEnabled', or(not(empty(tryGet(parameters('networkRestrictions'), 'ipRules'))), not(empty(tryGet(parameters('networkRestrictions'), 'virtualNetworkRules')))), 'enableFreeTier', parameters('enableFreeTier'), 'enableAutomaticFailover', parameters('enableAutomaticFailover'), 'enableAnalyticalStorage', parameters('enableAnalyticalStorage')), createObject()), if(or(or(not(empty(parameters('mongodbDatabases'))), not(empty(parameters('gremlinDatabases')))), not(empty(parameters('cassandraKeyspaces')))), createObject('disableLocalAuth', false(), 'disableKeyBasedMetadataWriteAccess', false()), createObject('disableLocalAuth', parameters('disableLocalAuthentication'), 'disableKeyBasedMetadataWriteAccess', parameters('disableKeyBasedMetadataWriteAccess'))), if(not(empty(parameters('mongodbDatabases'))), createObject('apiProperties', createObject('serverVersion', parameters('serverVersion'))), createObject())))]", + "dependsOn": [ + "cMKKeyVault::cMKKey" + ] + }, + "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": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_diagnosticSettings": { + "copy": { + "name": "databaseAccount_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', 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": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_sqlDatabases": { + "copy": { + "name": "databaseAccount_sqlDatabases", + "count": "[length(coalesce(parameters('sqlDatabases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('sqlDatabases'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('sqlDatabases'), createArray())[copyIndex()].name]" + }, + "containers": { + "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'containers')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'throughput')]" + }, + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(coalesce(parameters('sqlDatabases'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "3972888645334640168" + }, + "name": "DocumentDB Database Account SQL Databases", + "description": "This module deploys a SQL Database in a CosmosDB Account." + }, + "definitions": { + "containerType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the container." + } + }, + "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." + } + }, + "conflictResolutionPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/conflictResolutionPolicy" + }, + "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." + }, + "nullable": true + }, + "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." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "maxValue": 1000000, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the SQL Database resource." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy" + }, + "description": "Optional. Indexing policy of the container." + }, + "nullable": true + }, + "uniqueKeyPolicyKeys": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/uniqueKeyPolicy/properties/uniqueKeys" + }, + "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 + }, + "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." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a container." + } + } + }, + "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": { + "$ref": "#/definitions/containerType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of containers to deploy in the SQL database." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. Setting throughput at the database level is only recommended for development/test or when workload across all containers in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the SQL database resource." + }, + "nullable": true + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-04-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlDatabase": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2025-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(coalesce(parameters('containers'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-sqldb-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('containers'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "sqlDatabaseName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('containers'), createArray())[copyIndex()].name]" + }, + "analyticalStorageTtl": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'analyticalStorageTtl')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + }, + "conflictResolutionPolicy": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'conflictResolutionPolicy')]" + }, + "defaultTtl": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'defaultTtl')]" + }, + "indexingPolicy": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'indexingPolicy')]" + }, + "kind": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'kind')]" + }, + "version": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'version')]" + }, + "paths": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'paths')]" + }, + "throughput": "[if(and(or(not(equals(parameters('throughput'), null())), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), equals(tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'throughput'), null())), createObject('value', -1), createObject('value', tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'throughput')))]", + "uniqueKeyPolicyKeys": { + "value": "[tryGet(coalesce(parameters('containers'), createArray())[copyIndex()], 'uniqueKeyPolicyKeys')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "4781880351108045502" + }, + "name": "DocumentDB Database Account SQL Database Containers", + "description": "This module deploys a SQL Database Container in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "sqlDatabaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent SQL Database. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the container." + } + }, + "analyticalStorageTtl": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Default to 0. Indicates how long data should be retained in the analytical store, for a container. Analytical store is enabled when ATTL is set with a value other than 0. If the value is set to -1, the analytical store retains all historical data, irrespective of the retention of the data in the transactional store." + } + }, + "conflictResolutionPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/conflictResolutionPolicy" + }, + "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." + }, + "nullable": true + }, + "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." + } + }, + "throughput": { + "type": "int", + "defaultValue": 400, + "metadata": { + "description": "Optional. Default to 400. Request Units per second. Will be ignored if autoscaleSettingsMaxThroughput is used. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "maxValue": 1000000, + "metadata": { + "description": "Optional. Specifies the Autoscale settings and represents maximum throughput, the resource can scale up to. The autoscale throughput should have valid throughput values between 1000 and 1000000 inclusive in increments of 1000. If value is set to null, then autoscale will be disabled. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the container level and not at the database level." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the SQL Database resource." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy" + }, + "description": "Optional. Indexing policy of the container." + }, + "nullable": true + }, + "uniqueKeyPolicyKeys": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2025-04-15#properties/properties/properties/resource/properties/uniqueKeyPolicy/properties/uniqueKeys" + }, + "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 + }, + "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')]))]" + } + ] + }, + "resources": { + "databaseAccount::sqlDatabase": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2025-04-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('sqlDatabaseName'))]" + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-04-15", + "name": "[parameters('databaseAccountName')]" + }, + "container": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", + "apiVersion": "2025-04-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": "[shallowMerge(createArray(createObject('conflictResolutionPolicy', parameters('conflictResolutionPolicy'), 'id', parameters('name'), 'indexingPolicy', parameters('indexingPolicy'), '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()), if(not(equals(parameters('defaultTtl'), null())), createObject('defaultTtl', parameters('defaultTtl')), createObject())))]", + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), null(), createObject('throughput', if(and(equals(parameters('autoscaleSettingsMaxThroughput'), null()), not(equals(parameters('throughput'), -1))), parameters('throughput'), null()), 'autoscaleSettings', if(not(equals(parameters('autoscaleSettingsMaxThroughput'), null())), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null())))]" + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the container." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers', parameters('databaseAccountName'), parameters('sqlDatabaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the container was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "sqlDatabase" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL database." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL database was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_sqlRoleDefinitions": { + "copy": { + "name": "databaseAccount_sqlRoleDefinitions", + "count": "[length(coalesce(parameters('sqlRoleDefinitions'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-sqlrd-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'name')]" + }, + "dataActions": { + "value": "[coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()].dataActions]" + }, + "roleName": { + "value": "[coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()].roleName]" + }, + "assignableScopes": { + "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'assignableScopes')]" + }, + "sqlRoleAssignments": { + "value": "[tryGet(coalesce(parameters('sqlRoleDefinitions'), createArray())[copyIndex()], 'assignments')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "8514367433037227852" + }, + "name": "DocumentDB Database Account SQL Role Definitions.", + "description": "This module deploys a SQL Role Definision in a CosmosDB Account." + }, + "definitions": { + "sqlRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the SQL Role Assignments." + } + } + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the Role Definition." + } + }, + "roleName": { + "type": "string", + "metadata": { + "description": "Required. A user-friendly name for the Role Definition. Must be unique for the database account." + } + }, + "dataActions": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "metadata": { + "description": "Required. An array of data actions that are allowed." + } + }, + "assignableScopes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition. This will allow application of this Role Definition on the entire database account or any underlying Database / Collection. Must have at least one element. Scopes higher than Database account are not enforceable as assignable Scopes. Note that resources referenced in assignable Scopes need not exist. Defaults to the current account." + } + }, + "sqlRoleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/sqlRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of SQL Role Assignments to be created for the SQL Role Definition." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "enableReferencedModulesTelemetry": false + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.doctdb-dbacct-sqlroledefinition.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlRoleDefinition": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]", + "properties": { + "assignableScopes": "[coalesce(parameters('assignableScopes'), createArray(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]", + "permissions": [ + { + "dataActions": "[parameters('dataActions')]" + } + ], + "roleName": "[parameters('roleName')]", + "type": "CustomRole" + } + }, + "databaseAccount_sqlRoleAssignments": { + "copy": { + "name": "databaseAccount_sqlRoleAssignments", + "count": "[length(coalesce(parameters('sqlRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-sqlra-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "roleDefinitionIdOrName": { + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]" + }, + "principalId": { + "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'name')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "11817543900771838380" + }, + "name": "DocumentDB Database Account SQL Role Assignments.", + "description": "This module deploys a SQL Role Assignment in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the associated SQL Role Definition." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level." + } + } + }, + "variables": { + "builtInDataPlaneRoleNames": { + "Cosmos DB Built-in Data Reader": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]", + "Cosmos DB Built-in Data Contributor": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]" + }, + "formattedRoleDefinition": "[coalesce(tryGet(variables('builtInDataPlaneRoleNames'), parameters('roleDefinitionIdOrName')), if(contains(parameters('roleDefinitionIdOrName'), '/sqlRoleDefinitions/'), parameters('roleDefinitionIdOrName'), format('{0}/sqlRoleDefinitions/{1}', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('roleDefinitionIdOrName'))))]", + "formattedScope": "[replace(replace(coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))), '/sqlDatabases/', '/dbs/'), '/containers/', '/colls/')]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.doctdb-dbacct-sqlroleassignment.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlRoleAssignment": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[variables('formattedRoleDefinition')]", + "scope": "[variables('formattedScope')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL Role Assignment." + }, + "value": "[coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope')))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL Role Assignment." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL Role Definition was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "sqlRoleDefinition" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL Role Definition." + }, + "value": "[coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName')))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL Role Definition." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL Role Definition was created in." + }, + "value": "[resourceGroup().name]" + }, + "roleName": { + "type": "string", + "metadata": { + "description": "The role name of the SQL Role Definition." + }, + "value": "[reference('sqlRoleDefinition').roleName]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_sqlRoleAssignments": { + "copy": { + "name": "databaseAccount_sqlRoleAssignments", + "count": "[length(coalesce(parameters('sqlRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-sqlra-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "roleDefinitionIdOrName": { + "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]" + }, + "principalId": { + "value": "[coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'name')]" + }, + "scope": { + "value": "[tryGet(coalesce(parameters('sqlRoleAssignments'), createArray())[copyIndex()], 'scope')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "11817543900771838380" + }, + "name": "DocumentDB Database Account SQL Role Assignments.", + "description": "This module deploys a SQL Role Assignment in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the SQL Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the associated SQL Role Definition." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource id for which access is being granted through this Role Assignment. Defaults to the root of the database account, but can also be scoped to e.g., the container and database level." + } + } + }, + "variables": { + "builtInDataPlaneRoleNames": { + "Cosmos DB Built-in Data Reader": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000001', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]", + "Cosmos DB Built-in Data Contributor": "[format('{0}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]" + }, + "formattedRoleDefinition": "[coalesce(tryGet(variables('builtInDataPlaneRoleNames'), parameters('roleDefinitionIdOrName')), if(contains(parameters('roleDefinitionIdOrName'), '/sqlRoleDefinitions/'), parameters('roleDefinitionIdOrName'), format('{0}/sqlRoleDefinitions/{1}', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('roleDefinitionIdOrName'))))]", + "formattedScope": "[replace(replace(coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))), '/sqlDatabases/', '/dbs/'), '/containers/', '/colls/')]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.doctdb-dbacct-sqlroleassignment.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "sqlRoleAssignment": { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[variables('formattedRoleDefinition')]", + "scope": "[variables('formattedScope')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the SQL Role Assignment." + }, + "value": "[coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope')))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the SQL Role Assignment." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(variables('formattedRoleDefinition'), parameters('principalId'), variables('formattedScope'))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the SQL Role Definition was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount", + "databaseAccount_sqlDatabases", + "databaseAccount_sqlRoleDefinitions" + ] + }, + "databaseAccount_cassandraRoleDefinitions": { + "copy": { + "name": "databaseAccount_cassandraRoleDefinitions", + "count": "[length(coalesce(parameters('cassandraRoleDefinitions'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cassandra-rd-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'name')]" + }, + "roleName": { + "value": "[coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()].roleName]" + }, + "dataActions": { + "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'dataActions')]" + }, + "notDataActions": { + "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'notDataActions')]" + }, + "assignableScopes": { + "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'assignableScopes')]" + }, + "cassandraRoleAssignments": { + "value": "[tryGet(coalesce(parameters('cassandraRoleDefinitions'), createArray())[copyIndex()], 'assignments')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "10787709019875067397" + }, + "name": "DocumentDB Database Account Cassandra Role Definitions.", + "description": "This module deploys a Cassandra Role Definition in a CosmosDB Account." + }, + "definitions": { + "cassandraRoleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the role assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource path for which access is being granted. Defaults to the current account." + } + } + }, + "metadata": { + "__bicep_export!": true + } + } + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The unique identifier of the Role Definition." + } + }, + "roleName": { + "type": "string", + "metadata": { + "description": "Required. A user-friendly name for the Role Definition. Must be unique for the database account." + } + }, + "dataActions": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. An array of data actions that are allowed. Note: Valid data action strings for Cassandra API are currently undocumented (as of API version 2025-05-01-preview). Please refer to official Azure documentation once available." + } + }, + "notDataActions": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. An array of data actions that are denied. Note: Unlike SQL RBAC, Cassandra RBAC supports deny rules (notDataActions) for granular access control. Valid data action strings are currently undocumented (as of API version 2025-05-01-preview)." + } + }, + "assignableScopes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. A set of fully qualified Scopes at or below which Role Assignments may be created using this Role Definition. This will allow application of this Role Definition on the entire database account or any underlying Database / Keyspace. Must have at least one element. Scopes higher than Database account are not enforceable as assignable Scopes. Note that resources referenced in assignable Scopes need not exist. Defaults to the current account." + } + }, + "cassandraRoleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/cassandraRoleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. An array of Cassandra Role Assignments to be created for the Cassandra Role Definition." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "cassandraRoleDefinition": { + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraRoleDefinitions", + "apiVersion": "2025-05-01-preview", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]", + "properties": { + "assignableScopes": "[coalesce(parameters('assignableScopes'), createArray(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]", + "permissions": [ + { + "dataActions": "[parameters('dataActions')]", + "notDataActions": "[parameters('notDataActions')]" + } + ], + "roleName": "[parameters('roleName')]", + "type": "CustomRole" + } + }, + "databaseAccount_cassandraRoleAssignments": { + "copy": { + "name": "databaseAccount_cassandraRoleAssignments", + "count": "[length(coalesce(parameters('cassandraRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cassandra-ra-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "roleDefinitionId": { + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]" + }, + "principalId": { + "value": "[coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'name')]" + }, + "scope": { + "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'scope')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "14764024820910071147" + }, + "name": "DocumentDB Database Account Cassandra Role Assignments.", + "description": "This module deploys a Cassandra Role Assignment in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the Cassandra Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the associated Cassandra Role Definition." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource path for which access is being granted through this Cassandra Role Assignment. Defaults to the current account." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "cassandraRoleAssignment": { + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments", + "apiVersion": "2025-05-01-preview", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "scope": "[coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Cassandra Role Assignment." + }, + "value": "[coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Cassandra Role Assignment." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Cassandra Role Assignment was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "cassandraRoleDefinition" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the cassandra role definition." + }, + "value": "[coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName')))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cassandra role definition." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleDefinitions', parameters('databaseAccountName'), coalesce(parameters('name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), parameters('databaseAccountName'), parameters('roleName'))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the cassandra role definition was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_cassandraRoleAssignments": { + "copy": { + "name": "databaseAccount_cassandraRoleAssignments", + "count": "[length(coalesce(parameters('cassandraRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cassandra-ra-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "roleDefinitionId": { + "value": "[coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]" + }, + "principalId": { + "value": "[coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()].principalId]" + }, + "name": { + "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'name')]" + }, + "scope": { + "value": "[tryGet(coalesce(parameters('cassandraRoleAssignments'), createArray())[copyIndex()], 'scope')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "14764024820910071147" + }, + "name": "DocumentDB Database Account Cassandra Role Assignments.", + "description": "This module deploys a Cassandra Role Assignment in a CosmosDB Account." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name unique identifier of the Cassandra Role Assignment." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier for the associated AAD principal in the AAD graph to which access is being granted through this Role Assignment. Tenant ID for the principal is inferred using the tenant associated with the subscription." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "Required. The unique identifier of the associated Cassandra Role Definition." + } + }, + "scope": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The data plane resource path for which access is being granted through this Cassandra Role Assignment. Defaults to the current account." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "cassandraRoleAssignment": { + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments", + "apiVersion": "2025-05-01-preview", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]", + "properties": { + "principalId": "[parameters('principalId')]", + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "scope": "[coalesce(parameters('scope'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Cassandra Role Assignment." + }, + "value": "[coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName'))))]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Cassandra Role Assignment." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraRoleAssignments', parameters('databaseAccountName'), coalesce(parameters('name'), guid(parameters('roleDefinitionId'), parameters('principalId'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')))))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Cassandra Role Assignment was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount", + "databaseAccount_cassandraKeyspaces", + "databaseAccount_cassandraRoleDefinitions" + ] + }, + "databaseAccount_mongodbDatabases": { + "copy": { + "name": "databaseAccount_mongodbDatabases", + "count": "[length(coalesce(parameters('mongodbDatabases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-mongodb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "collections": { + "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'collections')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'throughput')]" + }, + "autoscaleSettings": { + "value": "[tryGet(coalesce(parameters('mongodbDatabases'), createArray())[copyIndex()], 'autoscaleSettings')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "13897098552792121791" + }, + "name": "DocumentDB Database Account MongoDB Databases", + "description": "This module deploys a MongoDB Database within a CosmosDB Account." + }, + "definitions": { + "collectionType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the collection." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "indexes": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/indexes" + }, + "description": "Required. Indexes for the collection." + } + }, + "shardKey": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/shardKey" + }, + "description": "Required. ShardKey for the collection." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a collection." + } + } + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the mongodb database." + } + }, + "throughput": { + "type": "int", + "defaultValue": 400, + "metadata": { + "description": "Optional. Request Units per second. Setting throughput at the database level is only recommended for development/test or when workload across all collections in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "collections": { + "type": "array", + "items": { + "$ref": "#/definitions/collectionType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Collections in the mongodb database." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "autoscaleSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2025-04-15#properties/properties/properties/options/properties/autoscaleSettings" + }, + "description": "Optional. Specifies the Autoscale settings. Note: Either throughput or autoscaleSettings is required, but not both." + }, + "nullable": true + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-04-15", + "name": "[parameters('databaseAccountName')]" + }, + "mongodbDatabase": { + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases", + "apiVersion": "2025-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'), 'autoscaleSettings', parameters('autoscaleSettings')))]" + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "mongodbDatabase_collections": { + "copy": { + "name": "mongodbDatabase_collections", + "count": "[length(coalesce(parameters('collections'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-collection-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('collections'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "mongodbDatabaseName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].name]" + }, + "indexes": { + "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].indexes]" + }, + "shardKey": { + "value": "[coalesce(parameters('collections'), createArray())[copyIndex()].shardKey]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('collections'), createArray())[copyIndex()], 'throughput')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "16151461445994734468" + }, + "name": "DocumentDB Database Account MongoDB Database Collections", + "description": "This module deploys a MongoDB Database Collection." + }, + "parameters": { + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cosmos DB database account. Required if the template is used in a standalone deployment." + } + }, + "mongodbDatabaseName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent mongodb database. Required if the template is used in a standalone deployment." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the collection." + } + }, + "throughput": { + "type": "int", + "defaultValue": 400, + "metadata": { + "description": "Optional. Request Units per second. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the collection level and not at the database level." + } + }, + "indexes": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/indexes" + }, + "description": "Required. Indexes for the collection." + } + }, + "shardKey": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections@2025-04-15#properties/properties/properties/resource/properties/shardKey" + }, + "description": "Required. ShardKey for the collection." + } + } + }, + "resources": [ + { + "type": "Microsoft.DocumentDB/databaseAccounts/mongodbDatabases/collections", + "apiVersion": "2025-04-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('mongodbDatabaseName'), parameters('name'))]", + "properties": { + "options": "[if(contains(reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('databaseAccountName')), '2025-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(coalesce(parameters('gremlinDatabases'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-gremlin-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "graphs": { + "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'graphs')]" + }, + "maxThroughput": { + "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'maxThroughput')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('gremlinDatabases'), createArray())[copyIndex()], 'throughput')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "11959636451300474346" + }, + "name": "DocumentDB Database Account Gremlin Databases", + "description": "This module deploys a Gremlin Database within a CosmosDB Account." + }, + "definitions": { + "graphType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the graph." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the Gremlin graph resource." + }, + "nullable": true + }, + "indexingPolicy": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy" + }, + "description": "Optional. Indexing policy of the graph." + }, + "nullable": true + }, + "partitionKeyPaths": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/partitionKey/properties/paths" + }, + "description": "Optional. List of paths using which data within the container can be partitioned." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a graph." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Gremlin database." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases@2024-11-15#properties/tags" + }, + "description": "Optional. Tags of the Gremlin database resource." + }, + "nullable": true + }, + "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", + "items": { + "$ref": "#/definitions/graphType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of graphs to deploy in the Gremlin database." + } + }, + "maxThroughput": { + "type": "int", + "defaultValue": 4000, + "metadata": { + "description": "Optional. Represents maximum throughput, the resource can scale up to. Cannot be set together with `throughput`. If `throughput` is set to something else than -1, this autoscale setting is ignored. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request Units per second (for example 10000). Cannot be set together with `maxThroughput`. Setting throughput at the database level is only recommended for development/test or when workload across all graphs in the shared throughput database is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the graph level and not at the database level." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-04-15", + "name": "[parameters('databaseAccountName')]" + }, + "gremlinDatabase": { + "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases", + "apiVersion": "2025-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(coalesce(parameters('graphs'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-gremlindb-{1}', uniqueString(deployment().name, parameters('name')), coalesce(parameters('graphs'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('graphs'), createArray())[copyIndex()].name]" + }, + "gremlinDatabaseName": { + "value": "[parameters('name')]" + }, + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "indexingPolicy": { + "value": "[tryGet(coalesce(parameters('graphs'), createArray())[copyIndex()], 'indexingPolicy')]" + }, + "partitionKeyPaths": { + "value": "[tryGet(coalesce(parameters('graphs'), createArray())[copyIndex()], 'partitionKeyPaths')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "10487122333182352122" + }, + "name": "DocumentDB Database Accounts Gremlin Databases Graphs", + "description": "This module deploys a DocumentDB Database Accounts Gremlin Database Graph." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the graph." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/tags" + }, + "description": "Optional. Tags of the Gremlin graph resource." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/indexingPolicy" + }, + "description": "Optional. Indexing policy of the graph." + }, + "nullable": true + }, + "partitionKeyPaths": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs@2025-04-15#properties/properties/properties/resource/properties/partitionKey/properties/paths" + }, + "description": "Optional. List of paths using which data within the container can be partitioned." + }, + "nullable": true + } + }, + "resources": { + "databaseAccount::gremlinDatabase": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases", + "apiVersion": "2025-04-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'))]" + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-04-15", + "name": "[parameters('databaseAccountName')]" + }, + "gremlinGraph": { + "type": "Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs", + "apiVersion": "2025-04-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('name')]", + "indexingPolicy": "[parameters('indexingPolicy')]", + "partitionKey": { + "paths": "[parameters('partitionKeyPaths')]" + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the graph." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the graph." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases/graphs', parameters('databaseAccountName'), parameters('gremlinDatabaseName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the graph was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "gremlinDatabase" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Gremlin database." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Gremlin database." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/gremlinDatabases', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Gremlin database was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "databaseAccount_tables": { + "copy": { + "name": "databaseAccount_tables", + "count": "[length(coalesce(parameters('tables'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-table-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('tables'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('tables'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "maxThroughput": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'maxThroughput')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('tables'), createArray())[copyIndex()], 'throughput')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "1787500858429182824" + }, + "name": "Azure Cosmos DB account tables", + "description": "This module deploys a table within an Azure Cosmos DB Account." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/tables@2025-04-15#properties/tags" + }, + "description": "Optional. Tags for the table." + }, + "nullable": true + }, + "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": "2025-04-15", + "name": "[parameters('databaseAccountName')]" + }, + "table": { + "type": "Microsoft.DocumentDB/databaseAccounts/tables", + "apiVersion": "2025-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_cassandraKeyspaces": { + "copy": { + "name": "databaseAccount_cassandraKeyspaces", + "count": "[length(coalesce(parameters('cassandraKeyspaces'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cassandradb-{1}', uniqueString(deployment().name, parameters('location')), coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "databaseAccountName": { + "value": "[parameters('name')]" + }, + "name": { + "value": "[coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()].name]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "tables": { + "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'tables')]" + }, + "views": { + "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'views')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + }, + "throughput": { + "value": "[tryGet(coalesce(parameters('cassandraKeyspaces'), createArray())[copyIndex()], 'throughput')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "15257396763463366586" + }, + "name": "DocumentDB Database Account Cassandra Keyspaces", + "description": "This module deploys a Cassandra Keyspace within a CosmosDB Account." + }, + "definitions": { + "tableType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the table." + } + }, + "schema": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/properties/properties/resource/properties/schema" + }, + "description": "Required. Schema definition for the table." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/tags" + }, + "description": "Optional. Tags for the table." + }, + "nullable": true + }, + "defaultTtl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Default TTL (Time To Live) in seconds for data in the table." + } + }, + "analyticalStorageTtl": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Analytical TTL for the table." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the table. Cannot be used with throughput." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a Cassandra table." + } + }, + "viewType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the view." + } + }, + "viewDefinition": { + "type": "string", + "metadata": { + "description": "Required. View definition (CQL statement)." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views@2025-05-01-preview#properties/tags" + }, + "description": "Optional. Tags for the view." + }, + "nullable": true + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the view. Cannot be used with throughput." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a Cassandra view (materialized view)." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Cassandra keyspace." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces@2024-11-15#properties/tags" + }, + "description": "Optional. Tags of the Cassandra keyspace resource." + }, + "nullable": true + }, + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cosmos DB account. Required if the template is used in a standalone deployment." + } + }, + "tables": { + "type": "array", + "items": { + "$ref": "#/definitions/tableType" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Array of Cassandra tables to deploy in the keyspace." + } + }, + "views": { + "type": "array", + "items": { + "$ref": "#/definitions/viewType" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Array of Cassandra views (materialized views) to deploy in the keyspace." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "defaultValue": 4000, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the keyspace. If not set, autoscale will be disabled. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput. Setting throughput at the keyspace level is only recommended for development/test or when workload across all tables in the shared throughput keyspace is uniform. For best performance for large production workloads, it is recommended to set dedicated throughput (autoscale or manual) at the table level." + } + } + }, + "resources": { + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "cassandraKeyspace": { + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(equals(parameters('throughput'), null()), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null()), 'throughput', parameters('throughput')))]", + "resource": { + "id": "[parameters('name')]" + } + }, + "dependsOn": [ + "databaseAccount" + ] + }, + "cassandraKeyspace_tables": { + "copy": { + "name": "cassandraKeyspace_tables", + "count": "[length(parameters('tables'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cassandradb-{1}', uniqueString(deployment().name, parameters('name')), parameters('tables')[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('tables')[copyIndex()].name]" + }, + "cassandraKeyspaceName": { + "value": "[parameters('name')]" + }, + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "schema": { + "value": "[parameters('tables')[copyIndex()].schema]" + }, + "analyticalStorageTtl": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'analyticalStorageTtl')]" + }, + "throughput": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'throughput')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + }, + "defaultTtl": { + "value": "[tryGet(parameters('tables')[copyIndex()], 'defaultTtl')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('tables')[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "15998065591386988132" + }, + "name": "DocumentDB Database Account Cassandra Keyspaces Tables", + "description": "This module deploys a Cassandra Table within a Cassandra Keyspace in a CosmosDB Account." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Cassandra table." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/tags" + }, + "description": "Optional. Tags of the Cassandra table resource." + }, + "nullable": true + }, + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "cassandraKeyspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cassandra Keyspace. Required if the template is used in a standalone deployment." + } + }, + "schema": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables@2024-11-15#properties/properties/properties/resource/properties/schema" + }, + "description": "Required. Schema definition for the Cassandra table." + } + }, + "analyticalStorageTtl": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Analytical TTL for the table. Default to 0 (disabled). Analytical store is enabled when set to a value other than 0. If set to -1, analytical store retains all historical data." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput. If not specified, the table will inherit throughput from the keyspace." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the table. Cannot be used with throughput. If not specified, the table will inherit throughput from the keyspace." + } + }, + "defaultTtl": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Default time to live in seconds. Default to 0 (disabled). If set to -1, items do not expire." + } + } + }, + "resources": { + "databaseAccount::cassandraKeyspace": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'))]" + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2024-11-15", + "name": "[parameters('databaseAccountName')]" + }, + "cassandraTable": { + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables", + "apiVersion": "2024-11-15", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "properties": { + "resource": { + "id": "[parameters('name')]", + "schema": "[parameters('schema')]", + "defaultTtl": "[parameters('defaultTtl')]", + "analyticalStorageTtl": "[parameters('analyticalStorageTtl')]" + }, + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(and(equals(parameters('throughput'), null()), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null()), 'throughput', parameters('throughput')))]" + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Cassandra table." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Cassandra table." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/tables', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Cassandra table was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "cassandraKeyspace" + ] + }, + "cassandraKeyspace_views": { + "copy": { + "name": "cassandraKeyspace_views", + "count": "[length(parameters('views'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-cassandraview-{1}', uniqueString(deployment().name, parameters('name')), parameters('views')[copyIndex()].name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('views')[copyIndex()].name]" + }, + "cassandraKeyspaceName": { + "value": "[parameters('name')]" + }, + "databaseAccountName": { + "value": "[parameters('databaseAccountName')]" + }, + "viewDefinition": { + "value": "[parameters('views')[copyIndex()].viewDefinition]" + }, + "throughput": { + "value": "[tryGet(parameters('views')[copyIndex()], 'throughput')]" + }, + "autoscaleSettingsMaxThroughput": { + "value": "[tryGet(parameters('views')[copyIndex()], 'autoscaleSettingsMaxThroughput')]" + }, + "tags": { + "value": "[coalesce(tryGet(parameters('views')[copyIndex()], 'tags'), parameters('tags'))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.40.2.10011", + "templateHash": "6617803098467821091" + }, + "name": "DocumentDB Database Account Cassandra Keyspaces Views", + "description": "This module deploys a Cassandra View (Materialized View) within a Cassandra Keyspace in a CosmosDB Account." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Cassandra view." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views@2025-05-01-preview#properties/tags" + }, + "description": "Optional. Tags of the Cassandra view resource." + }, + "nullable": true + }, + "databaseAccountName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Database Account. Required if the template is used in a standalone deployment." + } + }, + "cassandraKeyspaceName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Cassandra Keyspace. Required if the template is used in a standalone deployment." + } + }, + "viewDefinition": { + "type": "string", + "metadata": { + "description": "Required. View definition of the Cassandra view. This is the CQL statement that defines the materialized view." + } + }, + "throughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Request units per second. Cannot be used with autoscaleSettingsMaxThroughput." + } + }, + "autoscaleSettingsMaxThroughput": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. Maximum autoscale throughput for the view. Cannot be used with throughput." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + } + }, + "resources": { + "databaseAccount::cassandraKeyspace": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces", + "apiVersion": "2025-05-01-preview", + "name": "[format('{0}/{1}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'))]" + }, + "databaseAccount": { + "existing": true, + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-05-01-preview", + "name": "[parameters('databaseAccountName')]" + }, + "cassandraView": { + "type": "Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views", + "apiVersion": "2025-05-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]", + "tags": "[parameters('tags')]", + "location": "[parameters('location')]", + "properties": { + "resource": { + "id": "[parameters('name')]", + "viewDefinition": "[parameters('viewDefinition')]" + }, + "options": "[if(contains(reference('databaseAccount').capabilities, createObject('name', 'EnableServerless')), createObject(), createObject('autoscaleSettings', if(and(equals(parameters('throughput'), null()), not(equals(parameters('autoscaleSettingsMaxThroughput'), null()))), createObject('maxThroughput', parameters('autoscaleSettingsMaxThroughput')), null()), 'throughput', parameters('throughput')))]" + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Cassandra view." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Cassandra view." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces/views', parameters('databaseAccountName'), parameters('cassandraKeyspaceName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Cassandra view was created in." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "cassandraKeyspace" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Cassandra keyspace." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Cassandra keyspace." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts/cassandraKeyspaces', parameters('databaseAccountName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Cassandra keyspace 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": "2025-04-01", + "name": "[format('{0}-dbAccount-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", + "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", + "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'name'), format('pep-{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex()))]" + }, + "privateLinkServiceConnections": "[if(not(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true())), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service))))), createObject('value', null()))]", + "manualPrivateLinkServiceConnections": "[if(equals(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'isManualConnection'), true()), createObject('value', createArray(createObject('name', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateLinkServiceConnectionName'), format('{0}-{1}-{2}', last(split(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), '/')), coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service, copyIndex())), 'properties', createObject('privateLinkServiceId', resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name')), 'groupIds', createArray(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].service), 'requestMessage', coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'manualConnectionRequestMessage'), 'Manual approval required.'))))), createObject('value', null()))]", + "subnetResourceId": { + "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "location": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" + }, + "lock": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'lock'), parameters('lock'))]" + }, + "privateDnsZoneGroup": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'privateDnsZoneGroup')]" + }, + "roleAssignments": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'roleAssignments')]" + }, + "tags": { + "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'tags'), parameters('tags'))]" + }, + "customDnsConfigs": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customDnsConfigs')]" + }, + "ipConfigurations": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'ipConfigurations')]" + }, + "applicationSecurityGroupResourceIds": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'applicationSecurityGroupResourceIds')]" + }, + "customNetworkInterfaceName": { + "value": "[tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'customNetworkInterfaceName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" + }, + "name": "Private Endpoints", + "description": "This module deploys a Private Endpoint." + }, + "definitions": { + "privateDnsZoneGroupType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Private DNS Zone Group." + } + }, + "privateDnsZoneGroupConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "metadata": { + "description": "Required. The private DNS zone groups to associate the private endpoint. A DNS zone group can support up to 5 DNS zones." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a private dns zone group." + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "description": "The type of a private DNS zone group configuration.", + "__bicep_imported_from!": { + "sourceTemplate": "private-dns-zone-group/main.bicep" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the private endpoint resource to create." + } + }, + "subnetResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet where the endpoint needs to be created." + } + }, + "applicationSecurityGroupResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Application security groups in which the private endpoint IP configuration is included." + } + }, + "customNetworkInterfaceName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The custom name of the network interface attached to the private endpoint." + } + }, + "ipConfigurations": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, + "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." + }, + "nullable": true + }, + "privateDnsZoneGroup": { + "$ref": "#/definitions/privateDnsZoneGroupType", + "nullable": true, + "metadata": { + "description": "Optional. The private DNS zone group to configure for the private endpoint." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/roleAssignmentType" + }, + "nullable": true, + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, + "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." + }, + "nullable": true + }, + "customDnsConfigs": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, + "description": "Optional. Custom DNS configurations." + }, + "nullable": true + }, + "manualPrivateLinkServiceConnections": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, + "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." + }, + "nullable": true + }, + "privateLinkServiceConnections": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, + "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "DNS Resolver Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0f2ebee7-ffd4-4fc0-b3b7-664099fdad5d')]", + "DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'befefa01-2a29-4197-83a8-272ff33ce314')]", + "Domain Services Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'eeaeda52-9324-47f6-8069-5d5bade478b2')]", + "Domain Services Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '361898ef-9ed1-48c2-849c-a832951106bb')]", + "Network Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Private DNS Zone Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]" + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "privateEndpoint": { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-10-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "copy": [ + { + "name": "applicationSecurityGroups", + "count": "[length(coalesce(parameters('applicationSecurityGroupResourceIds'), createArray()))]", + "input": { + "id": "[coalesce(parameters('applicationSecurityGroupResourceIds'), createArray())[copyIndex('applicationSecurityGroups')]]" + } + } + ], + "customDnsConfigs": "[coalesce(parameters('customDnsConfigs'), createArray())]", + "customNetworkInterfaceName": "[coalesce(parameters('customNetworkInterfaceName'), '')]", + "ipConfigurations": "[coalesce(parameters('ipConfigurations'), createArray())]", + "manualPrivateLinkServiceConnections": "[coalesce(parameters('manualPrivateLinkServiceConnections'), createArray())]", + "privateLinkServiceConnections": "[coalesce(parameters('privateLinkServiceConnections'), createArray())]", + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + } + }, + "privateEndpoint_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_roleAssignments": { + "copy": { + "name": "privateEndpoint_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Network/privateEndpoints/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Network/privateEndpoints', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "privateEndpoint" + ] + }, + "privateEndpoint_privateDnsZoneGroup": { + "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[tryGet(parameters('privateDnsZoneGroup'), 'name')]" + }, + "privateEndpointName": { + "value": "[parameters('name')]" + }, + "privateDnsZoneConfigs": { + "value": "[parameters('privateDnsZoneGroup').privateDnsZoneGroupConfigs]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.38.5.1644", + "templateHash": "24141742673128945" + }, + "name": "Private Endpoint Private DNS Zone Groups", + "description": "This module deploys a Private Endpoint Private DNS Zone Group." + }, + "definitions": { + "privateDnsZoneGroupConfigType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the private DNS zone group config." + } + }, + "privateDnsZoneResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the private DNS zone." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." + } + } + }, + "parameters": { + "privateEndpointName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent private endpoint. Required if the template is used in a standalone deployment." + } + }, + "privateDnsZoneConfigs": { + "type": "array", + "items": { + "$ref": "#/definitions/privateDnsZoneGroupConfigType" + }, + "minLength": 1, + "maxLength": 5, + "metadata": { + "description": "Required. Array of private DNS zone configurations of the private DNS zone group. A DNS zone group can support up to 5 DNS zones." + } + }, + "name": { + "type": "string", + "defaultValue": "default", + "metadata": { + "description": "Optional. The name of the private DNS zone group." + } + } + }, + "resources": { + "privateEndpoint": { + "existing": true, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-10-01", + "name": "[parameters('privateEndpointName')]" + }, + "privateDnsZoneGroup": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", + "properties": { + "copy": [ + { + "name": "privateDnsZoneConfigs", + "count": "[length(parameters('privateDnsZoneConfigs'))]", + "input": { + "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId, '/')))]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigs')].privateDnsZoneResourceId]" + } + } + } + ] + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint DNS zone group." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint DNS zone group." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', parameters('privateEndpointName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint DNS zone group was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "privateEndpoint" + ] + } + }, + "outputs": { + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the private endpoint was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the private endpoint." + }, + "value": "[resourceId('Microsoft.Network/privateEndpoints', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "The name of the private endpoint." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" + }, + "customDnsConfigs": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, + "description": "The custom DNS configurations of the private endpoint." + }, + "value": "[reference('privateEndpoint').customDnsConfigs]" + }, + "networkInterfaceResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "The resource IDs of the network interfaces associated with the private endpoint." + }, + "value": "[map(reference('privateEndpoint').networkInterfaces, lambda('nic', lambdaVariables('nic').id))]" + }, + "groupId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The group Id for the private endpoint Group." + }, + "value": "[coalesce(tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'manualPrivateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0), tryGet(tryGet(tryGet(tryGet(reference('privateEndpoint'), 'privateLinkServiceConnections'), 0, 'properties'), 'groupIds'), 0))]" + } + } + } + }, + "dependsOn": [ + "databaseAccount" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the database account." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the database account." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the database account was created in." + }, + "value": "[resourceGroup().name]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('databaseAccount', '2025-04-15', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('databaseAccount', '2025-04-15', 'full').location]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "The endpoint of the database account." + }, + "value": "[reference('databaseAccount').documentEndpoint]" + }, + "privateEndpoints": { + "type": "array", + "items": { + "$ref": "#/definitions/privateEndpointOutputType" + }, + "metadata": { + "description": "The private endpoints of the database account." + }, + "copy": { + "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]", + "input": { + "name": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.name.value]", + "resourceId": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.resourceId.value]", + "groupId": "[tryGet(tryGet(reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs, 'groupId'), 'value')]", + "customDnsConfigs": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.customDnsConfigs.value]", + "networkInterfaceResourceIds": "[reference(format('databaseAccount_privateEndpoints[{0}]', copyIndex())).outputs.networkInterfaceResourceIds.value]" + } + } + }, + "primaryReadWriteKey": { + "type": "securestring", + "metadata": { + "description": "The primary read-write key." + }, + "value": "[listKeys('databaseAccount', '2025-04-15').primaryMasterKey]" + }, + "primaryReadOnlyKey": { + "type": "securestring", + "metadata": { + "description": "The primary read-only key." + }, + "value": "[listKeys('databaseAccount', '2025-04-15').primaryReadonlyMasterKey]" + }, + "primaryReadWriteConnectionString": { + "type": "securestring", + "metadata": { + "description": "The primary read-write connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[0].connectionString]" + }, + "primaryReadOnlyConnectionString": { + "type": "securestring", + "metadata": { + "description": "The primary read-only connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[2].connectionString]" + }, + "secondaryReadWriteKey": { + "type": "securestring", + "metadata": { + "description": "The secondary read-write key." + }, + "value": "[listKeys('databaseAccount', '2025-04-15').secondaryMasterKey]" + }, + "secondaryReadOnlyKey": { + "type": "securestring", + "metadata": { + "description": "The secondary read-only key." + }, + "value": "[listKeys('databaseAccount', '2025-04-15').secondaryReadonlyMasterKey]" + }, + "secondaryReadWriteConnectionString": { + "type": "securestring", + "metadata": { + "description": "The secondary read-write connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[1].connectionString]" + }, + "secondaryReadOnlyConnectionString": { + "type": "securestring", + "metadata": { + "description": "The secondary read-only connection string." + }, + "value": "[listConnectionStrings('databaseAccount', '2025-04-15').connectionStrings[3].connectionString]" + } + } + } + } + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Cosmos DB account." + }, + "value": "[reference('cosmosAccount').outputs.resourceId.value]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the Cosmos DB account." + }, + "value": "[reference('cosmosAccount').outputs.name.value]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "Endpoint of the Cosmos DB account." + }, + "value": "[format('https://{0}.documents.azure.com:443/', parameters('name'))]" + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Database name." + }, + "value": "[parameters('databaseName')]" + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Container name (first container)." + }, + "value": "[parameters('containers')[0].name]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.container-app-env.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "logAnalyticsWorkspaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference(resourceId('Microsoft.Resources/deployments', take(format('module.log-analytics.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value))]", + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "15830423550310603762" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name used for naming convention." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('cae-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Name of the Container Apps Environment." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Log Analytics workspace." + } + }, + "infrastructureSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Subnet resource ID for VNet integration (optional)." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable zone redundancy." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable Azure telemetry collection." + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.app.managedenvironment.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "appLogsConfiguration": { + "value": { + "destination": "log-analytics", + "logAnalyticsWorkspaceResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + } + }, + "infrastructureSubnetResourceId": "[if(not(empty(parameters('infrastructureSubnetId'))), createObject('value', parameters('infrastructureSubnetId')), createObject('value', null()))]", + "zoneRedundant": { + "value": "[parameters('zoneRedundant')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "7926712656612510784" + }, + "name": "App ManagedEnvironments", + "description": "This module deploys an App Managed Environment (also known as a Container App Environment)." + }, + "definitions": { + "certificateType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the certificate." + } + }, + "certificateType": { + "type": "string", + "allowedValues": [ + "ImagePullTrustedCA", + "ServerSSLCertificate" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of the certificate." + } + }, + "certificateValue": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The value of the certificate. PFX or PEM blob." + } + }, + "certificatePassword": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The password of the certificate." + } + }, + "certificateKeyVaultProperties": { + "$ref": "#/definitions/certificateKeyVaultPropertiesType", + "nullable": true, + "metadata": { + "description": "Optional. A key vault reference." + } + }, + "location": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The location for the resource." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments/certificates@2025-10-02-preview#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a certificate." + } + }, + "storageType": { + "type": "object", + "properties": { + "accessMode": { + "type": "string", + "allowedValues": [ + "ReadOnly", + "ReadWrite" + ], + "metadata": { + "description": "Required. Access mode for storage: \"ReadOnly\" or \"ReadWrite\"." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "NFS", + "SMB" + ], + "metadata": { + "description": "Required. Type of storage: \"SMB\" or \"NFS\"." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Storage account name." + } + }, + "name": { + "type": "string", + "metadata": { + "description": "Required. File share name." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type of the storage." + } + }, + "appLogsConfigurationType": { + "type": "object", + "discriminator": { + "propertyName": "destination", + "mapping": { + "azure-monitor": { + "$ref": "#/definitions/appLogsConfigurationMonitorType" + }, + "log-analytics": { + "$ref": "#/definitions/appLogsConfigurationLawType" + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the App Logs Configuration." + } + }, + "appLogsConfigurationMonitorType": { + "type": "object", + "properties": { + "destination": { + "type": "string", + "allowedValues": [ + "azure-monitor" + ], + "metadata": { + "description": "Required. The destination of the logs." + } + } + }, + "metadata": { + "description": "The type for the App Logs Configuration if using azure-monitor." + } + }, + "appLogsConfigurationLawType": { + "type": "object", + "properties": { + "destination": { + "type": "string", + "allowedValues": [ + "log-analytics" + ], + "metadata": { + "description": "Required. The destination of the logs." + } + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "metadata": { + "description": "Required. Existing Log Analytics Workspace resource ID." + } + } + }, + "metadata": { + "description": "The type for the App Logs Configuration if using log-analytics." + } + }, + "certificateKeyVaultPropertiesType": { + "type": "object", + "properties": { + "identityResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the identity. This is the identity that will be used to access the key vault." + } + }, + "keyVaultUrl": { + "type": "string", + "metadata": { + "description": "Required. A key vault URL referencing the wildcard certificate that will be used for the custom domain." + } + } + }, + "metadata": { + "description": "The type for the certificate's key vault properties.", + "__bicep_imported_from!": { + "sourceTemplate": "certificate/main.bicep" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + } + } + }, + "managedIdentityAllType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a managed identity configuration. To be used if both a system-assigned & user-assigned identities are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + } + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Container Apps Managed Environment." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-10-02-preview#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "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." + } + }, + "appInsightsConnectionString": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Application Insights connection string." + } + }, + "daprConfiguration": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-10-02-preview#properties/properties/properties/daprConfiguration" + }, + "description": "Optional. The configuration of Dapr component." + }, + "nullable": true + }, + "ingressConfiguration": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-10-02-preview#properties/properties/properties/ingressConfiguration" + }, + "description": "Optional. Ingress configuration for the Managed Environment." + }, + "nullable": true + }, + "kedaConfiguration": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-10-02-preview#properties/properties/properties/kedaConfiguration" + }, + "description": "Optional. The configuration of Keda component." + }, + "nullable": true + }, + "peerAuthentication": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-10-02-preview#properties/properties/properties/peerAuthentication" + }, + "description": "Optional. Peer authentication settings for the Managed Environment." + }, + "nullable": true + }, + "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." + } + }, + "infrastructureSubnetResourceId": { + "type": "string", + "nullable": true, + "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 \"infrastructureSubnetResourceId\" 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." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Disabled", + "allowedValues": [ + "Enabled", + "Disabled" + ], + "metadata": { + "description": "Optional. Whether to allow or block all public 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", + "nullable": true, + "metadata": { + "description": "Optional. Certificate to use for the custom domain. PFX or PEM." + } + }, + "dnsSuffix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. DNS suffix for the environment domain." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "nullable": true, + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "openTelemetryConfiguration": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-02-02-preview#properties/properties/properties/openTelemetryConfiguration" + }, + "description": "Optional. Open Telemetry configuration." + }, + "nullable": true + }, + "workloadProfiles": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments@2025-10-02-preview#properties/properties/properties/workloadProfiles" + }, + "description": "Conditional. Workload profiles configured for the Managed Environment. Required if zoneRedundant is set to true to make the resource WAF compliant." + }, + "nullable": true + }, + "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": { + "type": "array", + "items": { + "$ref": "#/definitions/storageType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The list of storages to mount on the environment." + } + }, + "certificate": { + "$ref": "#/definitions/certificateType", + "nullable": true, + "metadata": { + "description": "Optional. A Managed Environment Certificate." + } + }, + "appLogsConfiguration": { + "$ref": "#/definitions/appLogsConfigurationType", + "nullable": true, + "metadata": { + "description": "Optional. The AppLogsConfiguration for the Managed 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(variables('formattedUserAssignedIdentities'))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(variables('formattedUserAssignedIdentities'))), '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')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-11-01", + "name": "[format('46d3xbcp.res.app-managedenvironment.{0}.{1}', replace('0.13.3', '.', '-'), 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(tryGet(parameters('appLogsConfiguration'), 'logAnalyticsWorkspaceResourceId')))]", + "existing": true, + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2025-02-01", + "subscriptionId": "[split(tryGet(parameters('appLogsConfiguration'), 'logAnalyticsWorkspaceResourceId'), '/')[2]]", + "resourceGroup": "[split(tryGet(parameters('appLogsConfiguration'), 'logAnalyticsWorkspaceResourceId'), '/')[4]]", + "name": "[last(split(tryGet(parameters('appLogsConfiguration'), 'logAnalyticsWorkspaceResourceId'), '/'))]" + }, + "managedEnvironment": { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2025-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "appInsightsConfiguration": { + "connectionString": "[parameters('appInsightsConnectionString')]" + }, + "daprConfiguration": "[parameters('daprConfiguration')]", + "ingressConfiguration": "[parameters('ingressConfiguration')]", + "kedaConfiguration": "[parameters('kedaConfiguration')]", + "peerAuthentication": "[parameters('peerAuthentication')]", + "appLogsConfiguration": "[if(not(empty(parameters('appLogsConfiguration'))), shallowMerge(createArray(createObject('destination', parameters('appLogsConfiguration').destination), if(not(empty(tryGet(parameters('appLogsConfiguration'), 'logAnalyticsWorkspaceResourceId'))), createObject('logAnalyticsConfiguration', createObject('customerId', reference('logAnalyticsWorkspace').customerId, 'sharedKey', listKeys('logAnalyticsWorkspace', '2025-02-01').primarySharedKey)), createObject()))), null())]", + "daprAIConnectionString": "[parameters('daprAIConnectionString')]", + "daprAIInstrumentationKey": "[parameters('daprAIInstrumentationKey')]", + "customDomainConfiguration": { + "certificatePassword": "[parameters('certificatePassword')]", + "certificateValue": "[parameters('certificateValue')]", + "dnsSuffix": "[parameters('dnsSuffix')]", + "certificateKeyVaultProperties": "[if(not(empty(tryGet(parameters('certificate'), 'certificateKeyVaultProperties'))), createObject('identity', tryGet(parameters('certificate'), 'certificateKeyVaultProperties', 'identityResourceId'), 'keyVaultUrl', tryGet(parameters('certificate'), 'certificateKeyVaultProperties', 'keyVaultUrl')), null())]" + }, + "openTelemetryConfiguration": "[parameters('openTelemetryConfiguration')]", + "peerTrafficConfiguration": { + "encryption": { + "enabled": "[parameters('peerTrafficEncryption')]" + } + }, + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "vnetConfiguration": { + "internal": "[parameters('internal')]", + "infrastructureSubnetId": "[parameters('infrastructureSubnetResourceId')]", + "dockerBridgeCidr": "[if(not(empty(parameters('infrastructureSubnetResourceId'))), parameters('dockerBridgeCidr'), null())]", + "platformReservedCidr": "[if(and(empty(parameters('workloadProfiles')), not(empty(parameters('infrastructureSubnetResourceId')))), parameters('platformReservedCidr'), null())]", + "platformReservedDnsIP": "[if(and(empty(parameters('workloadProfiles')), not(empty(parameters('infrastructureSubnetResourceId')))), parameters('platformReservedDnsIP'), null())]" + }, + "workloadProfiles": "[parameters('workloadProfiles')]", + "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": "[resourceId('Microsoft.App/managedEnvironments', 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": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "managedEnvironment" + ] + }, + "managedEnvironment_storage": { + "copy": { + "name": "managedEnvironment_storage", + "count": "[length(coalesce(parameters('storages'), createArray()))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Managed-Environment-Storage-{1}', uniqueString(deployment().name), copyIndex())]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(parameters('storages'), createArray())[copyIndex()].name]" + }, + "managedEnvironmentName": { + "value": "[parameters('name')]" + }, + "kind": { + "value": "[coalesce(parameters('storages'), createArray())[copyIndex()].kind]" + }, + "accessMode": { + "value": "[coalesce(parameters('storages'), createArray())[copyIndex()].accessMode]" + }, + "storageAccountName": { + "value": "[coalesce(parameters('storages'), createArray())[copyIndex()].storageAccountName]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "10288949461861140115" + }, + "name": "App ManagedEnvironments Certificates", + "description": "This module deploys a App Managed Environment Certificate." + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the file share." + } + }, + "managedEnvironmentName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent app managed environment. Required if the template is used in a standalone deployment." + } + }, + "accessMode": { + "type": "string", + "metadata": { + "description": "Required. The access mode for the storage." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "NFS", + "SMB" + ], + "metadata": { + "description": "Required. Type of storage: \"SMB\" or \"NFS\"." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Storage account name." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-managedenvironment-storage.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "managedEnvironment": { + "existing": true, + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2025-10-02-preview", + "name": "[parameters('managedEnvironmentName')]" + }, + "storage": { + "type": "Microsoft.App/managedEnvironments/storages", + "apiVersion": "2025-10-02-preview", + "name": "[format('{0}/{1}', parameters('managedEnvironmentName'), parameters('name'))]", + "properties": { + "nfsAzureFile": "[if(equals(parameters('kind'), 'NFS'), createObject('accessMode', parameters('accessMode'), 'server', format('{0}.file.{1}', parameters('storageAccountName'), environment().suffixes.storage), 'shareName', format('/{0}/{1}', parameters('storageAccountName'), parameters('name'))), null())]", + "azureFile": "[if(equals(parameters('kind'), 'SMB'), createObject('accessMode', parameters('accessMode'), 'accountName', parameters('storageAccountName'), 'accountKey', listkeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2025-01-01').keys[0].value, 'shareName', parameters('name')), null())]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the file share." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the file share." + }, + "value": "[resourceId('Microsoft.App/managedEnvironments/storages', parameters('managedEnvironmentName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the file share was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "dependsOn": [ + "managedEnvironment" + ] + }, + "managedEnvironment_certificate": { + "condition": "[not(empty(parameters('certificate')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-Managed-Environment-Certificate', uniqueString(deployment().name))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[coalesce(tryGet(parameters('certificate'), 'name'), format('cert-{0}', parameters('name')))]" + }, + "managedEnvironmentName": { + "value": "[parameters('name')]" + }, + "certificateKeyVaultProperties": { + "value": "[tryGet(parameters('certificate'), 'certificateKeyVaultProperties')]" + }, + "certificateType": { + "value": "[tryGet(parameters('certificate'), 'certificateType')]" + }, + "certificateValue": { + "value": "[tryGet(parameters('certificate'), 'certificateValue')]" + }, + "certificatePassword": { + "value": "[tryGet(parameters('certificate'), 'certificatePassword')]" + }, + "location": { + "value": "[tryGet(parameters('certificate'), 'location')]" + }, + "tags": { + "value": "[tryGet(parameters('certificate'), 'tags')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "13909378080866770643" + }, + "name": "App ManagedEnvironments Certificates", + "description": "This module deploys a App Managed Environment Certificate." + }, + "definitions": { + "certificateKeyVaultPropertiesType": { + "type": "object", + "properties": { + "identityResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource ID of the identity. This is the identity that will be used to access the key vault." + } + }, + "keyVaultUrl": { + "type": "string", + "metadata": { + "description": "Required. A key vault URL referencing the wildcard certificate that will be used for the custom domain." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the certificate's key vault properties." + } + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the Container Apps Managed Environment Certificate." + } + }, + "managedEnvironmentName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent app managed environment. Required if the template is used in a standalone deployment." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all Resources." + } + }, + "certificateKeyVaultProperties": { + "$ref": "#/definitions/certificateKeyVaultPropertiesType", + "nullable": true, + "metadata": { + "description": "Optional. A key vault reference to the certificate to use for the custom domain." + } + }, + "certificateType": { + "type": "string", + "nullable": true, + "allowedValues": [ + "ServerSSLCertificate", + "ImagePullTrustedCA" + ], + "metadata": { + "description": "Optional. The type of the certificate." + } + }, + "certificateValue": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The value of the certificate. PFX or PEM blob." + } + }, + "certificatePassword": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Optional. The password of the certificate." + } + }, + "tags": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/managedEnvironments/certificates@2025-10-02-preview#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-managedenvironment-certificate.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "managedEnvironment": { + "existing": true, + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2025-10-02-preview", + "name": "[parameters('managedEnvironmentName')]" + }, + "managedEnvironmentCertificate": { + "type": "Microsoft.App/managedEnvironments/certificates", + "apiVersion": "2025-10-02-preview", + "name": "[format('{0}/{1}', parameters('managedEnvironmentName'), parameters('name'))]", + "location": "[parameters('location')]", + "properties": { + "certificateKeyVaultProperties": "[if(not(empty(parameters('certificateKeyVaultProperties'))), createObject('identity', parameters('certificateKeyVaultProperties').identityResourceId, 'keyVaultUrl', parameters('certificateKeyVaultProperties').keyVaultUrl), null())]", + "certificateType": "[parameters('certificateType')]", + "password": "[parameters('certificatePassword')]", + "value": "[parameters('certificateValue')]" + }, + "tags": "[parameters('tags')]" + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the key values." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the key values." + }, + "value": "[resourceId('Microsoft.App/managedEnvironments/certificates', parameters('managedEnvironmentName'), parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group the certificate was deployed into." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "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', '2025-10-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', '2025-10-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]" + } + } + } + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Container Apps Environment." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.app.managedenvironment.{0}', parameters('name')), 64)), '2025-04-01').outputs.name.value]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Container Apps Environment." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.app.managedenvironment.{0}', parameters('name')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "defaultDomain": { + "type": "string", + "metadata": { + "description": "The default domain of the Container Apps Environment." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.app.managedenvironment.{0}', parameters('name')), 64)), '2025-04-01').outputs.defaultDomain.value]" + }, + "staticIp": { + "type": "string", + "metadata": { + "description": "The static IP of the Container Apps Environment." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('avm.res.app.managedenvironment.{0}', parameters('name')), 64)), '2025-04-01').outputs.staticIp.value]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', take(format('module.log-analytics.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('backendContainerAppName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "environmentResourceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "ingressExternal": { + "value": true + }, + "ingressTargetPort": { + "value": 80 + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "containers": { + "value": [ + { + "name": "backend-api", + "image": "[format('{0}/backend-api:{1}', parameters('containerRegistryEndpoint'), parameters('imageTag'))]", + "env": [ + { + "name": "AZURE_OPENAI_ENDPOINT", + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiFoundryEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value)]" + }, + { + "name": "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", + "value": "[parameters('gptModelName')]" + }, + { + "name": "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME", + "value": "[parameters('embeddingModel')]" + }, + { + "name": "AZURE_OPENAI_API_VERSION", + "value": "2025-03-01-preview" + }, + { + "name": "COSMOS_DB_ACCOUNT_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value]" + }, + { + "name": "COSMOS_DB_DATABASE_NAME", + "value": "[variables('cosmosDatabaseName')]" + }, + { + "name": "COSMOS_DB_CONTAINER_NAME", + "value": "agent_telemetry" + }, + { + "name": "COSMOS_DB_CONTROL_CONTAINER_NAME", + "value": "processcontrol" + }, + { + "name": "COSMOS_DB_PROCESS_CONTAINER", + "value": "processes" + }, + { + "name": "COSMOS_DB_PROCESS_LOG_CONTAINER", + "value": "agent_telemetry" + }, + { + "name": "STORAGE_ACCOUNT_BLOB_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.blobEndpoint.value]" + }, + { + "name": "STORAGE_ACCOUNT_NAME", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_CONTAINER", + "value": "[variables('processBlobContainerName')]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_QUEUE", + "value": "[variables('processQueueName')]" + }, + { + "name": "STORAGE_ACCOUNT_QUEUE_URL", + "value": "[format('{0}', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.serviceEndpoints.value.queue)]" + }, + { + "name": "GLOBAL_LLM_SERVICE", + "value": "AzureOpenAI" + }, + { + "name": "PROCESSOR_CONTROL_URL", + "value": "[format('https://{0}.internal.{1}', variables('processorContainerAppName'), reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.defaultDomain.value)]" + }, + { + "name": "APP_ENV", + "value": "Prod" + } + ], + "resources": { + "cpu": "[json('1')]", + "memory": "2.0Gi" + } + } + ] + }, + "corsPolicy": { + "value": { + "allowedOrigins": [ + "*" + ], + "allowedMethods": [ + "GET", + "POST", + "PUT", + "DELETE", + "OPTIONS" + ], + "allowedHeaders": [ + "Authorization", + "Content-Type", + "*" + ] + } + }, + "scaleSettings": { + "value": { + "maxReplicas": 1, + "minReplicas": 1 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "11175324594317515373" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "environmentResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Container Apps Environment." + } + }, + "containers": { + "type": "array", + "metadata": { + "description": "Container definitions." + } + }, + "ingressExternal": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "ingressTargetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for ingress." + } + }, + "ingressTransport": { + "type": "string", + "defaultValue": "auto", + "allowedValues": [ + "auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Ingress transport protocol." + } + }, + "ingressAllowInsecure": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to allow insecure ingress connections." + } + }, + "disableIngress": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Disable ingress entirely (for background workers)." + } + }, + "registries": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Container registry configurations." + } + }, + "secrets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Secret definitions." + } + }, + "managedIdentities": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Managed identity configuration." + } + }, + "corsPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "CORS policy configuration." + } + }, + "activeRevisionsMode": { + "type": "string", + "defaultValue": "Single", + "allowedValues": [ + "Single", + "Multiple" + ], + "metadata": { + "description": "Active revision mode." + } + }, + "scaleSettings": { + "type": "object", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 0 + }, + "metadata": { + "description": "Scale settings (maxReplicas, minReplicas, rules, cooldownPeriod, pollingInterval)." + } + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Workload profile name." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable Azure telemetry collection." + } + } + }, + "resources": { + "containerApp": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.app.containerapp.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "environmentResourceId": { + "value": "[parameters('environmentResourceId')]" + }, + "containers": { + "value": "[parameters('containers')]" + }, + "ingressExternal": "[if(parameters('disableIngress'), createObject('value', false()), createObject('value', parameters('ingressExternal')))]", + "ingressTargetPort": { + "value": "[parameters('ingressTargetPort')]" + }, + "ingressTransport": { + "value": "[parameters('ingressTransport')]" + }, + "ingressAllowInsecure": { + "value": "[parameters('ingressAllowInsecure')]" + }, + "disableIngress": { + "value": "[parameters('disableIngress')]" + }, + "registries": { + "value": "[parameters('registries')]" + }, + "secrets": { + "value": "[parameters('secrets')]" + }, + "managedIdentities": "[if(not(empty(parameters('managedIdentities'))), createObject('value', parameters('managedIdentities')), createObject('value', createObject()))]", + "corsPolicy": "[if(not(empty(parameters('corsPolicy'))), createObject('value', parameters('corsPolicy')), createObject('value', null()))]", + "activeRevisionsMode": { + "value": "[parameters('activeRevisionsMode')]" + }, + "scaleSettings": { + "value": "[parameters('scaleSettings')]" + }, + "workloadProfileName": { + "value": "[parameters('workloadProfileName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.42.1.51946", + "templateHash": "12626366001403616495" + }, + "name": "Container Apps", + "description": "This module deploys a Container App." + }, + "definitions": { + "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." + } + }, + "scaleType": { + "type": "object", + "properties": { + "maxReplicas": { + "type": "int", + "metadata": { + "description": "Required. The maximum number of replicas." + } + }, + "minReplicas": { + "type": "int", + "metadata": { + "description": "Required. The minimum number of replicas." + } + }, + "cooldownPeriod": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The cooldown period in seconds." + } + }, + "pollingInterval": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The polling interval in seconds." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/scaleRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The scaling rules." + } + } + }, + "metadata": { + "description": "The scale settings for the Container App." + } + }, + "scaleRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the scaling rule." + } + }, + "custom": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The custom scaling rule." + } + }, + "azureQueue": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The Azure Queue based scaling rule." + } + }, + "http": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The HTTP requests based scaling rule." + } + }, + "tcp": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The TCP based scaling rule." + } + } + }, + "metadata": { + "description": "The scaling rules for the Container App." + } + }, + "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." + } + }, + "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. The URL of the Azure Key Vault secret referenced by the Container App. Required if `value` is null." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the container app secret." + } + }, + "value": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Conditional. The container app secret value, if not fetched from the Key Vault. Required if `keyVaultUrl` is not null." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a secret." + } + }, + "authConfigType": { + "type": "object", + "properties": { + "encryptionSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/encryptionSettings" + }, + "description": "Optional. The configuration settings of the secrets references of encryption key and signing key for ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "globalValidation": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/globalValidation" + }, + "description": "Optional. The configuration settings that determines the validation flow of users using Service Authentication and/or Authorization." + }, + "nullable": true + }, + "httpSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/httpSettings" + }, + "description": "Optional. The configuration settings of the HTTP requests for authentication and authorization requests made against ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "identityProviders": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/identityProviders" + }, + "description": "Optional. The configuration settings of each of the identity providers used to configure ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "login": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/login" + }, + "description": "Optional. The configuration settings of the login flow of users using ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "platform": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/platform" + }, + "description": "Optional. The configuration settings of the platform of ContainerApp Service Authentication/Authorization." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the container app's authentication configuration." + } + }, + "diagnosticSettingMetricsOnlyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.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.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.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." + } + }, + "kind": { + "type": "string", + "defaultValue": "containerapps", + "allowedValues": [ + "containerapps", + "workflowapp", + "functionapp" + ], + "metadata": { + "description": "Optional. Metadata used to render different experiences for resources of the same type." + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/service" + }, + "description": "Optional. Dev ContainerApp service type." + }, + "nullable": true + }, + "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." + } + }, + "scaleSettings": { + "$ref": "#/definitions/scaleType", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 3 + }, + "metadata": { + "description": "Optional. The scaling settings of the service." + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "registries": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/registries" + }, + "description": "Optional. Collection of private container registry credentials for containers used by the Container app." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/customDomains" + }, + "description": "Optional. Custom domain bindings for Container App hostnames." + }, + "nullable": true + }, + "exposedPort": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Exposed Port in containers for TCP traffic from ingress." + } + }, + "ipSecurityRestrictions": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/ipSecurityRestrictions" + }, + "description": "Optional. Rules to restrict incoming IP address." + }, + "nullable": true + }, + "traffic": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/traffic" + }, + "description": "Optional. Traffic weight configuration for routing traffic across revisions. Each entry specifies a revision (or latest) and its traffic percentage. Supports blue-green and canary deployment patterns." + }, + "nullable": true + }, + "dapr": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/dapr" + }, + "description": "Optional. Dapr configuration for the Container App." + }, + "nullable": true + }, + "identitySettings": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/identitySettings" + }, + "description": "Optional. Settings for Managed Identities that are assigned to the Container App. If a Managed Identity is not specified here, default settings will be used." + }, + "nullable": true + }, + "maxInactiveRevisions": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Max inactive revisions a Container App can have." + } + }, + "runtime": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/runtime" + }, + "description": "Optional. Runtime configuration for the Container App." + }, + "nullable": true + }, + "containers": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/containers" + }, + "description": "Required. List of container definitions for the Container App." + } + }, + "terminationGracePeriodSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The termination grace period for the container app." + } + }, + "initContainersTemplate": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/initContainers" + }, + "description": "Optional. List of specialized containers that run before app containers." + }, + "nullable": true + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/secretType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The secrets of the Container App." + } + }, + "revisionSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User friendly suffix that is appended to the revision name." + } + }, + "volumes": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/volumes" + }, + "description": "Optional. List of volume definitions for the Container App." + }, + "nullable": true + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Workload profile name to pin for container app execution." + } + }, + "authConfig": { + "$ref": "#/definitions/authConfigType", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Container App Auth configs." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingMetricsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "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(variables('formattedUserAssignedIdentities'))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(variables('formattedUserAssignedIdentities'))), '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')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-containerapp.{0}.{1}', replace('0.22.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" + } + } + } + } + }, + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2026-01-01", + "name": "[parameters('name')]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "properties": { + "environmentId": "[parameters('environmentResourceId')]", + "workloadProfileName": "[parameters('workloadProfileName')]", + "template": { + "containers": "[parameters('containers')]", + "terminationGracePeriodSeconds": "[parameters('terminationGracePeriodSeconds')]", + "initContainers": "[parameters('initContainersTemplate')]", + "revisionSuffix": "[parameters('revisionSuffix')]", + "scale": "[parameters('scaleSettings')]", + "serviceBinds": "[if(parameters('includeAddOns'), parameters('serviceBinds'), null())]", + "volumes": "[parameters('volumes')]" + }, + "configuration": { + "activeRevisionsMode": "[parameters('activeRevisionsMode')]", + "dapr": "[parameters('dapr')]", + "identitySettings": "[parameters('identitySettings')]", + "ingress": "[if(parameters('disableIngress'), null(), createObject('additionalPortMappings', parameters('additionalPortMappings'), 'allowInsecure', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('ingressAllowInsecure'), false()), 'customDomains', parameters('customDomains'), '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', parameters('ipSecurityRestrictions'), 'targetPort', parameters('ingressTargetPort'), 'stickySessions', createObject('affinity', parameters('stickySessionsAffinity')), 'traffic', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('traffic'), null()), 'transport', parameters('ingressTransport')))]", + "service": "[if(parameters('includeAddOns'), parameters('service'), null())]", + "maxInactiveRevisions": "[parameters('maxInactiveRevisions')]", + "registries": "[parameters('registries')]", + "secrets": "[parameters('secrets')]", + "runtime": "[parameters('runtime')]" + } + } + }, + "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": "[resourceId('Microsoft.App/containerApps', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "containerApp" + ] + }, + "containerApp_roleAssignments": { + "copy": { + "name": "containerApp_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.App/containerApps', 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" + ] + }, + "containerApp_diagnosticSettings": { + "copy": { + "name": "containerApp_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.App/containerApps', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "containerApp" + ] + }, + "containerAppAuthConfigs": { + "condition": "[not(empty(parameters('authConfig')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-auth-config', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerAppName": { + "value": "[parameters('name')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "encryptionSettings": { + "value": "[tryGet(parameters('authConfig'), 'encryptionSettings')]" + }, + "globalValidation": { + "value": "[tryGet(parameters('authConfig'), 'globalValidation')]" + }, + "httpSettings": { + "value": "[tryGet(parameters('authConfig'), 'httpSettings')]" + }, + "identityProviders": { + "value": "[tryGet(parameters('authConfig'), 'identityProviders')]" + }, + "login": { + "value": "[tryGet(parameters('authConfig'), 'login')]" + }, + "platform": { + "value": "[tryGet(parameters('authConfig'), 'platform')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.42.1.51946", + "templateHash": "4649255393182983719" + }, + "name": "Container App Auth Configs", + "description": "This module deploys Container App Auth Configs." + }, + "parameters": { + "containerAppName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Container App. Required if the template is used in a standalone deployment." + } + }, + "encryptionSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/encryptionSettings" + }, + "description": "Optional. The configuration settings of the secrets references of encryption key and signing key for ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "globalValidation": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/globalValidation" + }, + "description": "Optional. The configuration settings that determines the validation flow of users using Service Authentication and/or Authorization." + }, + "nullable": true + }, + "httpSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/httpSettings" + }, + "description": "Optional. The configuration settings of the HTTP requests for authentication and authorization requests made against ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "identityProviders": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/identityProviders" + }, + "description": "Optional. The configuration settings of each of the identity providers used to configure ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "login": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/login" + }, + "description": "Optional. The configuration settings of the login flow of users using ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "platform": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/platform" + }, + "description": "Optional. The configuration settings of the platform of ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-containerapp-authconfig.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "containerApp": { + "existing": true, + "type": "Microsoft.App/containerApps", + "apiVersion": "2026-01-01", + "name": "[parameters('containerAppName')]" + }, + "containerAppAuthConfigs": { + "type": "Microsoft.App/containerApps/authConfigs", + "apiVersion": "2026-01-01", + "name": "[format('{0}/{1}', parameters('containerAppName'), 'current')]", + "properties": { + "encryptionSettings": "[parameters('encryptionSettings')]", + "globalValidation": "[parameters('globalValidation')]", + "httpSettings": "[parameters('httpSettings')]", + "identityProviders": "[parameters('identityProviders')]", + "login": "[parameters('login')]", + "platform": "[parameters('platform')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the set of Container App Auth configs." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the set of Container App Auth configs." + }, + "value": "[resourceId('Microsoft.App/containerApps/authConfigs', parameters('containerAppName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group containing the set of Container App Auth configs." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "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', '2026-01-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('containerApp', '2026-01-01', 'full').location]" + } + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app." + }, + "value": "[reference('containerApp').outputs.name.value]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the container app." + }, + "value": "[reference('containerApp').outputs.resourceId.value]" + }, + "fqdn": { + "type": "string", + "metadata": { + "description": "The FQDN of the container app." + }, + "value": "[reference('containerApp').outputs.fqdn.value]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID." + }, + "value": "[coalesce(tryGet(tryGet(reference('containerApp').outputs, 'systemAssignedMIPrincipalId'), 'value'), '')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ca-frontend.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('frontEndContainerAppName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "environmentResourceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "ingressExternal": { + "value": true + }, + "ingressTargetPort": { + "value": 3000 + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "containers": { + "value": [ + { + "name": "frontend", + "image": "[format('{0}/frontend:{1}', parameters('containerRegistryEndpoint'), parameters('imageTag'))]", + "env": [ + { + "name": "API_URL", + "value": "[format('https://{0}', reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.fqdn.value)]" + }, + { + "name": "APP_ENV", + "value": "prod" + }, + { + "name": "REACT_APP_MSAL_POST_REDIRECT_URL", + "value": "/" + }, + { + "name": "REACT_APP_MSAL_REDIRECT_URL", + "value": "/" + }, + { + "name": "ALLOWED_ORIGINS", + "value": "[format('https://{0}.{1}', variables('frontEndContainerAppName'), reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.defaultDomain.value)]" + } + ], + "resources": { + "cpu": "[json('1')]", + "memory": "2.0Gi" + } + } + ] + }, + "scaleSettings": { + "value": { + "maxReplicas": 1, + "minReplicas": 1 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "11175324594317515373" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "environmentResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Container Apps Environment." + } + }, + "containers": { + "type": "array", + "metadata": { + "description": "Container definitions." + } + }, + "ingressExternal": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "ingressTargetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for ingress." + } + }, + "ingressTransport": { + "type": "string", + "defaultValue": "auto", + "allowedValues": [ + "auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Ingress transport protocol." + } + }, + "ingressAllowInsecure": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to allow insecure ingress connections." + } + }, + "disableIngress": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Disable ingress entirely (for background workers)." + } + }, + "registries": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Container registry configurations." + } + }, + "secrets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Secret definitions." + } + }, + "managedIdentities": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Managed identity configuration." + } + }, + "corsPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "CORS policy configuration." + } + }, + "activeRevisionsMode": { + "type": "string", + "defaultValue": "Single", + "allowedValues": [ + "Single", + "Multiple" + ], + "metadata": { + "description": "Active revision mode." + } + }, + "scaleSettings": { + "type": "object", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 0 + }, + "metadata": { + "description": "Scale settings (maxReplicas, minReplicas, rules, cooldownPeriod, pollingInterval)." + } + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Workload profile name." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable Azure telemetry collection." + } + } + }, + "resources": { + "containerApp": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.app.containerapp.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "environmentResourceId": { + "value": "[parameters('environmentResourceId')]" + }, + "containers": { + "value": "[parameters('containers')]" + }, + "ingressExternal": "[if(parameters('disableIngress'), createObject('value', false()), createObject('value', parameters('ingressExternal')))]", + "ingressTargetPort": { + "value": "[parameters('ingressTargetPort')]" + }, + "ingressTransport": { + "value": "[parameters('ingressTransport')]" + }, + "ingressAllowInsecure": { + "value": "[parameters('ingressAllowInsecure')]" + }, + "disableIngress": { + "value": "[parameters('disableIngress')]" + }, + "registries": { + "value": "[parameters('registries')]" + }, + "secrets": { + "value": "[parameters('secrets')]" + }, + "managedIdentities": "[if(not(empty(parameters('managedIdentities'))), createObject('value', parameters('managedIdentities')), createObject('value', createObject()))]", + "corsPolicy": "[if(not(empty(parameters('corsPolicy'))), createObject('value', parameters('corsPolicy')), createObject('value', null()))]", + "activeRevisionsMode": { + "value": "[parameters('activeRevisionsMode')]" + }, + "scaleSettings": { + "value": "[parameters('scaleSettings')]" + }, + "workloadProfileName": { + "value": "[parameters('workloadProfileName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.42.1.51946", + "templateHash": "12626366001403616495" + }, + "name": "Container Apps", + "description": "This module deploys a Container App." + }, + "definitions": { + "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." + } + }, + "scaleType": { + "type": "object", + "properties": { + "maxReplicas": { + "type": "int", + "metadata": { + "description": "Required. The maximum number of replicas." + } + }, + "minReplicas": { + "type": "int", + "metadata": { + "description": "Required. The minimum number of replicas." + } + }, + "cooldownPeriod": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The cooldown period in seconds." + } + }, + "pollingInterval": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The polling interval in seconds." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/scaleRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The scaling rules." + } + } + }, + "metadata": { + "description": "The scale settings for the Container App." + } + }, + "scaleRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the scaling rule." + } + }, + "custom": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The custom scaling rule." + } + }, + "azureQueue": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The Azure Queue based scaling rule." + } + }, + "http": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The HTTP requests based scaling rule." + } + }, + "tcp": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The TCP based scaling rule." + } + } + }, + "metadata": { + "description": "The scaling rules for the Container App." + } + }, + "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." + } + }, + "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. The URL of the Azure Key Vault secret referenced by the Container App. Required if `value` is null." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the container app secret." + } + }, + "value": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Conditional. The container app secret value, if not fetched from the Key Vault. Required if `keyVaultUrl` is not null." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a secret." + } + }, + "authConfigType": { + "type": "object", + "properties": { + "encryptionSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/encryptionSettings" + }, + "description": "Optional. The configuration settings of the secrets references of encryption key and signing key for ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "globalValidation": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/globalValidation" + }, + "description": "Optional. The configuration settings that determines the validation flow of users using Service Authentication and/or Authorization." + }, + "nullable": true + }, + "httpSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/httpSettings" + }, + "description": "Optional. The configuration settings of the HTTP requests for authentication and authorization requests made against ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "identityProviders": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/identityProviders" + }, + "description": "Optional. The configuration settings of each of the identity providers used to configure ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "login": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/login" + }, + "description": "Optional. The configuration settings of the login flow of users using ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "platform": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/platform" + }, + "description": "Optional. The configuration settings of the platform of ContainerApp Service Authentication/Authorization." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the container app's authentication configuration." + } + }, + "diagnosticSettingMetricsOnlyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.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.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.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." + } + }, + "kind": { + "type": "string", + "defaultValue": "containerapps", + "allowedValues": [ + "containerapps", + "workflowapp", + "functionapp" + ], + "metadata": { + "description": "Optional. Metadata used to render different experiences for resources of the same type." + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/service" + }, + "description": "Optional. Dev ContainerApp service type." + }, + "nullable": true + }, + "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." + } + }, + "scaleSettings": { + "$ref": "#/definitions/scaleType", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 3 + }, + "metadata": { + "description": "Optional. The scaling settings of the service." + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "registries": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/registries" + }, + "description": "Optional. Collection of private container registry credentials for containers used by the Container app." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/customDomains" + }, + "description": "Optional. Custom domain bindings for Container App hostnames." + }, + "nullable": true + }, + "exposedPort": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Exposed Port in containers for TCP traffic from ingress." + } + }, + "ipSecurityRestrictions": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/ipSecurityRestrictions" + }, + "description": "Optional. Rules to restrict incoming IP address." + }, + "nullable": true + }, + "traffic": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/traffic" + }, + "description": "Optional. Traffic weight configuration for routing traffic across revisions. Each entry specifies a revision (or latest) and its traffic percentage. Supports blue-green and canary deployment patterns." + }, + "nullable": true + }, + "dapr": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/dapr" + }, + "description": "Optional. Dapr configuration for the Container App." + }, + "nullable": true + }, + "identitySettings": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/identitySettings" + }, + "description": "Optional. Settings for Managed Identities that are assigned to the Container App. If a Managed Identity is not specified here, default settings will be used." + }, + "nullable": true + }, + "maxInactiveRevisions": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Max inactive revisions a Container App can have." + } + }, + "runtime": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/runtime" + }, + "description": "Optional. Runtime configuration for the Container App." + }, + "nullable": true + }, + "containers": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/containers" + }, + "description": "Required. List of container definitions for the Container App." + } + }, + "terminationGracePeriodSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The termination grace period for the container app." + } + }, + "initContainersTemplate": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/initContainers" + }, + "description": "Optional. List of specialized containers that run before app containers." + }, + "nullable": true + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/secretType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The secrets of the Container App." + } + }, + "revisionSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User friendly suffix that is appended to the revision name." + } + }, + "volumes": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/volumes" + }, + "description": "Optional. List of volume definitions for the Container App." + }, + "nullable": true + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Workload profile name to pin for container app execution." + } + }, + "authConfig": { + "$ref": "#/definitions/authConfigType", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Container App Auth configs." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingMetricsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "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(variables('formattedUserAssignedIdentities'))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(variables('formattedUserAssignedIdentities'))), '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')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-containerapp.{0}.{1}', replace('0.22.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" + } + } + } + } + }, + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2026-01-01", + "name": "[parameters('name')]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "properties": { + "environmentId": "[parameters('environmentResourceId')]", + "workloadProfileName": "[parameters('workloadProfileName')]", + "template": { + "containers": "[parameters('containers')]", + "terminationGracePeriodSeconds": "[parameters('terminationGracePeriodSeconds')]", + "initContainers": "[parameters('initContainersTemplate')]", + "revisionSuffix": "[parameters('revisionSuffix')]", + "scale": "[parameters('scaleSettings')]", + "serviceBinds": "[if(parameters('includeAddOns'), parameters('serviceBinds'), null())]", + "volumes": "[parameters('volumes')]" + }, + "configuration": { + "activeRevisionsMode": "[parameters('activeRevisionsMode')]", + "dapr": "[parameters('dapr')]", + "identitySettings": "[parameters('identitySettings')]", + "ingress": "[if(parameters('disableIngress'), null(), createObject('additionalPortMappings', parameters('additionalPortMappings'), 'allowInsecure', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('ingressAllowInsecure'), false()), 'customDomains', parameters('customDomains'), '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', parameters('ipSecurityRestrictions'), 'targetPort', parameters('ingressTargetPort'), 'stickySessions', createObject('affinity', parameters('stickySessionsAffinity')), 'traffic', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('traffic'), null()), 'transport', parameters('ingressTransport')))]", + "service": "[if(parameters('includeAddOns'), parameters('service'), null())]", + "maxInactiveRevisions": "[parameters('maxInactiveRevisions')]", + "registries": "[parameters('registries')]", + "secrets": "[parameters('secrets')]", + "runtime": "[parameters('runtime')]" + } + } + }, + "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": "[resourceId('Microsoft.App/containerApps', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "containerApp" + ] + }, + "containerApp_roleAssignments": { + "copy": { + "name": "containerApp_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.App/containerApps', 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" + ] + }, + "containerApp_diagnosticSettings": { + "copy": { + "name": "containerApp_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.App/containerApps', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "containerApp" + ] + }, + "containerAppAuthConfigs": { + "condition": "[not(empty(parameters('authConfig')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-auth-config', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerAppName": { + "value": "[parameters('name')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "encryptionSettings": { + "value": "[tryGet(parameters('authConfig'), 'encryptionSettings')]" + }, + "globalValidation": { + "value": "[tryGet(parameters('authConfig'), 'globalValidation')]" + }, + "httpSettings": { + "value": "[tryGet(parameters('authConfig'), 'httpSettings')]" + }, + "identityProviders": { + "value": "[tryGet(parameters('authConfig'), 'identityProviders')]" + }, + "login": { + "value": "[tryGet(parameters('authConfig'), 'login')]" + }, + "platform": { + "value": "[tryGet(parameters('authConfig'), 'platform')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.42.1.51946", + "templateHash": "4649255393182983719" + }, + "name": "Container App Auth Configs", + "description": "This module deploys Container App Auth Configs." + }, + "parameters": { + "containerAppName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Container App. Required if the template is used in a standalone deployment." + } + }, + "encryptionSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/encryptionSettings" + }, + "description": "Optional. The configuration settings of the secrets references of encryption key and signing key for ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "globalValidation": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/globalValidation" + }, + "description": "Optional. The configuration settings that determines the validation flow of users using Service Authentication and/or Authorization." + }, + "nullable": true + }, + "httpSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/httpSettings" + }, + "description": "Optional. The configuration settings of the HTTP requests for authentication and authorization requests made against ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "identityProviders": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/identityProviders" + }, + "description": "Optional. The configuration settings of each of the identity providers used to configure ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "login": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/login" + }, + "description": "Optional. The configuration settings of the login flow of users using ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "platform": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/platform" + }, + "description": "Optional. The configuration settings of the platform of ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-containerapp-authconfig.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "containerApp": { + "existing": true, + "type": "Microsoft.App/containerApps", + "apiVersion": "2026-01-01", + "name": "[parameters('containerAppName')]" + }, + "containerAppAuthConfigs": { + "type": "Microsoft.App/containerApps/authConfigs", + "apiVersion": "2026-01-01", + "name": "[format('{0}/{1}', parameters('containerAppName'), 'current')]", + "properties": { + "encryptionSettings": "[parameters('encryptionSettings')]", + "globalValidation": "[parameters('globalValidation')]", + "httpSettings": "[parameters('httpSettings')]", + "identityProviders": "[parameters('identityProviders')]", + "login": "[parameters('login')]", + "platform": "[parameters('platform')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the set of Container App Auth configs." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the set of Container App Auth configs." + }, + "value": "[resourceId('Microsoft.App/containerApps/authConfigs', parameters('containerAppName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group containing the set of Container App Auth configs." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "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', '2026-01-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('containerApp', '2026-01-01', 'full').location]" + } + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app." + }, + "value": "[reference('containerApp').outputs.name.value]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the container app." + }, + "value": "[reference('containerApp').outputs.resourceId.value]" + }, + "fqdn": { + "type": "string", + "metadata": { + "description": "The FQDN of the container app." + }, + "value": "[reference('containerApp').outputs.fqdn.value]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID." + }, + "value": "[coalesce(tryGet(tryGet(reference('containerApp').outputs, 'systemAssignedMIPrincipalId'), 'value'), '')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ca-processor.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('processorContainerAppName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "environmentResourceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "ingressExternal": { + "value": false + }, + "ingressTargetPort": { + "value": 8080 + }, + "ingressAllowInsecure": { + "value": true + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "containers": { + "value": [ + { + "name": "processor", + "image": "[format('{0}/processor:{1}', parameters('containerRegistryEndpoint'), parameters('imageTag'))]", + "env": [ + { + "name": "AZURE_OPENAI_ENDPOINT", + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiFoundryEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value)]" + }, + { + "name": "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", + "value": "[parameters('gptModelName')]" + }, + { + "name": "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME", + "value": "[parameters('embeddingModel')]" + }, + { + "name": "AZURE_OPENAI_API_VERSION", + "value": "2025-03-01-preview" + }, + { + "name": "COSMOS_DB_ACCOUNT_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value]" + }, + { + "name": "COSMOS_DB_DATABASE_NAME", + "value": "[variables('cosmosDatabaseName')]" + }, + { + "name": "COSMOS_DB_CONTAINER_NAME", + "value": "agent_telemetry" + }, + { + "name": "COSMOS_DB_CONTROL_CONTAINER_NAME", + "value": "processcontrol" + }, + { + "name": "COSMOS_DB_PROCESS_CONTAINER", + "value": "processes" + }, + { + "name": "COSMOS_DB_PROCESS_LOG_CONTAINER", + "value": "agent_telemetry" + }, + { + "name": "STORAGE_ACCOUNT_BLOB_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.blobEndpoint.value]" + }, + { + "name": "STORAGE_ACCOUNT_NAME", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_CONTAINER", + "value": "[variables('processBlobContainerName')]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_QUEUE", + "value": "[variables('processQueueName')]" + }, + { + "name": "STORAGE_ACCOUNT_QUEUE_URL", + "value": "[format('{0}', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.serviceEndpoints.value.queue)]" + }, + { + "name": "GLOBAL_LLM_SERVICE", + "value": "AzureOpenAI" + }, + { + "name": "CONTROL_API_ENABLED", + "value": "1" + }, + { + "name": "CONTROL_API_PORT", + "value": "8080" + } + ], + "resources": { + "cpu": "[json('2')]", + "memory": "4.0Gi" + } + } + ] + }, + "scaleSettings": { + "value": { + "maxReplicas": 1, + "minReplicas": 1 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "11175324594317515373" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "environmentResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Container Apps Environment." + } + }, + "containers": { + "type": "array", + "metadata": { + "description": "Container definitions." + } + }, + "ingressExternal": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "ingressTargetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for ingress." + } + }, + "ingressTransport": { + "type": "string", + "defaultValue": "auto", + "allowedValues": [ + "auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Ingress transport protocol." + } + }, + "ingressAllowInsecure": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to allow insecure ingress connections." + } + }, + "disableIngress": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Disable ingress entirely (for background workers)." + } + }, + "registries": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Container registry configurations." + } + }, + "secrets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Secret definitions." + } + }, + "managedIdentities": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Managed identity configuration." + } + }, + "corsPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "CORS policy configuration." + } + }, + "activeRevisionsMode": { + "type": "string", + "defaultValue": "Single", + "allowedValues": [ + "Single", + "Multiple" + ], + "metadata": { + "description": "Active revision mode." + } + }, + "scaleSettings": { + "type": "object", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 0 + }, + "metadata": { + "description": "Scale settings (maxReplicas, minReplicas, rules, cooldownPeriod, pollingInterval)." + } + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Workload profile name." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable Azure telemetry collection." + } + } + }, + "resources": { + "containerApp": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('avm.res.app.containerapp.{0}', parameters('name')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[parameters('name')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, + "environmentResourceId": { + "value": "[parameters('environmentResourceId')]" + }, + "containers": { + "value": "[parameters('containers')]" + }, + "ingressExternal": "[if(parameters('disableIngress'), createObject('value', false()), createObject('value', parameters('ingressExternal')))]", + "ingressTargetPort": { + "value": "[parameters('ingressTargetPort')]" + }, + "ingressTransport": { + "value": "[parameters('ingressTransport')]" + }, + "ingressAllowInsecure": { + "value": "[parameters('ingressAllowInsecure')]" + }, + "disableIngress": { + "value": "[parameters('disableIngress')]" + }, + "registries": { + "value": "[parameters('registries')]" + }, + "secrets": { + "value": "[parameters('secrets')]" + }, + "managedIdentities": "[if(not(empty(parameters('managedIdentities'))), createObject('value', parameters('managedIdentities')), createObject('value', createObject()))]", + "corsPolicy": "[if(not(empty(parameters('corsPolicy'))), createObject('value', parameters('corsPolicy')), createObject('value', null()))]", + "activeRevisionsMode": { + "value": "[parameters('activeRevisionsMode')]" + }, + "scaleSettings": { + "value": "[parameters('scaleSettings')]" + }, + "workloadProfileName": { + "value": "[parameters('workloadProfileName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.42.1.51946", + "templateHash": "12626366001403616495" + }, + "name": "Container Apps", + "description": "This module deploys a Container App." + }, + "definitions": { + "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." + } + }, + "scaleType": { + "type": "object", + "properties": { + "maxReplicas": { + "type": "int", + "metadata": { + "description": "Required. The maximum number of replicas." + } + }, + "minReplicas": { + "type": "int", + "metadata": { + "description": "Required. The minimum number of replicas." + } + }, + "cooldownPeriod": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The cooldown period in seconds." + } + }, + "pollingInterval": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The polling interval in seconds." + } + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/definitions/scaleRuleType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The scaling rules." + } + } + }, + "metadata": { + "description": "The scale settings for the Container App." + } + }, + "scaleRuleType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the scaling rule." + } + }, + "custom": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The custom scaling rule." + } + }, + "azureQueue": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The Azure Queue based scaling rule." + } + }, + "http": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The HTTP requests based scaling rule." + } + }, + "tcp": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. The TCP based scaling rule." + } + } + }, + "metadata": { + "description": "The scaling rules for the Container App." + } + }, + "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." + } + }, + "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. The URL of the Azure Key Vault secret referenced by the Container App. Required if `value` is null." + } + }, + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of the container app secret." + } + }, + "value": { + "type": "securestring", + "nullable": true, + "metadata": { + "description": "Conditional. The container app secret value, if not fetched from the Key Vault. Required if `keyVaultUrl` is not null." + } + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for a secret." + } + }, + "authConfigType": { + "type": "object", + "properties": { + "encryptionSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/encryptionSettings" + }, + "description": "Optional. The configuration settings of the secrets references of encryption key and signing key for ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "globalValidation": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/globalValidation" + }, + "description": "Optional. The configuration settings that determines the validation flow of users using Service Authentication and/or Authorization." + }, + "nullable": true + }, + "httpSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/httpSettings" + }, + "description": "Optional. The configuration settings of the HTTP requests for authentication and authorization requests made against ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "identityProviders": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/identityProviders" + }, + "description": "Optional. The configuration settings of each of the identity providers used to configure ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "login": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/login" + }, + "description": "Optional. The configuration settings of the login flow of users using ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "platform": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/platform" + }, + "description": "Optional. The configuration settings of the platform of ContainerApp Service Authentication/Authorization." + }, + "nullable": true + } + }, + "metadata": { + "__bicep_export!": true, + "description": "The type for the container app's authentication configuration." + } + }, + "diagnosticSettingMetricsOnlyType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" + } + } + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + }, + "notes": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the notes of the lock." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a lock.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.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.6.1" + } + } + }, + "roleAssignmentType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + }, + "metadata": { + "description": "An AVM-aligned type for a role assignment.", + "__bicep_imported_from!": { + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.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." + } + }, + "kind": { + "type": "string", + "defaultValue": "containerapps", + "allowedValues": [ + "containerapps", + "workflowapp", + "functionapp" + ], + "metadata": { + "description": "Optional. Metadata used to render different experiences for resources of the same type." + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/service" + }, + "description": "Optional. Dev ContainerApp service type." + }, + "nullable": true + }, + "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." + } + }, + "scaleSettings": { + "$ref": "#/definitions/scaleType", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 3 + }, + "metadata": { + "description": "Optional. The scaling settings of the service." + } + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/tags" + }, + "description": "Optional. Tags of the resource." + }, + "nullable": true + }, + "registries": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/registries" + }, + "description": "Optional. Collection of private container registry credentials for containers used by the Container app." + }, + "nullable": true + }, + "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", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/customDomains" + }, + "description": "Optional. Custom domain bindings for Container App hostnames." + }, + "nullable": true + }, + "exposedPort": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Exposed Port in containers for TCP traffic from ingress." + } + }, + "ipSecurityRestrictions": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/ipSecurityRestrictions" + }, + "description": "Optional. Rules to restrict incoming IP address." + }, + "nullable": true + }, + "traffic": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/ingress/properties/traffic" + }, + "description": "Optional. Traffic weight configuration for routing traffic across revisions. Each entry specifies a revision (or latest) and its traffic percentage. Supports blue-green and canary deployment patterns." + }, + "nullable": true + }, + "dapr": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/dapr" + }, + "description": "Optional. Dapr configuration for the Container App." + }, + "nullable": true + }, + "identitySettings": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/identitySettings" + }, + "description": "Optional. Settings for Managed Identities that are assigned to the Container App. If a Managed Identity is not specified here, default settings will be used." + }, + "nullable": true + }, + "maxInactiveRevisions": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Max inactive revisions a Container App can have." + } + }, + "runtime": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/configuration/properties/runtime" + }, + "description": "Optional. Runtime configuration for the Container App." + }, + "nullable": true + }, + "containers": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/containers" + }, + "description": "Required. List of container definitions for the Container App." + } + }, + "terminationGracePeriodSeconds": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The termination grace period for the container app." + } + }, + "initContainersTemplate": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/initContainers" + }, + "description": "Optional. List of specialized containers that run before app containers." + }, + "nullable": true + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/definitions/secretType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The secrets of the Container App." + } + }, + "revisionSuffix": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. User friendly suffix that is appended to the revision name." + } + }, + "volumes": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps@2026-01-01#properties/properties/properties/template/properties/volumes" + }, + "description": "Optional. List of volume definitions for the Container App." + }, + "nullable": true + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Workload profile name to pin for container app execution." + } + }, + "authConfig": { + "$ref": "#/definitions/authConfigType", + "nullable": true, + "metadata": { + "description": "Optional. The name of the Container App Auth configs." + } + }, + "diagnosticSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/diagnosticSettingMetricsOnlyType" + }, + "nullable": true, + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "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(variables('formattedUserAssignedIdentities'))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(variables('formattedUserAssignedIdentities'))), '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')]" + }, + "enableReferencedModulesTelemetry": false + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-containerapp.{0}.{1}', replace('0.22.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" + } + } + } + } + }, + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2026-01-01", + "name": "[parameters('name')]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", + "location": "[parameters('location')]", + "identity": "[variables('identity')]", + "properties": { + "environmentId": "[parameters('environmentResourceId')]", + "workloadProfileName": "[parameters('workloadProfileName')]", + "template": { + "containers": "[parameters('containers')]", + "terminationGracePeriodSeconds": "[parameters('terminationGracePeriodSeconds')]", + "initContainers": "[parameters('initContainersTemplate')]", + "revisionSuffix": "[parameters('revisionSuffix')]", + "scale": "[parameters('scaleSettings')]", + "serviceBinds": "[if(parameters('includeAddOns'), parameters('serviceBinds'), null())]", + "volumes": "[parameters('volumes')]" + }, + "configuration": { + "activeRevisionsMode": "[parameters('activeRevisionsMode')]", + "dapr": "[parameters('dapr')]", + "identitySettings": "[parameters('identitySettings')]", + "ingress": "[if(parameters('disableIngress'), null(), createObject('additionalPortMappings', parameters('additionalPortMappings'), 'allowInsecure', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('ingressAllowInsecure'), false()), 'customDomains', parameters('customDomains'), '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', parameters('ipSecurityRestrictions'), 'targetPort', parameters('ingressTargetPort'), 'stickySessions', createObject('affinity', parameters('stickySessionsAffinity')), 'traffic', if(not(equals(parameters('ingressTransport'), 'tcp')), parameters('traffic'), null()), 'transport', parameters('ingressTransport')))]", + "service": "[if(parameters('includeAddOns'), parameters('service'), null())]", + "maxInactiveRevisions": "[parameters('maxInactiveRevisions')]", + "registries": "[parameters('registries')]", + "secrets": "[parameters('secrets')]", + "runtime": "[parameters('runtime')]" + } + } + }, + "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": "[resourceId('Microsoft.App/containerApps', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[coalesce(tryGet(parameters('lock'), 'notes'), if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.'))]" + }, + "dependsOn": [ + "containerApp" + ] + }, + "containerApp_roleAssignments": { + "copy": { + "name": "containerApp_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.App/containerApps', 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" + ] + }, + "containerApp_diagnosticSettings": { + "copy": { + "name": "containerApp_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[resourceId('Microsoft.App/containerApps', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "containerApp" + ] + }, + "containerAppAuthConfigs": { + "condition": "[not(empty(parameters('authConfig')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}-auth-config', uniqueString(deployment().name, parameters('location')))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "containerAppName": { + "value": "[parameters('name')]" + }, + "enableTelemetry": { + "value": "[variables('enableReferencedModulesTelemetry')]" + }, + "encryptionSettings": { + "value": "[tryGet(parameters('authConfig'), 'encryptionSettings')]" + }, + "globalValidation": { + "value": "[tryGet(parameters('authConfig'), 'globalValidation')]" + }, + "httpSettings": { + "value": "[tryGet(parameters('authConfig'), 'httpSettings')]" + }, + "identityProviders": { + "value": "[tryGet(parameters('authConfig'), 'identityProviders')]" + }, + "login": { + "value": "[tryGet(parameters('authConfig'), 'login')]" + }, + "platform": { + "value": "[tryGet(parameters('authConfig'), 'platform')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.42.1.51946", + "templateHash": "4649255393182983719" + }, + "name": "Container App Auth Configs", + "description": "This module deploys Container App Auth Configs." + }, + "parameters": { + "containerAppName": { + "type": "string", + "metadata": { + "description": "Conditional. The name of the parent Container App. Required if the template is used in a standalone deployment." + } + }, + "encryptionSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/encryptionSettings" + }, + "description": "Optional. The configuration settings of the secrets references of encryption key and signing key for ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "globalValidation": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/globalValidation" + }, + "description": "Optional. The configuration settings that determines the validation flow of users using Service Authentication and/or Authorization." + }, + "nullable": true + }, + "httpSettings": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/httpSettings" + }, + "description": "Optional. The configuration settings of the HTTP requests for authentication and authorization requests made against ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "identityProviders": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/identityProviders" + }, + "description": "Optional. The configuration settings of each of the identity providers used to configure ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "login": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/login" + }, + "description": "Optional. The configuration settings of the login flow of users using ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "platform": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.App/containerApps/authConfigs@2026-01-01#properties/properties/properties/platform" + }, + "description": "Optional. The configuration settings of the platform of ContainerApp Service Authentication/Authorization." + }, + "nullable": true + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('46d3xbcp.res.app-containerapp-authconfig.{0}.{1}', replace('0.1.0', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + "containerApp": { + "existing": true, + "type": "Microsoft.App/containerApps", + "apiVersion": "2026-01-01", + "name": "[parameters('containerAppName')]" + }, + "containerAppAuthConfigs": { + "type": "Microsoft.App/containerApps/authConfigs", + "apiVersion": "2026-01-01", + "name": "[format('{0}/{1}', parameters('containerAppName'), 'current')]", + "properties": { + "encryptionSettings": "[parameters('encryptionSettings')]", + "globalValidation": "[parameters('globalValidation')]", + "httpSettings": "[parameters('httpSettings')]", + "identityProviders": "[parameters('identityProviders')]", + "login": "[parameters('login')]", + "platform": "[parameters('platform')]" + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the set of Container App Auth configs." + }, + "value": "current" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the set of Container App Auth configs." + }, + "value": "[resourceId('Microsoft.App/containerApps/authConfigs', parameters('containerAppName'), 'current')]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group containing the set of Container App Auth configs." + }, + "value": "[resourceGroup().name]" + } + } + } + }, + "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', '2026-01-01', 'full'), 'identity'), 'principalId')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference('containerApp', '2026-01-01', 'full').location]" + } + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app." + }, + "value": "[reference('containerApp').outputs.name.value]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the container app." + }, + "value": "[reference('containerApp').outputs.resourceId.value]" + }, + "fqdn": { + "type": "string", + "metadata": { + "description": "The FQDN of the container app." + }, + "value": "[reference('containerApp').outputs.fqdn.value]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID." + }, + "value": "[coalesce(tryGet(tryGet(reference('containerApp').outputs, 'systemAssignedMIPrincipalId'), 'value'), '')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.role-assignments.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "useExistingAIProject": { + "value": "[variables('useExistingAIProject')]" + }, + "existingFoundryProjectResourceId": { + "value": "[parameters('existingFoundryProjectResourceId')]" + }, + "aiFoundryResourceId": "[if(not(variables('useExistingAIProject')), if(not(variables('useExistingAIProject')), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value), createObject('value', '')), createObject('value', ''))]", + "aiSearchResourceId": { + "value": "" + }, + "storageAccountResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "aiProjectPrincipalId": "[if(variables('useExistingAIProject'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiProjectPrincipalId.value), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectIdentityPrincipalId.value))]", + "aiSearchPrincipalId": { + "value": "" + }, + "backendAppServicePrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.principalId.value]" + }, + "cosmosDbAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "existingAiProjectPrincipalId": "[if(not(empty(parameters('existingFoundryProjectResourceId'))), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiProjectPrincipalId.value), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "3917586023576904326" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Solution name suffix for generating unique role assignment GUIDs." + } + }, + "useExistingAIProject": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to use an existing AI project (true) or create new (false)." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the existing AI project (for deriving AI Services name/sub/RG)." + } + }, + "aiProjectPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the AI project identity." + } + }, + "existingAiProjectPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the existing AI project identity (for cross-service roles)." + } + }, + "aiSearchPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the AI Search identity." + } + }, + "backendAppServicePrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the backend App Service system-assigned identity (empty if not deployed)." + } + }, + "aiFoundryResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the AI Foundry account (empty if not deployed — new project path)." + } + }, + "aiSearchResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the AI Search service (empty if not deployed)." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the Storage Account (empty if not deployed)." + } + }, + "cosmosDbAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Cosmos DB account (empty if not deployed)." + } + } + }, + "variables": { + "existingAIFoundryName": "[if(parameters('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], '')]", + "existingAIFoundrySubscription": "[if(parameters('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], subscription().subscriptionId)]", + "existingAIFoundryResourceGroup": "[if(parameters('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], resourceGroup().name)]", + "roleDefinitions": { + "azureAiUser": "53ca6127-db72-4b80-b1b0-d745d6d5456d", + "cognitiveServicesUser": "a97b65f3-24c7-4388-baec-2e87135dc908", + "cognitiveServicesOpenAIUser": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", + "searchIndexDataReader": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "searchServiceContributor": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "storageBlobDataContributor": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "storageBlobDataReader": "2a2b9908-6ea1-4ae2-8e65-a410df84e7d1" + } + }, + "resources": [ + { + "condition": "[and(and(not(parameters('useExistingAIProject')), not(empty(parameters('aiSearchPrincipalId')))), not(empty(parameters('aiFoundryResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/'))), variables('roleDefinitions').cognitiveServicesOpenAIUser, 'search-openai')]", + "properties": { + "principalId": "[parameters('aiSearchPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').cognitiveServicesOpenAIUser)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(not(parameters('useExistingAIProject')), not(empty(parameters('aiFoundryResourceId')))), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('backendAppServicePrincipalId'), variables('roleDefinitions').azureAiUser, 'backend-ai-services')]", + "properties": { + "principalId": "[parameters('backendAppServicePrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').azureAiUser)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('aiSearchResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').searchIndexDataReader, 'project-search')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('aiSearchResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').searchServiceContributor, 'project-search')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchServiceContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('aiSearchResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').searchIndexDataReader, 'existing-project-search')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('aiSearchResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').searchServiceContributor, 'existing-project-search')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchServiceContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('aiSearchResourceId'))), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('backendAppServicePrincipalId'), variables('roleDefinitions').searchIndexDataReader, 'backend-search')]", + "properties": { + "principalId": "[parameters('backendAppServicePrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('storageAccountResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataContributor, 'project-storage')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('storageAccountResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataReader, 'project-storage')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('storageAccountResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataContributor, 'existing-project-storage')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('storageAccountResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataReader, 'existing-project-storage')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('storageAccountResourceId'))), not(empty(parameters('aiSearchPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiSearchPrincipalId'), variables('roleDefinitions').storageBlobDataReader, 'search-storage')]", + "properties": { + "principalId": "[parameters('aiSearchPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('cosmosDbAccountName'))), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2025-10-15", + "name": "[format('{0}/{1}', parameters('cosmosDbAccountName'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('cosmosDbAccountName'), '00000000-0000-0000-0000-000000000002'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbAccountName')), parameters('backendAppServicePrincipalId')))]", + "properties": { + "principalId": "[parameters('backendAppServicePrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('cosmosDbAccountName'), '00000000-0000-0000-0000-000000000002')]", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbAccountName'))]" + } + }, + { + "condition": "[and(parameters('useExistingAIProject'), not(empty(parameters('aiSearchPrincipalId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "assignOpenAIRoleToAISearchExisting", + "subscriptionId": "[variables('existingAIFoundrySubscription')]", + "resourceGroup": "[variables('existingAIFoundryResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('aiSearchPrincipalId')]" + }, + "roleDefinitionId": { + "value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').cognitiveServicesOpenAIUser)]" + }, + "roleAssignmentName": { + "value": "[guid(parameters('solutionName'), 'search-openai', variables('existingAIFoundryName'), variables('roleDefinitions').cognitiveServicesOpenAIUser)]" + }, + "aiFoundryName": { + "value": "[variables('existingAIFoundryName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "7353258032010757384" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The resource ID of the role definition to assign." + } + }, + "roleAssignmentName": { + "type": "string", + "metadata": { + "description": "A unique name for the role assignment." + } + }, + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "The name of the AI Foundry account to scope the role assignment to." + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "allowedValues": [ + "ServicePrincipal", + "User" + ], + "metadata": { + "description": "The principal type of the identity being assigned." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName'))]", + "name": "[parameters('roleAssignmentName')]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + } + }, + { + "condition": "[and(parameters('useExistingAIProject'), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "assignAiUserRoleToBackendExisting", + "subscriptionId": "[variables('existingAIFoundrySubscription')]", + "resourceGroup": "[variables('existingAIFoundryResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('backendAppServicePrincipalId')]" + }, + "roleDefinitionId": { + "value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').azureAiUser)]" + }, + "roleAssignmentName": { + "value": "[guid(parameters('solutionName'), 'backend-aiuser', variables('existingAIFoundryName'), variables('roleDefinitions').azureAiUser)]" + }, + "aiFoundryName": { + "value": "[variables('existingAIFoundryName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "7353258032010757384" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The resource ID of the role definition to assign." + } + }, + "roleAssignmentName": { + "type": "string", + "metadata": { + "description": "A unique name for the role assignment." + } + }, + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "The name of the AI Foundry account to scope the role assignment to." + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "allowedValues": [ + "ServicePrincipal", + "User" + ], + "metadata": { + "description": "The principal type of the identity being assigned." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName'))]", + "name": "[parameters('roleAssignmentName')]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + } + ], + "outputs": { + "SOLUTION_NAME": { + "type": "string", + "metadata": { + "description": "Solution suffix used for naming resources" + }, + "value": "[variables('solutionSuffix')]" + }, + "RESOURCE_GROUP_NAME": { + "type": "string", + "metadata": { + "description": "Name of the deployed resource group" + }, + "value": "[resourceGroup().name]" + }, + "CONTAINER_WEB_APP_NAME": { + "type": "string", + "metadata": { + "description": "The name of the web app container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-frontend.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "CONTAINER_WEB_APP_FQDN": { + "type": "string", + "metadata": { + "description": "The FQDN of the web app container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-frontend.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.fqdn.value]" + }, + "CONTAINER_API_APP_NAME": { + "type": "string", + "metadata": { + "description": "The name of the API container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "CONTAINER_API_APP_FQDN": { + "type": "string", + "metadata": { + "description": "The FQDN of the API container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.fqdn.value]" + }, + "AZURE_OPENAI_ENDPOINT": { + "type": "string", + "metadata": { + "description": "Azure OpenAI service endpoint URL" + }, + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiFoundryEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value)]" + }, + "AZURE_ENV_GPT_MODEL_NAME": { + "type": "string", + "metadata": { + "description": "GPT model deployment name" + }, + "value": "[parameters('gptModelName')]" + }, + "AZURE_ENV_EMBEDDING_DEPLOYMENT_NAME": { + "type": "string", + "metadata": { + "description": "Embedding model deployment name" + }, + "value": "[parameters('embeddingModel')]" + }, + "AZURE_COSMOSDB_ACCOUNT": { + "type": "string", + "metadata": { + "description": "Cosmos DB account name" + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "AZURE_COSMOSDB_DATABASE": { + "type": "string", + "metadata": { + "description": "Cosmos DB database name" + }, + "value": "[variables('cosmosDatabaseName')]" + }, + "AZURE_SUBSCRIPTION_ID": { + "type": "string", + "metadata": { + "description": "The Azure subscription ID." + }, + "value": "[subscription().subscriptionId]" + }, + "AZURE_RESOURCE_GROUP": { + "type": "string", + "metadata": { + "description": "The Azure resource group name." + }, + "value": "[resourceGroup().name]" + }, + "AZURE_AI_AGENT_ENDPOINT": { + "type": "string", + "metadata": { + "description": "Azure AI Agent service endpoint URL" + }, + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectEndpoint.value)]" + } + } +} \ No newline at end of file diff --git a/infra/avm/modules/ai/ai-foundry-connection.bicep b/infra/avm/modules/ai/ai-foundry-connection.bicep new file mode 100644 index 00000000..c3260e4d --- /dev/null +++ b/infra/avm/modules/ai/ai-foundry-connection.bicep @@ -0,0 +1,84 @@ +// ============================================================================ +// Module: AI Foundry Project Connection (Single) +// Description: Creates a single connection on an AI Foundry project. +// Generic, reusable — call once per connection type from main.bicep. +// Supports any connection category (CognitiveSearch, AzureBlob, +// AppInsights, RemoteTool, etc.) via parameterized properties. +// ============================================================================ + +targetScope = 'resourceGroup' + +@description('Required. Name of the parent AI Services account.') +param aiServicesAccountName string + +@description('Required. Name of the AI Foundry project.') +param projectName string + +@description('Required. Solution name suffix used to generate the connection name.') +param solutionName string + +@description('Optional. Connection name. Defaults to lowercase category with solution suffix.') +param connectionName string = toLower('${category}-connection-${solutionName}') + +@description('Required. Connection category (e.g., CognitiveSearch, AzureBlob, AppInsights, RemoteTool).') +param category string + +@description('Required. Connection target (URL or resource ID).') +param target string + +@description('Required. Authentication type (e.g., AAD, ApiKey, ProjectManagedIdentity).') +param authType string + +@description('Optional. Whether the connection is shared to all project users.') +param isSharedToAll bool = true + +@description('Optional. Whether this is the default connection for its category.') +param isDefault bool = false + +@description('Optional. Connection metadata object.') +param metadata object = {} + +@secure() +@description('Optional. Credentials key (for ApiKey auth type).') +param credentialsKey string = '' + +// ============================================================================ +// Existing Resource References +// ============================================================================ +resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: aiServicesAccountName +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' existing = { + parent: aiServicesAccount + name: projectName +} + +// ============================================================================ +// Connection +// ============================================================================ +var baseProperties = { + category: category + target: target + authType: authType + isSharedToAll: isSharedToAll + metadata: metadata +} + +var optionalDefault = isDefault ? { isDefault: true } : {} +var optionalCredentials = !empty(credentialsKey) ? { credentials: { key: credentialsKey } } : {} + +resource connection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-12-01' = { + parent: aiProject + name: connectionName + properties: any(union(baseProperties, optionalDefault, optionalCredentials)) +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Connection name.') +output connectionName string = connection.name + +@description('Connection resource ID.') +output connectionId string = connection.id diff --git a/infra/avm/modules/ai/ai-foundry-model-deployment.bicep b/infra/avm/modules/ai/ai-foundry-model-deployment.bicep new file mode 100644 index 00000000..1c534fd8 --- /dev/null +++ b/infra/avm/modules/ai/ai-foundry-model-deployment.bicep @@ -0,0 +1,64 @@ +// ============================================================================ +// Module: Model Deployment +// Description: Deploys a single AI model to an existing AI Services account. +// Called repetitively from main.bicep for each model in the array. +// Generic, reusable across GSAs. +// ============================================================================ + +@description('Required. Name of the parent AI Services account.') +param aiServicesAccountName string + +@description('Required. Name for this model deployment.') +param deploymentName string + +@description('Optional. Model format (e.g., OpenAI).') +param modelFormat string = 'OpenAI' + +@description('Required. Model name (e.g., gpt-4o, text-embedding-ada-002).') +param modelName string + +@description('Optional. Model version. Empty string means latest.') +param modelVersion string = '' + +@description('Optional. RAI policy name.') +param raiPolicyName string = 'Microsoft.Default' + +@description('Required. SKU name (e.g., Standard, GlobalStandard).') +param skuName string + +@description('Required. SKU capacity (tokens per minute in thousands).') +param skuCapacity int + +// ============================================================================ +// Model Deployment +// ============================================================================ +resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: aiServicesAccountName +} + +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-12-01' = { + parent: aiServicesAccount + name: deploymentName + properties: { + model: { + format: modelFormat + name: modelName + version: !empty(modelVersion) ? modelVersion : null + } + raiPolicyName: raiPolicyName + } + sku: { + name: skuName + capacity: skuCapacity + } +} + +// ============================================================================ +// Outputs +// ============================================================================ + +@description('Name of the deployed model.') +output name string = modelDeployment.name + +@description('Resource ID of the model deployment.') +output resourceId string = modelDeployment.id diff --git a/infra/avm/modules/ai/ai-foundry-project.bicep b/infra/avm/modules/ai/ai-foundry-project.bicep new file mode 100644 index 00000000..76671e96 --- /dev/null +++ b/infra/avm/modules/ai/ai-foundry-project.bicep @@ -0,0 +1,135 @@ +// ============================================================================ +// Module: AI Foundry Project (Account + Project) +// Description: AVM wrapper for Azure AI Services account creation and +// AI Foundry project provisioning. Generic, reusable across GSAs. +// AVM Module: avm/res/cognitive-services/account +// WAF: https://learn.microsoft.com/azure/well-architected/service-guides/azure-openai +// ============================================================================ + +@description('Required. Solution name suffix used to generate resource names.') +param solutionName string + +@description('Optional. Override name for the AI Services account. Defaults to aif-{solutionName}.') +param name string = 'aif-${solutionName}' + +@description('Optional. Override name for the AI Foundry project. Defaults to proj-{solutionName}.') +param projectName string = 'proj-${solutionName}' + +@description('Required. Azure region for the resources.') +param location string + +@description('Optional. Tags to apply to resources.') +param tags object = {} + +@description('Optional. SKU name for the AI Services account.') +param skuName string = 'S0' + +@description('Optional. Whether to disable local (key-based) authentication.') +param disableLocalAuth bool = true + +@description('Optional. Whether to allow project management (AI Foundry hub).') +param allowProjectManagement bool = true + +@description('Optional. Public network access setting.') +param publicNetworkAccess string = 'Enabled' + +@description('Optional. Managed identity type for the resources.') +@allowed(['SystemAssigned', 'UserAssigned', 'SystemAssigned, UserAssigned', 'None']) +param identityType string = 'SystemAssigned' + +@description('Optional. Network ACLs default action.') +@allowed(['Allow', 'Deny']) +param networkAclsDefaultAction string = 'Allow' + +// --- WAF: Monitoring --- +@description('Optional. Diagnostic settings for the resource.') +param diagnosticSettings array? + +// --- WAF: Telemetry --- +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// --- Role Assignments --- +@description('Optional. Array of role assignments to create on the AI Services account.') +param roleAssignments array? + +// ============================================================================ +// AI Services Account (AVM Module) +// ============================================================================ +module aiServicesAccount 'br/public:avm/res/cognitive-services/account:0.14.2' = { + name: take('avm.res.cognitive-services.account.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + sku: skuName + kind: 'AIServices' + disableLocalAuth: disableLocalAuth + allowProjectManagement: allowProjectManagement + customSubDomainName: name + networkAcls: { + defaultAction: networkAclsDefaultAction + virtualNetworkRules: [] + ipRules: [] + } + publicNetworkAccess: publicNetworkAccess + managedIdentities: { + systemAssigned: true + } + diagnosticSettings: diagnosticSettings + deployments: [] + roleAssignments: roleAssignments + // Private endpoints deployed separately to avoid AccountProvisioningStateInvalid + privateEndpoints: [] + } +} + +// ============================================================================ +// AI Foundry Project +// ============================================================================ +resource aiServicesResource 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: name + dependsOn: [aiServicesAccount] +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' = { + parent: aiServicesResource + name: projectName + location: location + tags: tags + kind: 'AIServices' + identity: { + type: identityType + } + properties: {} + dependsOn: [aiServicesAccount] +} + +// ============================================================================ +// Outputs +// ============================================================================ + +@description('Resource ID of the AI Services account.') +output resourceId string = aiServicesAccount.outputs.resourceId + +@description('Name of the AI Services account.') +output name string = aiServicesAccount.outputs.name + +@description('Endpoint of the AI Services account.') +output endpoint string = aiServicesAccount.outputs.endpoint + +@description('System-assigned identity principal ID of the AI Services account.') +output principalId string = aiServicesAccount.outputs.systemAssignedMIPrincipalId + +@description('Resource ID of the AI Foundry project.') +output projectResourceId string = aiProject.id + +@description('Name of the AI Foundry project.') +output projectName string = aiProject.name + +@description('AI Foundry project endpoint.') +output projectEndpoint string = aiProject.properties.endpoints['AI Foundry API'] + +@description('System-assigned identity principal ID of the project.') +output projectIdentityPrincipalId string = aiProject.identity.principalId diff --git a/infra/avm/modules/ai/existing-project-setup.bicep b/infra/avm/modules/ai/existing-project-setup.bicep new file mode 100644 index 00000000..7a396dcb --- /dev/null +++ b/infra/avm/modules/ai/existing-project-setup.bicep @@ -0,0 +1,50 @@ +// ============================================================================ +// Module: Existing AI Foundry Project Reference +// Description: References an existing AI Services account and project to +// retrieve their identities. No deployments, no connections. +// Use generic ai-foundry-connection and ai-foundry-model-deployment +// modules for those concerns. +// ============================================================================ + +@description('Required. The name of the existing Cognitive Services account.') +param name string + +@description('Required. The name of the existing AI project.') +param projectName string + +// ============================================================================ +// Existing Resource References +// ============================================================================ +resource cognitiveService 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: name +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' existing = { + parent: cognitiveService + name: projectName +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('The principal ID of the AI Foundry system-assigned managed identity (empty if none).') +output aiFoundryPrincipalId string = cognitiveService.identity.?principalId ?? '' + +@description('The principal ID of the AI Project system-assigned managed identity (empty if none).') +output aiProjectPrincipalId string = aiProject.identity.?principalId ?? '' + +@description('The name of the AI Services account.') +output aiServicesAccountName string = cognitiveService.name + +@description('The name of the AI project.') +output aiProjectName string = aiProject.name + +@description('The endpoint URL for the Azure OpenAI service.') +output aiFoundryEndpoint string = 'https://${name}.openai.azure.com/' + +@description('The endpoint URL for the AI Foundry project.') +output projectEndpoint string = 'https://${name}.services.ai.azure.com/api/projects/${projectName}' + +@description('The resource ID of the AI Services account.') +output aiFoundryResourceId string = cognitiveService.id + diff --git a/infra/avm/modules/compute/container-app-environment.bicep b/infra/avm/modules/compute/container-app-environment.bicep new file mode 100644 index 00000000..9822e5d5 --- /dev/null +++ b/infra/avm/modules/compute/container-app-environment.bicep @@ -0,0 +1,62 @@ +// ============================================================================ +// Module: Azure Container Apps Environment (AVM) +// AVM Module: avm/res/app/managed-environment:0.13.3 +// ============================================================================ + +@description('Solution name used for naming convention.') +param solutionName string + +@description('Name of the Container Apps Environment.') +param name string = 'cae-${solutionName}' + +@description('Azure region for deployment.') +param location string + +@description('Resource tags.') +param tags object = {} + +@description('Resource ID of the Log Analytics workspace.') +param logAnalyticsWorkspaceResourceId string + +@description('Subnet resource ID for VNet integration (optional).') +param infrastructureSubnetId string = '' + +@description('Enable zone redundancy.') +param zoneRedundant bool = false + +@description('Enable Azure telemetry collection.') +param enableTelemetry bool = true + +// ============================================================================ +// Container Apps Environment (AVM) +// ============================================================================ +module managedEnvironment 'br/public:avm/res/app/managed-environment:0.13.3' = { + name: take('avm.res.app.managedenvironment.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + } + infrastructureSubnetResourceId: !empty(infrastructureSubnetId) ? infrastructureSubnetId : null + zoneRedundant: zoneRedundant + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('The name of the Container Apps Environment.') +output name string = managedEnvironment.outputs.name + +@description('The resource ID of the Container Apps Environment.') +output resourceId string = managedEnvironment.outputs.resourceId + +@description('The default domain of the Container Apps Environment.') +output defaultDomain string = managedEnvironment.outputs.defaultDomain + +@description('The static IP of the Container Apps Environment.') +output staticIp string = managedEnvironment.outputs.staticIp diff --git a/infra/avm/modules/compute/container-app.bicep b/infra/avm/modules/compute/container-app.bicep new file mode 100644 index 00000000..07e7b4f9 --- /dev/null +++ b/infra/avm/modules/compute/container-app.bicep @@ -0,0 +1,105 @@ +// ============================================================================ +// Module: Azure Container App (AVM) +// AVM Module: avm/res/app/container-app:0.22.1 +// ============================================================================ + +@description('Name of the container app.') +param name string + +@description('Azure region for deployment.') +param location string + +@description('Resource tags.') +param tags object = {} + +@description('Resource ID of the Container Apps Environment.') +param environmentResourceId string + +@description('Container definitions.') +param containers array + +@description('Enable external ingress.') +param ingressExternal bool = true + +@description('Target port for ingress.') +param ingressTargetPort int = 80 + +@description('Ingress transport protocol.') +@allowed(['auto', 'http', 'http2', 'tcp']) +param ingressTransport string = 'auto' + +@description('Whether to allow insecure ingress connections.') +param ingressAllowInsecure bool = false + +@description('Disable ingress entirely (for background workers).') +param disableIngress bool = false + +@description('Container registry configurations.') +param registries array? + +@description('Secret definitions.') +param secrets array? + +@description('Managed identity configuration.') +param managedIdentities object = {} + +@description('CORS policy configuration.') +param corsPolicy object = {} + +@description('Active revision mode.') +@allowed(['Single', 'Multiple']) +param activeRevisionsMode string = 'Single' + +@description('Scale settings (maxReplicas, minReplicas, rules, cooldownPeriod, pollingInterval).') +param scaleSettings object = { + maxReplicas: 10 + minReplicas: 0 +} + +@description('Workload profile name.') +param workloadProfileName string? + +@description('Enable Azure telemetry collection.') +param enableTelemetry bool = true + +// ============================================================================ +// Container App (AVM) +// ============================================================================ +module containerApp 'br/public:avm/res/app/container-app:0.22.1' = { + name: take('avm.res.app.containerapp.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + environmentResourceId: environmentResourceId + containers: containers + ingressExternal: disableIngress ? false : ingressExternal + ingressTargetPort: ingressTargetPort + ingressTransport: ingressTransport + ingressAllowInsecure: ingressAllowInsecure + disableIngress: disableIngress + registries: registries + secrets: secrets + managedIdentities: !empty(managedIdentities) ? managedIdentities : {} + corsPolicy: !empty(corsPolicy) ? corsPolicy : null + activeRevisionsMode: activeRevisionsMode + scaleSettings: scaleSettings + workloadProfileName: workloadProfileName + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('The name of the container app.') +output name string = containerApp.outputs.name + +@description('The resource ID of the container app.') +output resourceId string = containerApp.outputs.resourceId + +@description('The FQDN of the container app.') +output fqdn string = containerApp.outputs.fqdn + +@description('System-assigned identity principal ID.') +output principalId string = containerApp.outputs.?systemAssignedMIPrincipalId ?? '' diff --git a/infra/avm/modules/data/cosmos-db.bicep b/infra/avm/modules/data/cosmos-db.bicep new file mode 100644 index 00000000..49c39f76 --- /dev/null +++ b/infra/avm/modules/data/cosmos-db.bicep @@ -0,0 +1,146 @@ +// ============================================================================ +// Module: Cosmos DB +// Description: AVM wrapper for Azure Cosmos DB (NoSQL) with WAF alignment +// AVM Module: avm/res/document-db/database-account:0.19.0 +// WAF: https://learn.microsoft.com/azure/well-architected/service-guides/cosmos-db +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Name of the Cosmos DB account.') +param name string = 'cosmos-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Database name.') +param databaseName string = 'db_conversation_history' + +@description('Container definitions.') +param containers array = [ + { + name: 'conversations' + partitionKeyPath: '/userId' + } +] + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// --- WAF: Monitoring --- +@description('Diagnostic settings for monitoring.') +param diagnosticSettings array = [] + +// --- WAF: Private Networking --- +@description('Public network access setting.') +param publicNetworkAccess string = 'Enabled' + +@description('Whether to enable private networking.') +param enablePrivateNetworking bool = false + +@description('Subnet resource ID for the private endpoint.') +param privateEndpointSubnetId string = '' + +@description('Private DNS zone resource IDs for Cosmos DB.') +param privateDnsZoneResourceIds array = [] + +var privateDnsZoneConfigs = [for (zoneId, i) in privateDnsZoneResourceIds: { + name: 'dns-zone-${i}' + privateDnsZoneResourceId: zoneId +}] + +// --- WAF: Redundancy --- +@description('Enable zone redundancy.') +param zoneRedundant bool = false + +@description('Enable automatic failover.') +param enableAutomaticFailover bool = false + +@description('Optional. HA paired region for multi-region failover when redundancy is enabled.') +param haLocation string = '' + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module cosmosAccount 'br/public:avm/res/document-db/database-account:0.19.0' = { + name: take('avm.res.document-db.database-account.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + capabilitiesToAdd: zoneRedundant ? [] : ['EnableServerless'] + sqlDatabases: [ + { + name: databaseName + containers: [for container in containers: { + name: container.name + paths: [container.partitionKeyPath] + kind: 'Hash' + version: 2 + }] + } + ] + sqlRoleAssignments: [] + diagnosticSettings: !empty(diagnosticSettings) ? diagnosticSettings : [] + networkRestrictions: { + networkAclBypass: 'None' + publicNetworkAccess: publicNetworkAccess + } + privateEndpoints: enablePrivateNetworking ? [ + { + name: 'pep-${name}' + customNetworkInterfaceName: 'nic-${name}' + subnetResourceId: privateEndpointSubnetId + service: 'Sql' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: privateDnsZoneConfigs + } + } + ] : [] + zoneRedundant: zoneRedundant + enableAutomaticFailover: enableAutomaticFailover + failoverLocations: zoneRedundant + ? [ + { + failoverPriority: 0 + isZoneRedundant: true + locationName: location + } + { + failoverPriority: 1 + isZoneRedundant: true + locationName: haLocation + } + ] + : [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Cosmos DB account.') +output resourceId string = cosmosAccount.outputs.resourceId + +@description('Name of the Cosmos DB account.') +output name string = cosmosAccount.outputs.name + +@description('Endpoint of the Cosmos DB account.') +output endpoint string = 'https://${name}.documents.azure.com:443/' + +@description('Database name.') +output databaseName string = databaseName + +@description('Container name (first container).') +output containerName string = containers[0].name diff --git a/infra/avm/modules/data/storage-account.bicep b/infra/avm/modules/data/storage-account.bicep new file mode 100644 index 00000000..1ff0e497 --- /dev/null +++ b/infra/avm/modules/data/storage-account.bicep @@ -0,0 +1,135 @@ +// ============================================================================ +// Module: Storage Account +// Description: AVM wrapper for Azure Storage Account with WAF alignment +// AVM Module: avm/res/storage/storage-account:0.32.0 +// WAF: https://learn.microsoft.com/azure/well-architected/service-guides/storage-accounts +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Name of the storage account.') +param name string = take('st${toLower(replace(solutionName, '-', ''))}', 24) + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Storage account SKU.') +param skuName string = 'Standard_LRS' + +@description('Storage account kind.') +param kind string = 'StorageV2' + +@description('Access tier.') +@allowed(['Hot', 'Cool']) +param accessTier string = 'Hot' + +@description('Allow blob public access.') +param allowBlobPublicAccess bool = false + +@description('Allow shared key access.') +param allowSharedKeyAccess bool = true + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Blob containers to create.') +param containers array = [ + { + name: 'default' + publicAccess: 'None' + } +] + +// --- WAF: Monitoring --- +@description('Diagnostic settings for monitoring.') +param diagnosticSettings array = [] + +// --- WAF: Private Networking --- +@description('Public network access setting.') +param publicNetworkAccess string = 'Enabled' + +@description('Network ACLs for the storage account.') +param networkAcls object = { + defaultAction: 'Allow' + bypass: 'AzureServices' +} + +@description('Whether to enable private networking.') +param enablePrivateNetworking bool = false + +@description('Subnet resource ID for the private endpoint.') +param privateEndpointSubnetId string = '' + +@description('Private DNS zone resource IDs for Storage (blob).') +param privateDnsZoneResourceIds array = [] + +var privateDnsZoneConfigs = [for (zoneId, i) in privateDnsZoneResourceIds: { + name: 'dns-zone-${i}' + privateDnsZoneResourceId: zoneId +}] + +// --- Role Assignments --- +@description('Optional. Array of role assignments to create on the Storage Account.') +param roleAssignments array = [] + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module storage 'br/public:avm/res/storage/storage-account:0.32.0' = { + name: take('avm.res.storage.storage-account.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + skuName: skuName + kind: kind + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowSharedKeyAccess: allowSharedKeyAccess + minimumTlsVersion: 'TLS1_2' + supportsHttpsTrafficOnly: true + requireInfrastructureEncryption: true + publicNetworkAccess: publicNetworkAccess + networkAcls: networkAcls + blobServices: { + containers: [for container in containers: { + name: container.name + publicAccess: container.publicAccess + }] + diagnosticSettings: !empty(diagnosticSettings) ? diagnosticSettings : [] + } + diagnosticSettings: !empty(diagnosticSettings) ? diagnosticSettings : [] + privateEndpoints: enablePrivateNetworking ? [ + { + name: 'pep-${name}' + customNetworkInterfaceName: 'nic-${name}' + subnetResourceId: privateEndpointSubnetId + service: 'blob' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: privateDnsZoneConfigs + } + } + ] : [] + roleAssignments: !empty(roleAssignments) ? roleAssignments : [] + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Storage Account.') +output resourceId string = storage.outputs.resourceId + +@description('Name of the Storage Account.') +output name string = storage.outputs.name + +@description('Primary blob endpoint.') +output blobEndpoint string = storage.outputs.primaryBlobEndpoint + +@description('Service endpoints.') +output serviceEndpoints object = storage.outputs.serviceEndpoints diff --git a/infra/avm/modules/identity/cross-scope-role-assignment.bicep b/infra/avm/modules/identity/cross-scope-role-assignment.bicep new file mode 100644 index 00000000..8ed9e333 --- /dev/null +++ b/infra/avm/modules/identity/cross-scope-role-assignment.bicep @@ -0,0 +1,37 @@ +// ============================================================================ +// cross-scope-role-assignment.bicep +// Description: Reusable helper that creates a single role assignment scoped +// to an existing AI Services resource. Used for cross-resource- +// group RBAC where the AI Services lives in a different RG. +// ============================================================================ + +@description('The principal ID to assign the role to.') +param principalId string + +@description('The resource ID of the role definition to assign.') +param roleDefinitionId string + +@description('A unique name for the role assignment.') +param roleAssignmentName string + +@description('The name of the AI Foundry account to scope the role assignment to.') +param aiFoundryName string + +@description('The principal type of the identity being assigned.') +@allowed(['ServicePrincipal', 'User']) +param principalType string = 'ServicePrincipal' + +// Reference the existing AI Foundry resource in this resource group +resource aiFoundryAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: aiFoundryName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: roleAssignmentName + scope: aiFoundryAccount + properties: { + roleDefinitionId: roleDefinitionId + principalId: principalId + principalType: principalType + } +} diff --git a/infra/avm/modules/identity/managed-identity.bicep b/infra/avm/modules/identity/managed-identity.bicep new file mode 100644 index 00000000..f2d264ee --- /dev/null +++ b/infra/avm/modules/identity/managed-identity.bicep @@ -0,0 +1,49 @@ +// ============================================================================ +// Module: Managed Identity +// Description: AVM wrapper for User-Assigned Managed Identity +// AVM Module: avm/res/managed-identity/user-assigned-identity +// Usage: Call this module once per identity from main.bicep +// ============================================================================ + +@description('Solution name used for resource naming.') +param solutionName string + +@description('Name of the managed identity.') +param identityName string = 'id-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module managedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.5.0' = { + name: take('avm.res.managed-identity.user-assigned-identity.${identityName}', 64) + params: { + name: identityName + location: location + tags: tags + enableTelemetry: enableTelemetry + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the managed identity.') +output resourceId string = managedIdentity.outputs.resourceId + +@description('Principal ID of the managed identity.') +output principalId string = managedIdentity.outputs.principalId + +@description('Client ID of the managed identity.') +output clientId string = managedIdentity.outputs.clientId + +@description('Name of the managed identity.') +output name string = managedIdentity.outputs.name diff --git a/infra/avm/modules/identity/role-assignments.bicep b/infra/avm/modules/identity/role-assignments.bicep new file mode 100644 index 00000000..0545ad0c --- /dev/null +++ b/infra/avm/modules/identity/role-assignments.bicep @@ -0,0 +1,280 @@ +// ============================================================================ +// Module: Role Assignments (centralized — all cross-service + data plane RBAC) +// Description: RG-level, cross-service, and data-plane role assignments. +// One place to audit "who has access to what". +// ============================================================================ + +// ============================================================================ +// Parameters +// ============================================================================ + +@description('Solution name suffix for generating unique role assignment GUIDs.') +param solutionName string = '' + +@description('Whether to use an existing AI project (true) or create new (false).') +param useExistingAIProject bool = false + +@description('Resource ID of the existing AI project (for deriving AI Services name/sub/RG).') +param existingFoundryProjectResourceId string = '' + +// --- Identity Principal IDs --- + +@description('Principal ID of the AI project identity.') +param aiProjectPrincipalId string = '' + +@description('Principal ID of the existing AI project identity (for cross-service roles).') +param existingAiProjectPrincipalId string = '' + +@description('Principal ID of the AI Search identity.') +param aiSearchPrincipalId string = '' + +@description('Principal ID of the backend App Service system-assigned identity (empty if not deployed).') +param backendAppServicePrincipalId string = '' + +// --- Resource References --- + +@description('Resource ID of the AI Foundry account (empty if not deployed — new project path).') +param aiFoundryResourceId string = '' + +@description('Resource ID of the AI Search service (empty if not deployed).') +param aiSearchResourceId string = '' + +@description('Resource ID of the Storage Account (empty if not deployed).') +param storageAccountResourceId string = '' + +@description('Name of the Cosmos DB account (empty if not deployed).') +param cosmosDbAccountName string = '' + +// ============================================================================ +// Derived Variables +// ============================================================================ + +var existingAIFoundryName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[8] : '' +var existingAIFoundrySubscription = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[2] : subscription().subscriptionId +var existingAIFoundryResourceGroup = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[4] : resourceGroup().name + +// ============================================================================ +// Role Definitions +// ============================================================================ + +var roleDefinitions = { + azureAiUser: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Foundry User + cognitiveServicesUser: 'a97b65f3-24c7-4388-baec-2e87135dc908' + cognitiveServicesOpenAIUser: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + searchIndexDataReader: '1407120a-92aa-4202-b7e9-c0e197c71c8f' + searchServiceContributor: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' + storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' + storageBlobDataReader: '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1' +} + +// ============================================================================ +// Existing Resource References +// ============================================================================ + +resource aiFoundryAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = if (!empty(aiFoundryResourceId)) { + name: last(split(aiFoundryResourceId, '/')) +} + +resource aiSearchService 'Microsoft.Search/searchServices@2025-05-01' existing = if (!empty(aiSearchResourceId)) { + name: last(split(aiSearchResourceId, '/')) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-08-01' existing = if (!empty(storageAccountResourceId)) { + name: last(split(storageAccountResourceId, '/')) +} + +resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2025-10-15' existing = if (!empty(cosmosDbAccountName)) { + name: cosmosDbAccountName +} + +resource cosmosContributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2025-10-15' existing = if (!empty(cosmosDbAccountName)) { + parent: cosmosAccount + name: '00000000-0000-0000-0000-000000000002' // Cosmos DB Built-in Data Contributor +} + +// ============================================================================ +// 1. AI SERVICES ROLE ASSIGNMENTS +// Cross-service roles scoped to AI Foundry account +// ============================================================================ + +// AI Search → Cognitive Services OpenAI User on AI Foundry (new project, same RG) +resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAIProject && !empty(aiSearchPrincipalId) && !empty(aiFoundryResourceId)) { + name: guid(resourceGroup().id, aiFoundryAccount.id, roleDefinitions.cognitiveServicesOpenAIUser, 'search-openai') + scope: aiFoundryAccount + properties: { + principalId: aiSearchPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.cognitiveServicesOpenAIUser) + principalType: 'ServicePrincipal' + } +} + +// AI Search → Cognitive Services OpenAI User on existing AI Foundry (cross-scope) +module assignOpenAIToSearchExisting './cross-scope-role-assignment.bicep' = if (useExistingAIProject && !empty(aiSearchPrincipalId)) { + name: 'assignOpenAIRoleToAISearchExisting' + scope: resourceGroup(existingAIFoundrySubscription, existingAIFoundryResourceGroup) + params: { + principalId: aiSearchPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.cognitiveServicesOpenAIUser) + roleAssignmentName: guid(solutionName, 'search-openai', existingAIFoundryName, roleDefinitions.cognitiveServicesOpenAIUser) + aiFoundryName: existingAIFoundryName + } +} + +// Backend App Service → Foundry User on AI Foundry (new project, same RG) +resource backendAppAiUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAIProject && !empty(aiFoundryResourceId) && !empty(backendAppServicePrincipalId)) { + name: guid(resourceGroup().id, backendAppServicePrincipalId, roleDefinitions.azureAiUser, 'backend-ai-services') + scope: aiFoundryAccount + properties: { + principalId: backendAppServicePrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.azureAiUser) + principalType: 'ServicePrincipal' + } +} + +// Backend App Service → Foundry User on existing AI Foundry (cross-scope) +module backendAppAiUserExisting './cross-scope-role-assignment.bicep' = if (useExistingAIProject && !empty(backendAppServicePrincipalId)) { + name: 'assignAiUserRoleToBackendExisting' + scope: resourceGroup(existingAIFoundrySubscription, existingAIFoundryResourceGroup) + params: { + principalId: backendAppServicePrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.azureAiUser) + roleAssignmentName: guid(solutionName, 'backend-aiuser', existingAIFoundryName, roleDefinitions.azureAiUser) + aiFoundryName: existingAIFoundryName + } +} + +// ============================================================================ +// 2. SEARCH SERVICE ROLE ASSIGNMENTS +// AI Project and Backend identities → AI Search +// ============================================================================ + +// AI Project → Search Index Data Reader on AI Search (new project) +resource projectSearchReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiSearchResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.searchIndexDataReader, 'project-search') + scope: aiSearchService + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataReader) + principalType: 'ServicePrincipal' + } +} + +// AI Project → Search Service Contributor on AI Search (new project) +resource projectSearchContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiSearchResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.searchServiceContributor, 'project-search') + scope: aiSearchService + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchServiceContributor) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Search Index Data Reader on AI Search +resource existingProjectSearchReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(aiSearchResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.searchIndexDataReader, 'existing-project-search') + scope: aiSearchService + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataReader) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Search Service Contributor on AI Search +resource existingProjectSearchContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(aiSearchResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.searchServiceContributor, 'existing-project-search') + scope: aiSearchService + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchServiceContributor) + principalType: 'ServicePrincipal' + } +} + +// Backend App Service → Search Index Data Reader on AI Search +resource backendAppSearchReaderAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiSearchResourceId) && !empty(backendAppServicePrincipalId)) { + name: guid(resourceGroup().id, backendAppServicePrincipalId, roleDefinitions.searchIndexDataReader, 'backend-search') + scope: aiSearchService + properties: { + principalId: backendAppServicePrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataReader) + principalType: 'ServicePrincipal' + } +} + +// ============================================================================ +// 3. STORAGE ROLE ASSIGNMENTS +// AI Project, AI Search, and Existing Project identities → Storage +// ============================================================================ + +// AI Project → Storage Blob Data Contributor (new project) +resource projectStorageContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(storageAccountResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.storageBlobDataContributor, 'project-storage') + scope: storageAccount + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataContributor) + principalType: 'ServicePrincipal' + } +} + +// AI Project → Storage Blob Data Reader (new project) +resource projectStorageReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(storageAccountResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.storageBlobDataReader, 'project-storage') + scope: storageAccount + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataReader) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Storage Blob Data Contributor +resource existingProjectStorageContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(storageAccountResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.storageBlobDataContributor, 'existing-project-storage') + scope: storageAccount + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataContributor) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Storage Blob Data Reader +resource existingProjectStorageReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(storageAccountResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.storageBlobDataReader, 'existing-project-storage') + scope: storageAccount + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataReader) + principalType: 'ServicePrincipal' + } +} + +// AI Search → Storage Blob Data Reader +resource searchStorageReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(storageAccountResourceId) && !empty(aiSearchPrincipalId)) { + name: guid(resourceGroup().id, aiSearchPrincipalId, roleDefinitions.storageBlobDataReader, 'search-storage') + scope: storageAccount + properties: { + principalId: aiSearchPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataReader) + principalType: 'ServicePrincipal' + } +} + +// ============================================================================ +// 4. COSMOS DB ROLE ASSIGNMENTS +// Backend App Service → Cosmos DB (data-plane, uses sqlRoleAssignments) +// ============================================================================ + +resource backendAppCosmosRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2025-10-15' = if (!empty(cosmosDbAccountName) && !empty(backendAppServicePrincipalId)) { + parent: cosmosAccount + name: guid(cosmosContributorRoleDefinition.id, cosmosAccount.id, backendAppServicePrincipalId) + properties: { + principalId: backendAppServicePrincipalId + roleDefinitionId: cosmosContributorRoleDefinition.id + scope: cosmosAccount.id + } +} + diff --git a/infra/avm/modules/monitoring/app-insights.bicep b/infra/avm/modules/monitoring/app-insights.bicep new file mode 100644 index 00000000..b726ae81 --- /dev/null +++ b/infra/avm/modules/monitoring/app-insights.bicep @@ -0,0 +1,76 @@ +// ============================================================================ +// Module: Application Insights +// Description: AVM wrapper for Application Insights with WAF alignment +// AVM Module: avm/res/insights/component:0.7.1 +// WAF: https://learn.microsoft.com/azure/well-architected/service-guides/application-insights +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Optional. Override name for the Application Insights instance. Defaults to appi-{solutionName}.') +param name string = 'appi-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Resource ID of the Log Analytics workspace to link to.') +param workspaceResourceId string + +@description('Application type.') +param applicationType string = 'web' + +@description('Retention period in days. WAF recommends 365.') +param retentionInDays int = 365 + +@description('Disable IP masking for security. WAF recommends false.') +param disableIpMasking bool = false + +@description('Flow type for Application Insights.') +param flowType string = 'Bluefield' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Kind of Application Insights resource.') +param kind string = 'web' + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module appInsights 'br/public:avm/res/insights/component:0.7.1' = { + name: take('avm.res.insights.component.${name}', 64) + params: { + name: name + location: location + tags: tags + workspaceResourceId: workspaceResourceId + kind: kind + applicationType: applicationType + enableTelemetry: enableTelemetry + retentionInDays: retentionInDays + disableIpMasking: disableIpMasking + flowType: flowType + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Application Insights instance.') +output resourceId string = appInsights.outputs.resourceId + +@description('Name of the Application Insights instance.') +output name string = appInsights.outputs.name + +@description('Instrumentation key for the Application Insights instance.') +output instrumentationKey string = appInsights.outputs.instrumentationKey + +@description('Connection string for the Application Insights instance.') +output connectionString string = appInsights.outputs.connectionString + +@description('Application ID of the Application Insights instance.') +output applicationId string = appInsights.outputs.applicationId diff --git a/infra/avm/modules/monitoring/data-collection-rule.bicep b/infra/avm/modules/monitoring/data-collection-rule.bicep new file mode 100644 index 00000000..c1fd7606 --- /dev/null +++ b/infra/avm/modules/monitoring/data-collection-rule.bicep @@ -0,0 +1,149 @@ +// ============================================================================ +// Module: Data Collection Rule +// Description: AVM wrapper for Azure Monitor Data Collection Rule +// AVM Module: avm/res/insights/data-collection-rule +// WAF: Monitoring for VM observability +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Optional. Override name for the data collection rule. Defaults to dcr-{solutionName}.') +param name string = 'dcr-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Resource ID of the Log Analytics workspace destination.') +param logAnalyticsWorkspaceResourceId string + +@description('Name of the Log Analytics workspace (used for destination naming).') +param logAnalyticsWorkspaceName string = '' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +var dcrLogAnalyticsDestinationName = !empty(logAnalyticsWorkspaceName) ? 'la-${logAnalyticsWorkspaceName}-destination' : 'la-${name}-destination' + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module dataCollectionRule 'br/public:avm/res/insights/data-collection-rule:0.11.0' = { + name: take('avm.res.insights.data-collection-rule.${name}', 64) + params: { + name: name + tags: tags + enableTelemetry: enableTelemetry + location: location + dataCollectionRuleProperties: { + kind: 'Windows' + dataSources: { + performanceCounters: [ + { + streams: ['Microsoft-Perf'] + samplingFrequencyInSeconds: 60 + counterSpecifiers: [ + '\\Processor Information(_Total)\\% Processor Time' + '\\Processor Information(_Total)\\% Privileged Time' + '\\Processor Information(_Total)\\% User Time' + '\\Processor Information(_Total)\\Processor Frequency' + '\\System\\Processes' + '\\Process(_Total)\\Thread Count' + '\\Process(_Total)\\Handle Count' + '\\System\\System Up Time' + '\\System\\Context Switches/sec' + '\\System\\Processor Queue Length' + '\\Memory\\% Committed Bytes In Use' + '\\Memory\\Available Bytes' + '\\Memory\\Committed Bytes' + '\\Memory\\Cache Bytes' + '\\Memory\\Pool Paged Bytes' + '\\Memory\\Pool Nonpaged Bytes' + '\\Memory\\Pages/sec' + '\\Memory\\Page Faults/sec' + '\\Process(_Total)\\Working Set' + '\\Process(_Total)\\Working Set - Private' + '\\LogicalDisk(_Total)\\% Disk Time' + '\\LogicalDisk(_Total)\\% Disk Read Time' + '\\LogicalDisk(_Total)\\% Disk Write Time' + '\\LogicalDisk(_Total)\\% Idle Time' + '\\LogicalDisk(_Total)\\Disk Bytes/sec' + '\\LogicalDisk(_Total)\\Disk Read Bytes/sec' + '\\LogicalDisk(_Total)\\Disk Write Bytes/sec' + '\\LogicalDisk(_Total)\\Disk Transfers/sec' + '\\LogicalDisk(_Total)\\Disk Reads/sec' + '\\LogicalDisk(_Total)\\Disk Writes/sec' + '\\LogicalDisk(_Total)\\Avg. Disk sec/Transfer' + '\\LogicalDisk(_Total)\\Avg. Disk sec/Read' + '\\LogicalDisk(_Total)\\Avg. Disk sec/Write' + '\\LogicalDisk(_Total)\\Avg. Disk Queue Length' + '\\LogicalDisk(_Total)\\Avg. Disk Read Queue Length' + '\\LogicalDisk(_Total)\\Avg. Disk Write Queue Length' + '\\LogicalDisk(_Total)\\% Free Space' + '\\LogicalDisk(_Total)\\Free Megabytes' + '\\Network Interface(*)\\Bytes Total/sec' + '\\Network Interface(*)\\Bytes Sent/sec' + '\\Network Interface(*)\\Bytes Received/sec' + '\\Network Interface(*)\\Packets/sec' + '\\Network Interface(*)\\Packets Sent/sec' + '\\Network Interface(*)\\Packets Received/sec' + '\\Network Interface(*)\\Packets Outbound Errors' + '\\Network Interface(*)\\Packets Received Errors' + ] + name: 'perfCounterDataSource60' + } + ] + windowsEventLogs: [ + { + name: 'SecurityAuditEvents' + streams: ['Microsoft-WindowsEvent'] + xPathQueries: [ + 'Security!*[System[(EventID=4624 or EventID=4625)]]' + ] + } + { + name: 'AuditSuccessFailure' + streams: ['Microsoft-Event'] + xPathQueries: [ + 'Security!*[System[(band(Keywords,13510798882111488)) and (EventID != 4624)]]' + ] + } + ] + } + destinations: { + logAnalytics: [ + { + workspaceResourceId: logAnalyticsWorkspaceResourceId + name: dcrLogAnalyticsDestinationName + } + ] + } + dataFlows: [ + { + streams: ['Microsoft-Perf'] + destinations: [dcrLogAnalyticsDestinationName] + transformKql: 'source' + outputStream: 'Microsoft-Perf' + } + { + streams: ['Microsoft-Event'] + destinations: [dcrLogAnalyticsDestinationName] + transformKql: 'source' + outputStream: 'Microsoft-Event' + } + ] + } + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the data collection rule.') +output resourceId string = dataCollectionRule.outputs.resourceId + +@description('Name of the data collection rule.') +output name string = dataCollectionRule.outputs.name diff --git a/infra/avm/modules/monitoring/log-analytics.bicep b/infra/avm/modules/monitoring/log-analytics.bicep new file mode 100644 index 00000000..3b231240 --- /dev/null +++ b/infra/avm/modules/monitoring/log-analytics.bicep @@ -0,0 +1,90 @@ +// ============================================================================ +// Module: Log Analytics Workspace +// Description: AVM wrapper for Log Analytics Workspace with WAF alignment +// AVM Module: avm/res/operational-insights/workspace:0.15.0 +// WAF: https://learn.microsoft.com/azure/well-architected/service-guides/azure-log-analytics +// Note: This module only handles NEW workspace creation. +// Existing workspace logic is handled in main.bicep. +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Optional. Override name for the Log Analytics workspace. Defaults to log-{solutionName}.') +param name string = 'log-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Retention period in days. WAF recommends 365.') +param retentionInDays int = 365 + +@description('SKU name for the workspace.') +param skuName string = 'PerGB2018' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// --- WAF: Private Networking --- +@description('Public network access for ingestion.') +param publicNetworkAccessForIngestion string = 'Enabled' + +@description('Public network access for query.') +param publicNetworkAccessForQuery string = 'Enabled' + +// --- WAF: Redundancy --- +@description('Enable workspace replication for redundancy.') +param enableReplication bool = false + +@description('Replication location (paired region).') +param replicationLocation string = '' + +@description('Daily quota in GB. WAF recommends 150 GB/day as starting point.') +param dailyQuotaGb string = '' + +// --- WAF: Monitoring (VM data sources for private networking) --- +@description('Data sources for VM monitoring (Windows events, perf counters).') +param dataSources array = [] + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module workspace 'br/public:avm/res/operational-insights/workspace:0.15.0' = { + name: take('avm.res.operational-insights.workspace.${name}', 64) + params: { + name: name + location: location + tags: tags + dataRetention: retentionInDays + skuName: skuName + enableTelemetry: enableTelemetry + features: { enableLogAccessUsingOnlyResourcePermissions: true } + diagnosticSettings: [{ useThisWorkspace: true }] + publicNetworkAccessForIngestion: publicNetworkAccessForIngestion + publicNetworkAccessForQuery: publicNetworkAccessForQuery + dailyQuotaGb: !empty(dailyQuotaGb) ? dailyQuotaGb : null + replication: enableReplication ? { + enabled: true + location: replicationLocation + } : null + dataSources: !empty(dataSources) ? dataSources : null + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Log Analytics workspace.') +output resourceId string = workspace.outputs.resourceId + +@description('Name of the Log Analytics workspace.') +output name string = workspace.outputs.name + +@description('Location of the workspace.') +output location string = location + +@description('Log Analytics workspace customer ID.') +output logAnalyticsWorkspaceId string = workspace.outputs.logAnalyticsWorkspaceId diff --git a/infra/avm/modules/networking/bastion-host.bicep b/infra/avm/modules/networking/bastion-host.bicep new file mode 100644 index 00000000..bf524087 --- /dev/null +++ b/infra/avm/modules/networking/bastion-host.bicep @@ -0,0 +1,85 @@ +// ============================================================================ +// Module: Bastion Host +// Description: AVM wrapper for Azure Bastion Host +// AVM Module: avm/res/network/bastion-host +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +var name = 'bas-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Resource ID of the virtual network.') +param virtualNetworkResourceId string + +@description('Optional. Diagnostic settings for the resource.') +param diagnosticSettings array? + +@description('SKU name for the Bastion Host.') +param skuName string = 'Standard' + +@description('Number of scale units.') +param scaleUnits int = 4 + +@description('Disable copy/paste functionality.') +param disableCopyPaste bool = true + +@description('Enable file copy functionality.') +param enableFileCopy bool = false + +@description('Enable IP Connect functionality.') +param enableIpConnect bool = false + +@description('Enable shareable link functionality.') +param enableShareableLink bool = false + +@description('Availability zones for the Bastion Host public IP. Pass empty array to disable zone redundancy.') +param availabilityZones array = [] + +@description('Optional. Diagnostic settings for the public IP address.') +param publicIPDiagnosticSettings array? + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module bastionHost 'br/public:avm/res/network/bastion-host:0.8.2' = { + name: take('avm.res.network.bastion-host.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + skuName: skuName + virtualNetworkResourceId: virtualNetworkResourceId + availabilityZones: availabilityZones + publicIPAddressObject: { + name: 'pip-${name}' + diagnosticSettings: publicIPDiagnosticSettings + tags: tags + } + disableCopyPaste: disableCopyPaste + enableFileCopy: enableFileCopy + enableIpConnect: enableIpConnect + enableShareableLink: enableShareableLink + scaleUnits: scaleUnits + diagnosticSettings: diagnosticSettings + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Bastion Host.') +output resourceId string = bastionHost.outputs.resourceId + +@description('Name of the Bastion Host.') +output name string = bastionHost.outputs.name diff --git a/infra/avm/modules/networking/private-dns-zone.bicep b/infra/avm/modules/networking/private-dns-zone.bicep new file mode 100644 index 00000000..24bd2892 --- /dev/null +++ b/infra/avm/modules/networking/private-dns-zone.bicep @@ -0,0 +1,40 @@ +// ============================================================================ +// Module: Private DNS Zone +// Description: AVM wrapper for Azure Private DNS Zone +// AVM Module: avm/res/network/private-dns-zone +// Usage: Call once per DNS zone from main.bicep +// ============================================================================ + +@description('Name of the private DNS zone (e.g., privatelink.cognitiveservices.azure.com).') +param name string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Virtual network links to associate with the DNS zone.') +param virtualNetworkLinks array = [] + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module privateDnsZone 'br/public:avm/res/network/private-dns-zone:0.8.1' = { + name: take('avm.res.network.private-dns-zone.${name}', 64) + params: { + name: name + tags: tags + enableTelemetry: enableTelemetry + virtualNetworkLinks: virtualNetworkLinks + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the private DNS zone.') +output resourceId string = privateDnsZone.outputs.resourceId + +@description('Name of the private DNS zone.') +output name string = privateDnsZone.outputs.name diff --git a/infra/avm/modules/networking/private-endpoint.bicep b/infra/avm/modules/networking/private-endpoint.bicep new file mode 100644 index 00000000..04bfff07 --- /dev/null +++ b/infra/avm/modules/networking/private-endpoint.bicep @@ -0,0 +1,52 @@ +// ============================================================================ +// Module: Private Endpoint +// Description: AVM wrapper for Azure Private Endpoint +// AVM Module: avm/res/network/private-endpoint +// Usage: Call once per private endpoint from main.bicep +// ============================================================================ + +@description('Name of the private endpoint.') +param name string + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Optional. Custom NIC name for the private endpoint.') +param customNetworkInterfaceName string = '' + +@description('Resource ID of the subnet for the private endpoint.') +param subnetResourceId string + +@description('Private link service connections configuration.') +param privateLinkServiceConnections array + +@description('Optional. Private DNS zone group configuration.') +param privateDnsZoneGroup object? + +// ============================================================================ +// AVM Module Deployment +// ============================================================================ +module privateEndpoint 'br/public:avm/res/network/private-endpoint:0.12.0' = { + name: take('avm.res.network.private-endpoint.${name}', 64) + params: { + name: name + location: location + tags: tags + customNetworkInterfaceName: !empty(customNetworkInterfaceName) ? customNetworkInterfaceName : 'nic-${name}' + subnetResourceId: subnetResourceId + privateLinkServiceConnections: privateLinkServiceConnections + privateDnsZoneGroup: privateDnsZoneGroup + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the private endpoint.') +output resourceId string = privateEndpoint.outputs.resourceId + +@description('Name of the private endpoint.') +output name string = privateEndpoint.outputs.name diff --git a/infra/avm/modules/networking/virtual-network.bicep b/infra/avm/modules/networking/virtual-network.bicep new file mode 100644 index 00000000..241e39d5 --- /dev/null +++ b/infra/avm/modules/networking/virtual-network.bicep @@ -0,0 +1,303 @@ +// ============================================================================ +// Module: Virtual Network +// Description: VNet, Subnets, and NSGs using AVM modules. +// Each subnet gets its own NSG. Subnet config is passed as param. +// AVM Modules: +// - avm/res/network/network-security-group:0.5.3 +// - avm/res/network/virtual-network:0.8.0 +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +var name = 'vnet-${solutionName}' + +@description('Azure region for the resource.') +param location string = resourceGroup().location + +@description('Address prefixes for the virtual network.') +param addressPrefixes array + +@description('Subnet configurations.') +param subnets subnetType[] = [ + { + name: 'backend' + addressPrefixes: ['10.0.0.0/27'] + networkSecurityGroup: { + name: 'nsg-backend' + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: ['22', '3389'] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } + } + { + name: 'webserverfarm' + addressPrefixes: ['10.0.4.0/27'] + delegation: 'Microsoft.Web/serverfarms' + privateEndpointNetworkPolicies: 'Enabled' + privateLinkServiceNetworkPolicies: 'Enabled' + networkSecurityGroup: { + name: 'nsg-webserverfarm' + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: ['22', '3389'] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } + } + { + name: 'administration' + addressPrefixes: ['10.0.0.32/27'] + networkSecurityGroup: { + name: 'nsg-administration' + securityRules: [ + { + name: 'deny-hop-outbound' + properties: { + access: 'Deny' + destinationAddressPrefix: '*' + destinationPortRanges: ['22', '3389'] + direction: 'Outbound' + priority: 200 + protocol: 'Tcp' + sourceAddressPrefix: 'VirtualNetwork' + sourcePortRange: '*' + } + } + ] + } + } + { + name: 'AzureBastionSubnet' + addressPrefixes: ['10.0.0.64/26'] + networkSecurityGroup: { + name: 'nsg-bastion' + securityRules: [ + { + name: 'AllowGatewayManager' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 2702 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'GatewayManager' + destinationAddressPrefix: '*' + } + } + { + name: 'AllowHttpsInBound' + properties: { + access: 'Allow' + direction: 'Inbound' + priority: 2703 + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'Internet' + destinationAddressPrefix: '*' + } + } + { + name: 'AllowSshRdpOutbound' + properties: { + access: 'Allow' + direction: 'Outbound' + priority: 100 + protocol: '*' + sourcePortRange: '*' + destinationPortRanges: ['22', '3389'] + sourceAddressPrefix: '*' + destinationAddressPrefix: 'VirtualNetwork' + } + } + { + name: 'AllowAzureCloudOutbound' + properties: { + access: 'Allow' + direction: 'Outbound' + priority: 110 + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'AzureCloud' + } + } + ] + } + } +] + +@description('Tags to apply to the resources.') +param tags object = {} + +@description('Resource ID of the Log Analytics Workspace for diagnostics.') +param logAnalyticsWorkspaceId string + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Suffix for resource naming.') +param resourceSuffix string + +// ============================================================================ +// NSGs — one per subnet +// ============================================================================ +@batchSize(1) +module nsgs 'br/public:avm/res/network/network-security-group:0.5.3' = [ + for (subnet, i) in subnets: if (!empty(subnet.?networkSecurityGroup)) { + name: take('avm.res.network.nsg.${subnet.?networkSecurityGroup.name}.${resourceSuffix}', 64) + params: { + name: '${subnet.?networkSecurityGroup.name}-${resourceSuffix}' + location: location + securityRules: subnet.?networkSecurityGroup.securityRules + tags: tags + enableTelemetry: enableTelemetry + } + } +] + +// ============================================================================ +// Virtual Network + Subnets +// ============================================================================ +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.8.0' = { + name: take('avm.res.network.virtual-network.${name}', 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 + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +output name string = virtualNetwork.outputs.name +output resourceId string = virtualNetwork.outputs.resourceId + +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 + } +] + +// Individual subnet outputs for backward compatibility +output backendSubnetResourceId string = contains(map(subnets, subnet => subnet.name), 'backend') + ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(subnets, subnet => subnet.name), 'backend')] + : '' +output webserverfarmSubnetResourceId string = contains(map(subnets, subnet => subnet.name), 'webserverfarm') + ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(subnets, subnet => subnet.name), 'webserverfarm')] + : '' +output administrationSubnetResourceId string = contains(map(subnets, subnet => subnet.name), 'administration') + ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(subnets, subnet => subnet.name), 'administration')] + : '' +output bastionSubnetResourceId string = contains(map(subnets, subnet => subnet.name), 'AzureBastionSubnet') + ? virtualNetwork.outputs.subnetResourceIds[indexOf(map(subnets, subnet => subnet.name), 'AzureBastionSubnet')] + : '' + +// ============================================================================ +// Custom Types +// ============================================================================ +@export() +@description('Subnet output type') +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 NSG, if any.') + nsgName: string? + @description('The resource ID of the associated NSG, if any.') + nsgResourceId: string? +} + +@export() +@description('Subnet configuration type') +type subnetType = { + @description('Required. The name of the subnet.') + name: string + @description('Required. Address prefixes for the subnet.') + addressPrefixes: string[] + @description('Optional. Delegation for the subnet.') + delegation: string? + @description('Optional. Private endpoint network policies.') + privateEndpointNetworkPolicies: ('Disabled' | 'Enabled' | 'NetworkSecurityGroupEnabled' | 'RouteTableEnabled')? + @description('Optional. Private link service network policies.') + privateLinkServiceNetworkPolicies: ('Disabled' | 'Enabled')? + @description('Optional. NSG configuration for the subnet.') + networkSecurityGroup: networkSecurityGroupType? + @description('Optional. Route table resource ID.') + routeTableResourceId: string? + @description('Optional. Service endpoint policies.') + serviceEndpointPolicies: object[]? + @description('Optional. Service endpoints to enable.') + serviceEndpoints: string[]? + @description('Optional. Disable default outbound connectivity.') + defaultOutboundAccess: bool? +} + +@export() +@description('NSG configuration type') +type networkSecurityGroupType = { + @description('Required. The name of the NSG.') + name: string + @description('Required. Security rules for the NSG.') + securityRules: object[] +} diff --git a/infra/bicep/main.bicep b/infra/bicep/main.bicep new file mode 100644 index 00000000..a0730863 --- /dev/null +++ b/infra/bicep/main.bicep @@ -0,0 +1,510 @@ +// ========== main.bicep ========== // +targetScope = 'resourceGroup' + +// ============================================================================== +// Parameters +// ============================================================================== + +// ── Core ── + +@minLength(3) +@maxLength(16) +@description('Required. A unique application/solution name for all resources in this deployment.') +param solutionName string = 'containermig' + +@maxLength(5) +@description('Optional. A unique text suffix appended to resource names for uniqueness.') +param solutionUniqueText string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) + +@description('Optional. Primary Azure region for resource deployment. Defaults to resource group location.') +param location string = '' + +@allowed([ + 'australiaeast' + 'eastus' + 'eastus2' + 'francecentral' + 'japaneast' + 'norwayeast' + 'southindia' + 'swedencentral' + 'uksouth' + 'westus' + 'westus3' +]) +@metadata({ + azd: { + type: 'location' + usageName: [ + 'OpenAI.GlobalStandard.gpt-5.1,500' + ] + } +}) +@description('Required. Location for AI Foundry and model deployments.') +param azureAiServiceLocation string + +@description('Optional. Secondary location for Cosmos DB resources.') +param cosmosLocation string = 'eastus2' + +// ── AI Configuration ── + +@allowed([ + 'Standard' + 'GlobalStandard' +]) +@description('Optional. GPT model deployment type.') +param deploymentType string = 'GlobalStandard' + +@description('Optional. Name of the GPT model to deploy.') +param gptModelName string = 'gpt-5.1' + +@description('Optional. Version of the GPT model.') +param gptModelVersion string = '2025-11-13' + +@description('Optional. GPT model deployment capacity (tokens per minute in thousands).') +param gptDeploymentCapacity int = 500 + +@description('Optional. Name of the embedding model to deploy.') +param embeddingModel string = 'text-embedding-3-large' + +@description('Optional. Version of the embedding model.') +param embeddingModelVersion string = '1' + +@allowed([ + 'Standard' + 'GlobalStandard' +]) +@description('Optional. Embedding model deployment type.') +param embeddingDeploymentType string = 'GlobalStandard' + +@description('Optional. Embedding model deployment capacity.') +param embeddingDeploymentCapacity int = 500 + +// ── Container Apps Configuration ── + +@description('Optional. The endpoint (excluding https://) of an existing container registry.') +param containerRegistryEndpoint string = 'containermigrationacr.azurecr.io' + +@description('Optional. The image tag to use for container images.') +param imageTag string = 'latest_v2' + +// ── Identity ── + +@description('Optional. Resource ID of an existing Foundry project.') +param existingFoundryProjectResourceId string = '' + +@description('Optional. Existing Log Analytics Workspace Resource ID.') +param existingLogAnalyticsWorkspaceId string = '' + +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags object = {} + +// ============================================================================== +// Variables +// ============================================================================== + +var solutionLocation = empty(location) ? resourceGroup().location : location + +var solutionSuffix = toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueText}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) + +var deployerInfo = deployer() +var deployingUserPrincipalId = deployerInfo.objectId +var deployingUserPrincipalType = contains(deployerInfo, 'userPrincipalName') ? 'User' : 'ServicePrincipal' + +var createdBy = contains(deployerInfo, 'userPrincipalName') + ? split(deployerInfo.userPrincipalName, '@')[0] + : deployerInfo.objectId + +var existingTags = resourceGroup().tags ?? {} +var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) + +var aiModelDeployments = [ + { + name: gptModelName + model: gptModelName + sku: { + name: deploymentType + capacity: gptDeploymentCapacity + } + version: gptModelVersion + raiPolicyName: 'Microsoft.Default' + } + { + name: embeddingModel + model: embeddingModel + sku: { + name: embeddingDeploymentType + capacity: embeddingDeploymentCapacity + } + version: embeddingModelVersion + raiPolicyName: 'Microsoft.Default' + } +] + +// Cosmos DB containers for migration solution +var cosmosDatabaseName = 'migration_db' +var cosmosContainers = [ + { name: 'processes', partitionKeyPath: '/_partitionKey' } + { name: 'agent_telemetry', partitionKeyPath: '/_partitionKey' } + { name: 'processcontrol', partitionKeyPath: '/_partitionKey' } + { name: 'files', partitionKeyPath: '/_partitionKey' } + { name: 'process_statuses', partitionKeyPath: '/_partitionKey' } +] + +// Storage containers +var processBlobContainerName = 'processes' +var processQueueName = 'processes-queue' + +// ========== Resource Group Tag ========== // +resource resourceGroupTags 'Microsoft.Resources/tags@2023-07-01' = { + name: 'default' + properties: { + tags: union( + existingTags, + tags, + { + TemplateName: 'Container Migration' + CreatedBy: createdBy + DeploymentName: deployment().name + Type: 'Non-WAF' + } + ) + } +} + +// ========== Monitoring (Log Analytics) ========== // + +module log_analytics './modules/monitoring/log-analytics.bicep' = if (!useExistingLogAnalytics) { + name: take('module.log-analytics.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + } + scope: resourceGroup(resourceGroup().name) +} + +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics + ? existingLogAnalyticsWorkspaceId + : log_analytics!.outputs.resourceId + +// ========== AI Foundry and related resources ========== // + +// Deploy new AI Services account + AI Foundry project (no connections, no deployments) +module ai_foundry_project './modules/ai/ai-foundry-project.bicep' = if (empty(existingFoundryProjectResourceId)) { + name: take('module.ai-foundry-project.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: azureAiServiceLocation + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Unified AI Foundry resource name vars ========== // +var useExistingAIProject = !empty(existingFoundryProjectResourceId) +var aiFoundryResourceName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[8] : ai_foundry_project!.outputs.name +var aiProjectResourceName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[10] : ai_foundry_project!.outputs.projectName +var aiServiceSubscription = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[2] : subscription().subscriptionId +var aiServiceResourceGroup = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[4] : resourceGroup().name + +// Reference existing AI Foundry project (identity only) +module existing_project_setup './modules/ai/existing-project-setup.bicep' = if (useExistingAIProject) { + name: take('module.existing-project-setup.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + name: aiFoundryResourceName + projectName: aiProjectResourceName + } +} + +// Storage Blob connection (single call for both existing and new paths) +module foundry_storage_connection './modules/ai/ai-foundry-connection.bicep' = { + name: take('module.foundry-storage-conn.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + solutionName: solutionSuffix + aiServicesAccountName: aiFoundryResourceName + projectName: aiProjectResourceName + category: 'AzureBlob' + target: storage_account.outputs.blobEndpoint + authType: 'AAD' + metadata: { + ResourceId: storage_account.outputs.resourceId + AccountName: storage_account.outputs.name + ContainerName: 'default' + } + } +} + +// Model deployments (single loop for both existing and new paths) +@batchSize(1) +module model_deployments './modules/ai/ai-foundry-model-deployment.bicep' = [for (deployment, i) in aiModelDeployments: { + name: take('module.model-deployment-${i}.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + aiServicesAccountName: aiFoundryResourceName + deploymentName: deployment.name + modelName: deployment.model + modelVersion: deployment.version + raiPolicyName: deployment.raiPolicyName + skuName: deployment.sku.name + skuCapacity: deployment.sku.capacity + } +}] + +// ========== AI outputs (ternary: existing vs new) ========== // +var aiFoundryEndpoint = useExistingAIProject ? existing_project_setup!.outputs.aiFoundryEndpoint : ai_foundry_project!.outputs.endpoint +var projectEndpoint = useExistingAIProject ? existing_project_setup!.outputs.projectEndpoint : ai_foundry_project!.outputs.projectEndpoint +var aiFoundryResourceId = !useExistingAIProject ? ai_foundry_project!.outputs.resourceId : '' +var aiProjectPrincipalId = useExistingAIProject ? existing_project_setup!.outputs.aiProjectPrincipalId : ai_foundry_project!.outputs.projectIdentityPrincipalId + +// ========== Storage Account module ========== // +module storage_account './modules/data/storage-account.bicep' = { + name: take('module.storage-account.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + tags: tags + containers: [ + { name: 'data', publicAccess: 'None' } + ] + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Cosmos DB module ========== // +module cosmosDBModule './modules/data/cosmos-db.bicep' = { + name: take('module.cosmos-db.${solutionName}', 64) + params: { + solutionName: solutionSuffix + name: 'cosmos-${solutionSuffix}' + location: cosmosLocation + databaseName: cosmosDatabaseName + containers: cosmosContainers + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Container App Environment ========== // +module containerAppEnv './modules/compute/container-app-environment.bicep' = { + name: take('module.container-app-env.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + } +} + +// ========== Container Apps ========== // +var backendContainerAppName = take('ca-backend-api-${solutionSuffix}', 32) +var processorContainerAppName = take('ca-processor-${solutionSuffix}', 32) +var frontEndContainerAppName = take('ca-frontend-${solutionSuffix}', 32) + +// ========== Backend API Container App ========== // +module ca_backend_api './modules/compute/container-app.bicep' = { + name: take('module.ca-backend-api.${solutionName}', 64) + params: { + name: backendContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: true + ingressTargetPort: 80 + containers: [ + { + name: 'backend-api' + image: '${containerRegistryEndpoint}/backend-api:${imageTag}' + env: [ + { name: 'AZURE_OPENAI_ENDPOINT', value: aiFoundryEndpoint } + { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT_NAME', value: gptModelName } + { name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME', value: embeddingModel } + { name: 'AZURE_OPENAI_API_VERSION', value: '2025-03-01-preview' } + { name: 'COSMOS_DB_ACCOUNT_URL', value: cosmosDBModule.outputs.endpoint } + { name: 'COSMOS_DB_DATABASE_NAME', value: cosmosDatabaseName } + { name: 'COSMOS_DB_CONTAINER_NAME', value: 'agent_telemetry' } + { name: 'COSMOS_DB_CONTROL_CONTAINER_NAME', value: 'processcontrol' } + { name: 'COSMOS_DB_PROCESS_CONTAINER', value: 'processes' } + { name: 'COSMOS_DB_PROCESS_LOG_CONTAINER', value: 'agent_telemetry' } + { name: 'STORAGE_ACCOUNT_BLOB_URL', value: storage_account.outputs.blobEndpoint } + { name: 'STORAGE_ACCOUNT_NAME', value: storage_account.outputs.name } + { name: 'STORAGE_ACCOUNT_PROCESS_CONTAINER', value: processBlobContainerName } + { name: 'STORAGE_ACCOUNT_PROCESS_QUEUE', value: processQueueName } + { name: 'STORAGE_ACCOUNT_QUEUE_URL', value: '${storage_account.outputs.serviceEndpoints.queue}' } + { name: 'GLOBAL_LLM_SERVICE', value: 'AzureOpenAI' } + { name: 'PROCESSOR_CONTROL_URL', value: 'https://${processorContainerAppName}.internal.${containerAppEnv.outputs.defaultDomain}' } + { name: 'APP_ENV', value: 'Prod' } + ] + resources: { + cpu: json('1') + memory: '2.0Gi' + } + } + ] + corsPolicy: { + allowedOrigins: ['*'] + allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] + allowedHeaders: ['Authorization', 'Content-Type', '*'] + } + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Frontend Container App ========== // +module ca_frontend './modules/compute/container-app.bicep' = { + name: take('module.ca-frontend.${solutionName}', 64) + params: { + name: frontEndContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: true + ingressTargetPort: 3000 + containers: [ + { + name: 'frontend' + image: '${containerRegistryEndpoint}/frontend:${imageTag}' + env: [ + { name: 'API_URL', value: 'https://${ca_backend_api.outputs.fqdn}' } + { name: 'APP_ENV', value: 'prod' } + { name: 'REACT_APP_MSAL_POST_REDIRECT_URL', value: '/' } + { name: 'REACT_APP_MSAL_REDIRECT_URL', value: '/' } + { name: 'ALLOWED_ORIGINS', value: 'https://${frontEndContainerAppName}.${containerAppEnv.outputs.defaultDomain}' } + ] + resources: { + cpu: json('1') + memory: '2.0Gi' + } + } + ] + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Processor Container App ========== // +module ca_processor './modules/compute/container-app.bicep' = { + name: take('module.ca-processor.${solutionName}', 64) + params: { + name: processorContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: false + ingressTargetPort: 8080 + ingressAllowInsecure: true + containers: [ + { + name: 'processor' + image: '${containerRegistryEndpoint}/processor:${imageTag}' + env: [ + { name: 'AZURE_OPENAI_ENDPOINT', value: aiFoundryEndpoint } + { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT_NAME', value: gptModelName } + { name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME', value: embeddingModel } + { name: 'AZURE_OPENAI_API_VERSION', value: '2025-03-01-preview' } + { name: 'COSMOS_DB_ACCOUNT_URL', value: cosmosDBModule.outputs.endpoint } + { name: 'COSMOS_DB_DATABASE_NAME', value: cosmosDatabaseName } + { name: 'COSMOS_DB_CONTAINER_NAME', value: 'agent_telemetry' } + { name: 'COSMOS_DB_CONTROL_CONTAINER_NAME', value: 'processcontrol' } + { name: 'COSMOS_DB_PROCESS_CONTAINER', value: 'processes' } + { name: 'COSMOS_DB_PROCESS_LOG_CONTAINER', value: 'agent_telemetry' } + { name: 'STORAGE_ACCOUNT_BLOB_URL', value: storage_account.outputs.blobEndpoint } + { name: 'STORAGE_ACCOUNT_NAME', value: storage_account.outputs.name } + { name: 'STORAGE_ACCOUNT_PROCESS_CONTAINER', value: processBlobContainerName } + { name: 'STORAGE_ACCOUNT_PROCESS_QUEUE', value: processQueueName } + { name: 'STORAGE_ACCOUNT_QUEUE_URL', value: '${storage_account.outputs.serviceEndpoints.queue}' } + { name: 'GLOBAL_LLM_SERVICE', value: 'AzureOpenAI' } + { name: 'CONTROL_API_ENABLED', value: '1' } + { name: 'CONTROL_API_PORT', value: '8080' } + ] + resources: { + cpu: json('2') + memory: '4.0Gi' + } + } + ] + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Role Assignments ========== // +module role_assignments './modules/identity/role-assignments.bicep' = { + name: take('module.role-assignments.${solutionName}', 64) + params: { + solutionName: solutionSuffix + useExistingAIProject: useExistingAIProject + existingFoundryProjectResourceId: existingFoundryProjectResourceId + aiFoundryResourceId: !useExistingAIProject ? aiFoundryResourceId : '' + aiSearchResourceId: '' + storageAccountResourceId: storage_account.outputs.resourceId + aiProjectPrincipalId: aiProjectPrincipalId + aiSearchPrincipalId: '' + deployerPrincipalId: deployingUserPrincipalId + deployerPrincipalType: deployingUserPrincipalType + backendAppServicePrincipalId: ca_backend_api.outputs.principalId + cosmosDbAccountName: cosmosDBModule.outputs.name + existingAiProjectPrincipalId: !empty(existingFoundryProjectResourceId) ? existing_project_setup!.outputs.aiProjectPrincipalId : '' + } + scope: resourceGroup(resourceGroup().name) +} + +// ============================================================================== +// Outputs +// ============================================================================== + +@description('Solution suffix used for naming resources') +output SOLUTION_NAME string = solutionSuffix + +@description('Name of the deployed resource group') +output RESOURCE_GROUP_NAME string = resourceGroup().name + +@description('The name of the web app container app.') +output CONTAINER_WEB_APP_NAME string = ca_frontend.outputs.name + +@description('The FQDN of the web app container app.') +output CONTAINER_WEB_APP_FQDN string = ca_frontend.outputs.fqdn + +@description('The name of the API container app.') +output CONTAINER_API_APP_NAME string = ca_backend_api.outputs.name + +@description('The FQDN of the API container app.') +output CONTAINER_API_APP_FQDN string = ca_backend_api.outputs.fqdn + +@description('Azure OpenAI service endpoint URL') +output AZURE_OPENAI_ENDPOINT string = aiFoundryEndpoint + +@description('GPT model deployment name') +output AZURE_ENV_GPT_MODEL_NAME string = gptModelName + +@description('Embedding model deployment name') +output AZURE_ENV_EMBEDDING_DEPLOYMENT_NAME string = embeddingModel + +@description('Cosmos DB account name') +output AZURE_COSMOSDB_ACCOUNT string = cosmosDBModule.outputs.name + +@description('Cosmos DB database name') +output AZURE_COSMOSDB_DATABASE string = cosmosDatabaseName + +@description('The Azure subscription ID.') +output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId + +@description('The Azure resource group name.') +output AZURE_RESOURCE_GROUP string = resourceGroup().name + +@description('Azure AI Agent service endpoint URL') +output AZURE_AI_AGENT_ENDPOINT string = projectEndpoint diff --git a/infra/bicep/main.json b/infra/bicep/main.json new file mode 100644 index 00000000..2757538e --- /dev/null +++ b/infra/bicep/main.json @@ -0,0 +1,3150 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "14848162840202561752" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "defaultValue": "containermig", + "minLength": 3, + "maxLength": 16, + "metadata": { + "description": "Required. A unique application/solution name for all resources in this deployment." + } + }, + "solutionUniqueText": { + "type": "string", + "defaultValue": "[substring(uniqueString(subscription().id, resourceGroup().name, parameters('solutionName')), 0, 5)]", + "maxLength": 5, + "metadata": { + "description": "Optional. A unique text suffix appended to resource names for uniqueness." + } + }, + "location": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Primary Azure region for resource deployment. Defaults to resource group location." + } + }, + "azureAiServiceLocation": { + "type": "string", + "allowedValues": [ + "australiaeast", + "eastus", + "eastus2", + "francecentral", + "japaneast", + "norwayeast", + "southindia", + "swedencentral", + "uksouth", + "westus", + "westus3" + ], + "metadata": { + "azd": { + "type": "location", + "usageName": [ + "OpenAI.GlobalStandard.gpt-5.1,500" + ] + }, + "description": "Required. Location for AI Foundry and model deployments." + } + }, + "cosmosLocation": { + "type": "string", + "defaultValue": "eastus2", + "metadata": { + "description": "Optional. Secondary location for Cosmos DB resources." + } + }, + "deploymentType": { + "type": "string", + "defaultValue": "GlobalStandard", + "allowedValues": [ + "Standard", + "GlobalStandard" + ], + "metadata": { + "description": "Optional. GPT model deployment type." + } + }, + "gptModelName": { + "type": "string", + "defaultValue": "gpt-5.1", + "metadata": { + "description": "Optional. Name of the GPT model to deploy." + } + }, + "gptModelVersion": { + "type": "string", + "defaultValue": "2025-11-13", + "metadata": { + "description": "Optional. Version of the GPT model." + } + }, + "gptDeploymentCapacity": { + "type": "int", + "defaultValue": 500, + "metadata": { + "description": "Optional. GPT model deployment capacity (tokens per minute in thousands)." + } + }, + "embeddingModel": { + "type": "string", + "defaultValue": "text-embedding-3-large", + "metadata": { + "description": "Optional. Name of the embedding model to deploy." + } + }, + "embeddingModelVersion": { + "type": "string", + "defaultValue": "1", + "metadata": { + "description": "Optional. Version of the embedding model." + } + }, + "embeddingDeploymentType": { + "type": "string", + "defaultValue": "GlobalStandard", + "allowedValues": [ + "Standard", + "GlobalStandard" + ], + "metadata": { + "description": "Optional. Embedding model deployment type." + } + }, + "embeddingDeploymentCapacity": { + "type": "int", + "defaultValue": 500, + "metadata": { + "description": "Optional. Embedding model deployment capacity." + } + }, + "containerRegistryEndpoint": { + "type": "string", + "defaultValue": "containermigrationacr.azurecr.io", + "metadata": { + "description": "Optional. The endpoint (excluding https://) of an existing container registry." + } + }, + "imageTag": { + "type": "string", + "defaultValue": "latest_v2", + "metadata": { + "description": "Optional. The image tag to use for container images." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of an existing Foundry project." + } + }, + "existingLogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Existing Log Analytics Workspace Resource ID." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. The tags to apply to all deployed Azure resources." + } + } + }, + "variables": { + "solutionLocation": "[if(empty(parameters('location')), resourceGroup().location, parameters('location'))]", + "solutionSuffix": "[toLower(trim(replace(replace(replace(replace(replace(replace(format('{0}{1}', parameters('solutionName'), parameters('solutionUniqueText')), '-', ''), '_', ''), '.', ''), '/', ''), ' ', ''), '*', '')))]", + "deployerInfo": "[deployer()]", + "deployingUserPrincipalId": "[variables('deployerInfo').objectId]", + "deployingUserPrincipalType": "[if(contains(variables('deployerInfo'), 'userPrincipalName'), 'User', 'ServicePrincipal')]", + "createdBy": "[if(contains(variables('deployerInfo'), 'userPrincipalName'), split(variables('deployerInfo').userPrincipalName, '@')[0], variables('deployerInfo').objectId)]", + "existingTags": "[coalesce(resourceGroup().tags, createObject())]", + "useExistingLogAnalytics": "[not(empty(parameters('existingLogAnalyticsWorkspaceId')))]", + "aiModelDeployments": [ + { + "name": "[parameters('gptModelName')]", + "model": "[parameters('gptModelName')]", + "sku": { + "name": "[parameters('deploymentType')]", + "capacity": "[parameters('gptDeploymentCapacity')]" + }, + "version": "[parameters('gptModelVersion')]", + "raiPolicyName": "Microsoft.Default" + }, + { + "name": "[parameters('embeddingModel')]", + "model": "[parameters('embeddingModel')]", + "sku": { + "name": "[parameters('embeddingDeploymentType')]", + "capacity": "[parameters('embeddingDeploymentCapacity')]" + }, + "version": "[parameters('embeddingModelVersion')]", + "raiPolicyName": "Microsoft.Default" + } + ], + "cosmosDatabaseName": "migration_db", + "cosmosContainers": [ + { + "name": "processes", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "agent_telemetry", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "processcontrol", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "files", + "partitionKeyPath": "/_partitionKey" + }, + { + "name": "process_statuses", + "partitionKeyPath": "/_partitionKey" + } + ], + "processBlobContainerName": "processes", + "processQueueName": "processes-queue", + "useExistingAIProject": "[not(empty(parameters('existingFoundryProjectResourceId')))]", + "aiServiceSubscription": "[if(variables('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], subscription().subscriptionId)]", + "aiServiceResourceGroup": "[if(variables('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], resourceGroup().name)]", + "backendContainerAppName": "[take(format('ca-backend-api-{0}', variables('solutionSuffix')), 32)]", + "processorContainerAppName": "[take(format('ca-processor-{0}', variables('solutionSuffix')), 32)]", + "frontEndContainerAppName": "[take(format('ca-frontend-{0}', variables('solutionSuffix')), 32)]" + }, + "resources": [ + { + "type": "Microsoft.Resources/tags", + "apiVersion": "2023-07-01", + "name": "default", + "properties": { + "tags": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration', 'CreatedBy', variables('createdBy'), 'DeploymentName', deployment().name, 'Type', 'Non-WAF'))]" + } + }, + { + "condition": "[not(variables('useExistingLogAnalytics'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.log-analytics.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[variables('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.43.8.12551", + "templateHash": "11837290836179258853" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name suffix used to derive the resource name." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('log-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Optional. Override name for the Log Analytics workspace. Defaults to log-{solutionName}." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the resource." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to apply to the resource." + } + }, + "retentionInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "Retention period in days." + } + }, + "skuName": { + "type": "string", + "defaultValue": "PerGB2018", + "metadata": { + "description": "SKU name for the workspace." + } + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-09-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": "[parameters('retentionInDays')]", + "sku": { + "name": "[parameters('skuName')]" + } + } + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Log Analytics workspace." + }, + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace." + }, + "value": "[parameters('name')]" + }, + "location": { + "type": "string", + "metadata": { + "description": "Location of the workspace." + }, + "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), '2023-09-01', 'full').location]" + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "metadata": { + "description": "Log Analytics workspace customer ID." + }, + "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', parameters('name')), '2023-09-01').customerId]" + } + } + } + } + }, + { + "condition": "[empty(parameters('existingFoundryProjectResourceId'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[parameters('azureAiServiceLocation')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "11110992144119277828" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Required. Solution name suffix used to generate resource names." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('aif-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Optional. Override name for the AI Services account. Defaults to aif-{solutionName}." + } + }, + "projectName": { + "type": "string", + "defaultValue": "[format('proj-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Optional. Override name for the AI Foundry project. Defaults to proj-{solutionName}." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Required. Azure region for the resources." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources." + } + }, + "skuName": { + "type": "string", + "defaultValue": "S0", + "metadata": { + "description": "Optional. SKU name for the AI Services account." + } + }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether to disable local (key-based) authentication." + } + }, + "allowProjectManagement": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether to allow project management (AI Foundry hub)." + } + }, + "publicNetworkAccess": { + "type": "string", + "defaultValue": "Enabled", + "metadata": { + "description": "Optional. Public network access setting." + } + }, + "identityType": { + "type": "string", + "defaultValue": "SystemAssigned", + "allowedValues": [ + "SystemAssigned", + "UserAssigned", + "SystemAssigned, UserAssigned", + "None" + ], + "metadata": { + "description": "Optional. Managed identity type for the resources." + } + }, + "networkAclsDefaultAction": { + "type": "string", + "defaultValue": "Allow", + "allowedValues": [ + "Allow", + "Deny" + ], + "metadata": { + "description": "Optional. Network ACLs default action." + } + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-12-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "kind": "AIServices", + "identity": { + "type": "[parameters('identityType')]" + }, + "properties": { + "allowProjectManagement": "[parameters('allowProjectManagement')]", + "customSubDomainName": "[parameters('name')]", + "networkAcls": { + "defaultAction": "[parameters('networkAclsDefaultAction')]", + "virtualNetworkRules": [], + "ipRules": [] + }, + "publicNetworkAccess": "[parameters('publicNetworkAccess')]", + "disableLocalAuth": "[parameters('disableLocalAuth')]" + } + }, + { + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-12-01", + "name": "[format('{0}/{1}', parameters('name'), parameters('projectName'))]", + "location": "[parameters('location')]", + "kind": "AIServices", + "identity": { + "type": "[parameters('identityType')]" + }, + "properties": {}, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Services account." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the AI Services account." + }, + "value": "[parameters('name')]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "Endpoint of the AI Services account (OpenAI Language Model Instance API)." + }, + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2025-12-01').endpoints['OpenAI Language Model Instance API']]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID of the AI Services account." + }, + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2025-12-01', 'full').identity.principalId]" + }, + "projectResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the AI Foundry project." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName'))]" + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Name of the AI Foundry project." + }, + "value": "[parameters('projectName')]" + }, + "projectEndpoint": { + "type": "string", + "metadata": { + "description": "AI Foundry project endpoint." + }, + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName')), '2025-12-01').endpoints['AI Foundry API']]" + }, + "projectIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID of the project." + }, + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName')), '2025-12-01', 'full').identity.principalId]" + } + } + } + } + }, + { + "condition": "[variables('useExistingAIProject')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)]", + "subscriptionId": "[variables('aiServiceSubscription')]", + "resourceGroup": "[variables('aiServiceResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[8]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value))]", + "projectName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[10]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectName.value))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "7433604311330565773" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. The name of the existing Cognitive Services account." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Required. The name of the existing AI project." + } + } + }, + "resources": [], + "outputs": { + "aiFoundryPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the AI Foundry system-assigned managed identity." + }, + "value": "[if(and(contains(reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2025-12-01', 'full'), 'identity'), contains(reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2025-12-01', 'full').identity, 'principalId')), reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2025-12-01', 'full').identity.principalId, '')]" + }, + "aiProjectPrincipalId": { + "type": "string", + "metadata": { + "description": "The principal ID of the AI Project system-assigned managed identity." + }, + "value": "[if(and(contains(reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName')), '2025-12-01', 'full'), 'identity'), contains(reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName')), '2025-12-01', 'full').identity, 'principalId')), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('name'), parameters('projectName')), '2025-12-01', 'full').identity.principalId, '')]" + }, + "aiServicesAccountName": { + "type": "string", + "metadata": { + "description": "The name of the AI Services account." + }, + "value": "[parameters('name')]" + }, + "aiProjectName": { + "type": "string", + "metadata": { + "description": "The name of the AI project." + }, + "value": "[parameters('projectName')]" + }, + "aiFoundryEndpoint": { + "type": "string", + "metadata": { + "description": "The endpoint URL for the Azure OpenAI service." + }, + "value": "[format('https://{0}.openai.azure.com/', parameters('name'))]" + }, + "projectEndpoint": { + "type": "string", + "metadata": { + "description": "The endpoint URL for the AI Foundry project." + }, + "value": "[format('https://{0}.services.ai.azure.com/api/projects/{1}', parameters('name'), parameters('projectName'))]" + }, + "aiFoundryResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the AI Services account." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.foundry-storage-conn.{0}', parameters('solutionName')), 64)]", + "subscriptionId": "[variables('aiServiceSubscription')]", + "resourceGroup": "[variables('aiServiceResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "aiServicesAccountName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[8]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value))]", + "projectName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[10]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectName.value))]", + "category": { + "value": "AzureBlob" + }, + "target": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.blobEndpoint.value]" + }, + "authType": { + "value": "AAD" + }, + "metadata": { + "value": { + "ResourceId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]", + "AccountName": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]", + "ContainerName": "default" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "17598394785963531330" + } + }, + "parameters": { + "aiServicesAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the parent AI Services account." + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI Foundry project." + } + }, + "solutionName": { + "type": "string", + "metadata": { + "description": "Required. Solution name suffix used to generate the connection name." + } + }, + "connectionName": { + "type": "string", + "defaultValue": "[toLower(format('{0}-connection-{1}', parameters('category'), parameters('solutionName')))]", + "metadata": { + "description": "Optional. Connection name. Defaults to lowercase category with solution suffix." + } + }, + "category": { + "type": "string", + "metadata": { + "description": "Required. Connection category (e.g., CognitiveSearch, AzureBlob, AppInsights, RemoteTool)." + } + }, + "target": { + "type": "string", + "metadata": { + "description": "Required. Connection target (URL or resource ID)." + } + }, + "authType": { + "type": "string", + "metadata": { + "description": "Required. Authentication type (e.g., AAD, ApiKey, ProjectManagedIdentity)." + } + }, + "isSharedToAll": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Whether the connection is shared to all project users." + } + }, + "isDefault": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Whether this is the default connection for its category." + } + }, + "metadata": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Connection metadata object." + } + }, + "credentialsKey": { + "type": "securestring", + "defaultValue": "", + "metadata": { + "description": "Optional. Credentials key (for ApiKey auth type)." + } + } + }, + "variables": { + "baseProperties": { + "category": "[parameters('category')]", + "target": "[parameters('target')]", + "authType": "[parameters('authType')]", + "isSharedToAll": "[parameters('isSharedToAll')]", + "metadata": "[parameters('metadata')]" + }, + "optionalDefault": "[if(parameters('isDefault'), createObject('isDefault', true()), createObject())]", + "optionalCredentials": "[if(not(empty(parameters('credentialsKey'))), createObject('credentials', createObject('key', parameters('credentialsKey'))), createObject())]" + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/projects/connections", + "apiVersion": "2025-12-01", + "name": "[format('{0}/{1}/{2}', parameters('aiServicesAccountName'), parameters('projectName'), parameters('connectionName'))]", + "properties": "[union(variables('baseProperties'), variables('optionalDefault'), variables('optionalCredentials'))]" + } + ], + "outputs": { + "connectionName": { + "type": "string", + "metadata": { + "description": "Connection name." + }, + "value": "[parameters('connectionName')]" + }, + "connectionId": { + "type": "string", + "metadata": { + "description": "Connection resource ID." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects/connections', parameters('aiServicesAccountName'), parameters('projectName'), parameters('connectionName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "copy": { + "name": "model_deployments", + "count": "[length(variables('aiModelDeployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.model-deployment-{0}.{1}', copyIndex(), parameters('solutionName')), 64)]", + "subscriptionId": "[variables('aiServiceSubscription')]", + "resourceGroup": "[variables('aiServiceResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesAccountName": "[if(variables('useExistingAIProject'), createObject('value', split(parameters('existingFoundryProjectResourceId'), '/')[8]), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value))]", + "deploymentName": { + "value": "[variables('aiModelDeployments')[copyIndex()].name]" + }, + "modelName": { + "value": "[variables('aiModelDeployments')[copyIndex()].model]" + }, + "modelVersion": { + "value": "[variables('aiModelDeployments')[copyIndex()].version]" + }, + "raiPolicyName": { + "value": "[variables('aiModelDeployments')[copyIndex()].raiPolicyName]" + }, + "skuName": { + "value": "[variables('aiModelDeployments')[copyIndex()].sku.name]" + }, + "skuCapacity": { + "value": "[variables('aiModelDeployments')[copyIndex()].sku.capacity]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "8590492508744195961" + } + }, + "parameters": { + "aiServicesAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the parent AI Services account." + } + }, + "deploymentName": { + "type": "string", + "metadata": { + "description": "Required. Name for this model deployment." + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "Optional. Model format (e.g., OpenAI)." + } + }, + "modelName": { + "type": "string", + "metadata": { + "description": "Required. Model name (e.g., gpt-4o, text-embedding-ada-002)." + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Model version. Empty string means latest." + } + }, + "raiPolicyName": { + "type": "string", + "defaultValue": "Microsoft.Default", + "metadata": { + "description": "Optional. RAI policy name." + } + }, + "skuName": { + "type": "string", + "metadata": { + "description": "Required. SKU name (e.g., Standard, GlobalStandard)." + } + }, + "skuCapacity": { + "type": "int", + "metadata": { + "description": "Required. SKU capacity (tokens per minute in thousands)." + } + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-12-01", + "name": "[format('{0}/{1}', parameters('aiServicesAccountName'), parameters('deploymentName'))]", + "properties": { + "model": { + "format": "[parameters('modelFormat')]", + "name": "[parameters('modelName')]", + "version": "[if(not(empty(parameters('modelVersion'))), parameters('modelVersion'), null())]" + }, + "raiPolicyName": "[parameters('raiPolicyName')]" + }, + "sku": { + "name": "[parameters('skuName')]", + "capacity": "[parameters('skuCapacity')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the deployed model." + }, + "value": "[parameters('deploymentName')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the model deployment." + }, + "value": "[resourceId('Microsoft.CognitiveServices/accounts/deployments', parameters('aiServicesAccountName'), parameters('deploymentName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.storage-account.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "containers": { + "value": [ + { + "name": "data", + "publicAccess": "None" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "8152635002386994629" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name suffix used to derive the resource name." + } + }, + "name": { + "type": "string", + "defaultValue": "[take(format('st{0}', toLower(replace(parameters('solutionName'), '-', ''))), 24)]", + "metadata": { + "description": "Name of the storage account." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the resource." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to apply to the resource." + } + }, + "skuName": { + "type": "string", + "defaultValue": "Standard_LRS", + "metadata": { + "description": "Storage account SKU." + } + }, + "kind": { + "type": "string", + "defaultValue": "StorageV2", + "metadata": { + "description": "Storage account kind." + } + }, + "accessTier": { + "type": "string", + "defaultValue": "Hot", + "allowedValues": [ + "Hot", + "Cool" + ], + "metadata": { + "description": "Access tier." + } + }, + "allowBlobPublicAccess": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Allow blob public access." + } + }, + "allowSharedKeyAccess": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Allow shared key access." + } + }, + "containers": { + "type": "array", + "defaultValue": [ + { + "name": "default", + "publicAccess": "None" + } + ], + "metadata": { + "description": "Blob containers to create." + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2025-08-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "[parameters('kind')]", + "sku": { + "name": "[parameters('skuName')]" + }, + "properties": { + "accessTier": "[parameters('accessTier')]", + "allowBlobPublicAccess": "[parameters('allowBlobPublicAccess')]", + "allowSharedKeyAccess": "[parameters('allowSharedKeyAccess')]", + "minimumTlsVersion": "TLS1_2", + "supportsHttpsTrafficOnly": true, + "encryption": { + "services": { + "blob": { + "enabled": true + }, + "file": { + "enabled": true + } + }, + "keySource": "Microsoft.Storage", + "requireInfrastructureEncryption": true + } + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2025-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + }, + { + "copy": { + "name": "blobContainers", + "count": "[length(parameters('containers'))]" + }, + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2025-08-01", + "name": "[format('{0}/{1}/{2}', parameters('name'), 'default', parameters('containers')[copyIndex()].name)]", + "properties": { + "publicAccess": "[parameters('containers')[copyIndex()].publicAccess]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('name'), 'default')]" + ] + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Storage Account." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the Storage Account." + }, + "value": "[parameters('name')]" + }, + "blobEndpoint": { + "type": "string", + "metadata": { + "description": "Primary blob endpoint." + }, + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '2025-08-01').primaryEndpoints.blob]" + }, + "serviceEndpoints": { + "type": "object", + "metadata": { + "description": "All service endpoints." + }, + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '2025-08-01').primaryEndpoints]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "name": { + "value": "[format('cosmos-{0}', variables('solutionSuffix'))]" + }, + "location": { + "value": "[parameters('cosmosLocation')]" + }, + "databaseName": { + "value": "[variables('cosmosDatabaseName')]" + }, + "containers": { + "value": "[variables('cosmosContainers')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "1335207738172550086" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name suffix used to derive the resource name." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('cosmos-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Name of the Cosmos DB account." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the resource." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags to apply to the resource." + } + }, + "databaseName": { + "type": "string", + "defaultValue": "db_conversation_history", + "metadata": { + "description": "Database name." + } + }, + "containers": { + "type": "array", + "defaultValue": [ + { + "name": "conversations", + "partitionKeyPath": "/userId" + } + ], + "metadata": { + "description": "Container definitions." + } + } + }, + "resources": [ + { + "copy": { + "name": "database::list", + "count": "[length(parameters('containers'))]" + }, + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", + "apiVersion": "2025-10-15", + "name": "[format('{0}/{1}/{2}', parameters('name'), parameters('databaseName'), parameters('containers')[copyIndex()].name)]", + "properties": { + "resource": { + "id": "[parameters('containers')[copyIndex()].name]", + "partitionKey": { + "paths": [ + "[parameters('containers')[copyIndex()].partitionKeyPath]" + ] + } + }, + "options": {} + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlDatabases', parameters('name'), parameters('databaseName'))]" + ] + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2025-10-15", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "kind": "GlobalDocumentDB", + "properties": { + "consistencyPolicy": { + "defaultConsistencyLevel": "Session" + }, + "locations": [ + { + "locationName": "[parameters('location')]", + "failoverPriority": 0, + "isZoneRedundant": false + } + ], + "databaseAccountOfferType": "Standard", + "enableAutomaticFailover": false, + "enableMultipleWriteLocations": false, + "disableLocalAuth": true, + "capabilities": [ + { + "name": "EnableServerless" + } + ] + } + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", + "apiVersion": "2025-10-15", + "name": "[format('{0}/{1}', parameters('name'), parameters('databaseName'))]", + "properties": { + "resource": { + "id": "[parameters('databaseName')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + ] + } + ], + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Cosmos DB account." + }, + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('name'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the Cosmos DB account." + }, + "value": "[parameters('name')]" + }, + "endpoint": { + "type": "string", + "metadata": { + "description": "Endpoint of the Cosmos DB account." + }, + "value": "[format('https://{0}.documents.azure.com:443/', parameters('name'))]" + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Database name." + }, + "value": "[parameters('databaseName')]" + }, + "containerName": { + "type": "string", + "metadata": { + "description": "Container name (first container)." + }, + "value": "[parameters('containers')[0].name]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.container-app-env.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "logAnalyticsWorkspaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.log-analytics.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "10904031848221113546" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "metadata": { + "description": "Solution name used for naming convention." + } + }, + "name": { + "type": "string", + "defaultValue": "[format('cae-{0}', parameters('solutionName'))]", + "metadata": { + "description": "Name of the Container Apps Environment." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Log Analytics workspace." + } + }, + "infrastructureSubnetId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Subnet resource ID for VNet integration (optional)." + } + }, + "zoneRedundant": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable zone redundancy." + } + } + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2024-03-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(parameters('logAnalyticsWorkspaceResourceId'), '2023-09-01').customerId]", + "sharedKey": "[listKeys(parameters('logAnalyticsWorkspaceResourceId'), '2023-09-01').primarySharedKey]" + } + }, + "vnetConfiguration": "[if(empty(parameters('infrastructureSubnetId')), null(), createObject('infrastructureSubnetId', parameters('infrastructureSubnetId')))]", + "zoneRedundant": "[parameters('zoneRedundant')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Container Apps Environment." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Container Apps Environment." + }, + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + }, + "defaultDomain": { + "type": "string", + "metadata": { + "description": "The default domain of the Container Apps Environment." + }, + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2024-03-01').defaultDomain]" + }, + "staticIp": { + "type": "string", + "metadata": { + "description": "The static IP address of the Container Apps Environment." + }, + "value": "[reference(resourceId('Microsoft.App/managedEnvironments', parameters('name')), '2024-03-01').staticIp]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.log-analytics.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('backendContainerAppName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "environmentResourceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "ingressExternal": { + "value": true + }, + "ingressTargetPort": { + "value": 80 + }, + "containers": { + "value": [ + { + "name": "backend-api", + "image": "[format('{0}/backend-api:{1}', parameters('containerRegistryEndpoint'), parameters('imageTag'))]", + "env": [ + { + "name": "AZURE_OPENAI_ENDPOINT", + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiFoundryEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value)]" + }, + { + "name": "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", + "value": "[parameters('gptModelName')]" + }, + { + "name": "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME", + "value": "[parameters('embeddingModel')]" + }, + { + "name": "AZURE_OPENAI_API_VERSION", + "value": "2025-03-01-preview" + }, + { + "name": "COSMOS_DB_ACCOUNT_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value]" + }, + { + "name": "COSMOS_DB_DATABASE_NAME", + "value": "[variables('cosmosDatabaseName')]" + }, + { + "name": "COSMOS_DB_CONTAINER_NAME", + "value": "agent_telemetry" + }, + { + "name": "COSMOS_DB_CONTROL_CONTAINER_NAME", + "value": "processcontrol" + }, + { + "name": "COSMOS_DB_PROCESS_CONTAINER", + "value": "processes" + }, + { + "name": "COSMOS_DB_PROCESS_LOG_CONTAINER", + "value": "agent_telemetry" + }, + { + "name": "STORAGE_ACCOUNT_BLOB_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.blobEndpoint.value]" + }, + { + "name": "STORAGE_ACCOUNT_NAME", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_CONTAINER", + "value": "[variables('processBlobContainerName')]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_QUEUE", + "value": "[variables('processQueueName')]" + }, + { + "name": "STORAGE_ACCOUNT_QUEUE_URL", + "value": "[format('{0}', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.serviceEndpoints.value.queue)]" + }, + { + "name": "GLOBAL_LLM_SERVICE", + "value": "AzureOpenAI" + }, + { + "name": "PROCESSOR_CONTROL_URL", + "value": "[format('https://{0}.internal.{1}', variables('processorContainerAppName'), reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.defaultDomain.value)]" + }, + { + "name": "APP_ENV", + "value": "Prod" + } + ], + "resources": { + "cpu": "[json('1')]", + "memory": "2.0Gi" + } + } + ] + }, + "corsPolicy": { + "value": { + "allowedOrigins": [ + "*" + ], + "allowedMethods": [ + "GET", + "POST", + "PUT", + "DELETE", + "OPTIONS" + ], + "allowedHeaders": [ + "Authorization", + "Content-Type", + "*" + ] + } + }, + "scaleSettings": { + "value": { + "maxReplicas": 1, + "minReplicas": 1 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "16738571182591726351" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "environmentResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Container Apps Environment." + } + }, + "containers": { + "type": "array", + "metadata": { + "description": "Container definitions." + } + }, + "ingressExternal": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "ingressTargetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for ingress." + } + }, + "ingressTransport": { + "type": "string", + "defaultValue": "auto", + "allowedValues": [ + "auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Ingress transport protocol." + } + }, + "ingressAllowInsecure": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to allow insecure ingress connections." + } + }, + "disableIngress": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Disable ingress entirely (for background workers)." + } + }, + "registries": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Container registry configurations." + } + }, + "secrets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Secret definitions." + } + }, + "managedIdentities": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Managed identity configuration." + } + }, + "corsPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "CORS policy configuration." + } + }, + "activeRevisionsMode": { + "type": "string", + "defaultValue": "Single", + "allowedValues": [ + "Single", + "Multiple" + ], + "metadata": { + "description": "Active revision mode." + } + }, + "scaleSettings": { + "type": "object", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 0 + }, + "metadata": { + "description": "Scale settings (maxReplicas, minReplicas, rules)." + } + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Workload profile name." + } + } + }, + "variables": { + "identityConfig": "[if(empty(parameters('managedIdentities')), createObject('type', 'None'), createObject('type', if(contains(parameters('managedIdentities'), 'userAssignedResourceIds'), if(and(contains(parameters('managedIdentities'), 'systemAssigned'), parameters('managedIdentities').systemAssigned), 'SystemAssigned,UserAssigned', 'UserAssigned'), 'SystemAssigned'), 'userAssignedIdentities', if(contains(parameters('managedIdentities'), 'userAssignedResourceIds'), reduce(parameters('managedIdentities').userAssignedResourceIds, createObject(), lambda('cur', 'id', union(lambdaVariables('cur'), createObject(format('{0}', lambdaVariables('id')), createObject())))), null())))]", + "ingressConfig": "[if(parameters('disableIngress'), null(), createObject('external', parameters('ingressExternal'), 'targetPort', parameters('ingressTargetPort'), 'transport', parameters('ingressTransport'), 'allowInsecure', parameters('ingressAllowInsecure'), 'corsPolicy', if(not(empty(parameters('corsPolicy'))), parameters('corsPolicy'), null())))]" + }, + "resources": { + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2024-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identityConfig')]", + "properties": { + "managedEnvironmentId": "[parameters('environmentResourceId')]", + "workloadProfileName": "[parameters('workloadProfileName')]", + "configuration": { + "activeRevisionsMode": "[parameters('activeRevisionsMode')]", + "ingress": "[variables('ingressConfig')]", + "registries": "[parameters('registries')]", + "secrets": "[parameters('secrets')]" + }, + "template": { + "containers": "[parameters('containers')]", + "scale": { + "minReplicas": "[parameters('scaleSettings').minReplicas]", + "maxReplicas": "[parameters('scaleSettings').maxReplicas]", + "rules": "[if(contains(parameters('scaleSettings'), 'rules'), parameters('scaleSettings').rules, null())]" + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app." + }, + "value": "[parameters('name')]" + }, + "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 FQDN of the container app." + }, + "value": "[if(not(parameters('disableIngress')), reference('containerApp').configuration.ingress.fqdn, '')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID." + }, + "value": "[if(contains(reference('containerApp', '2024-10-02-preview', 'full').identity.type, 'SystemAssigned'), reference('containerApp', '2024-10-02-preview', 'full').identity.principalId, '')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ca-frontend.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('frontEndContainerAppName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "environmentResourceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "ingressExternal": { + "value": true + }, + "ingressTargetPort": { + "value": 3000 + }, + "containers": { + "value": [ + { + "name": "frontend", + "image": "[format('{0}/frontend:{1}', parameters('containerRegistryEndpoint'), parameters('imageTag'))]", + "env": [ + { + "name": "API_URL", + "value": "[format('https://{0}', reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.fqdn.value)]" + }, + { + "name": "APP_ENV", + "value": "prod" + }, + { + "name": "REACT_APP_MSAL_POST_REDIRECT_URL", + "value": "/" + }, + { + "name": "REACT_APP_MSAL_REDIRECT_URL", + "value": "/" + }, + { + "name": "ALLOWED_ORIGINS", + "value": "[format('https://{0}.{1}', variables('frontEndContainerAppName'), reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.defaultDomain.value)]" + } + ], + "resources": { + "cpu": "[json('1')]", + "memory": "2.0Gi" + } + } + ] + }, + "scaleSettings": { + "value": { + "maxReplicas": 1, + "minReplicas": 1 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "16738571182591726351" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "environmentResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Container Apps Environment." + } + }, + "containers": { + "type": "array", + "metadata": { + "description": "Container definitions." + } + }, + "ingressExternal": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "ingressTargetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for ingress." + } + }, + "ingressTransport": { + "type": "string", + "defaultValue": "auto", + "allowedValues": [ + "auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Ingress transport protocol." + } + }, + "ingressAllowInsecure": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to allow insecure ingress connections." + } + }, + "disableIngress": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Disable ingress entirely (for background workers)." + } + }, + "registries": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Container registry configurations." + } + }, + "secrets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Secret definitions." + } + }, + "managedIdentities": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Managed identity configuration." + } + }, + "corsPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "CORS policy configuration." + } + }, + "activeRevisionsMode": { + "type": "string", + "defaultValue": "Single", + "allowedValues": [ + "Single", + "Multiple" + ], + "metadata": { + "description": "Active revision mode." + } + }, + "scaleSettings": { + "type": "object", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 0 + }, + "metadata": { + "description": "Scale settings (maxReplicas, minReplicas, rules)." + } + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Workload profile name." + } + } + }, + "variables": { + "identityConfig": "[if(empty(parameters('managedIdentities')), createObject('type', 'None'), createObject('type', if(contains(parameters('managedIdentities'), 'userAssignedResourceIds'), if(and(contains(parameters('managedIdentities'), 'systemAssigned'), parameters('managedIdentities').systemAssigned), 'SystemAssigned,UserAssigned', 'UserAssigned'), 'SystemAssigned'), 'userAssignedIdentities', if(contains(parameters('managedIdentities'), 'userAssignedResourceIds'), reduce(parameters('managedIdentities').userAssignedResourceIds, createObject(), lambda('cur', 'id', union(lambdaVariables('cur'), createObject(format('{0}', lambdaVariables('id')), createObject())))), null())))]", + "ingressConfig": "[if(parameters('disableIngress'), null(), createObject('external', parameters('ingressExternal'), 'targetPort', parameters('ingressTargetPort'), 'transport', parameters('ingressTransport'), 'allowInsecure', parameters('ingressAllowInsecure'), 'corsPolicy', if(not(empty(parameters('corsPolicy'))), parameters('corsPolicy'), null())))]" + }, + "resources": { + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2024-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identityConfig')]", + "properties": { + "managedEnvironmentId": "[parameters('environmentResourceId')]", + "workloadProfileName": "[parameters('workloadProfileName')]", + "configuration": { + "activeRevisionsMode": "[parameters('activeRevisionsMode')]", + "ingress": "[variables('ingressConfig')]", + "registries": "[parameters('registries')]", + "secrets": "[parameters('secrets')]" + }, + "template": { + "containers": "[parameters('containers')]", + "scale": { + "minReplicas": "[parameters('scaleSettings').minReplicas]", + "maxReplicas": "[parameters('scaleSettings').maxReplicas]", + "rules": "[if(contains(parameters('scaleSettings'), 'rules'), parameters('scaleSettings').rules, null())]" + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app." + }, + "value": "[parameters('name')]" + }, + "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 FQDN of the container app." + }, + "value": "[if(not(parameters('disableIngress')), reference('containerApp').configuration.ingress.fqdn, '')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID." + }, + "value": "[if(contains(reference('containerApp', '2024-10-02-preview', 'full').identity.type, 'SystemAssigned'), reference('containerApp', '2024-10-02-preview', 'full').identity.principalId, '')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.ca-processor.{0}', parameters('solutionName')), 64)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('processorContainerAppName')]" + }, + "location": { + "value": "[variables('solutionLocation')]" + }, + "tags": { + "value": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'Container Migration'))]" + }, + "environmentResourceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "ingressExternal": { + "value": false + }, + "ingressTargetPort": { + "value": 8080 + }, + "ingressAllowInsecure": { + "value": true + }, + "containers": { + "value": [ + { + "name": "processor", + "image": "[format('{0}/processor:{1}', parameters('containerRegistryEndpoint'), parameters('imageTag'))]", + "env": [ + { + "name": "AZURE_OPENAI_ENDPOINT", + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiFoundryEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value)]" + }, + { + "name": "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME", + "value": "[parameters('gptModelName')]" + }, + { + "name": "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME", + "value": "[parameters('embeddingModel')]" + }, + { + "name": "AZURE_OPENAI_API_VERSION", + "value": "2025-03-01-preview" + }, + { + "name": "COSMOS_DB_ACCOUNT_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value]" + }, + { + "name": "COSMOS_DB_DATABASE_NAME", + "value": "[variables('cosmosDatabaseName')]" + }, + { + "name": "COSMOS_DB_CONTAINER_NAME", + "value": "agent_telemetry" + }, + { + "name": "COSMOS_DB_CONTROL_CONTAINER_NAME", + "value": "processcontrol" + }, + { + "name": "COSMOS_DB_PROCESS_CONTAINER", + "value": "processes" + }, + { + "name": "COSMOS_DB_PROCESS_LOG_CONTAINER", + "value": "agent_telemetry" + }, + { + "name": "STORAGE_ACCOUNT_BLOB_URL", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.blobEndpoint.value]" + }, + { + "name": "STORAGE_ACCOUNT_NAME", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_CONTAINER", + "value": "[variables('processBlobContainerName')]" + }, + { + "name": "STORAGE_ACCOUNT_PROCESS_QUEUE", + "value": "[variables('processQueueName')]" + }, + { + "name": "STORAGE_ACCOUNT_QUEUE_URL", + "value": "[format('{0}', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.serviceEndpoints.value.queue)]" + }, + { + "name": "GLOBAL_LLM_SERVICE", + "value": "AzureOpenAI" + }, + { + "name": "CONTROL_API_ENABLED", + "value": "1" + }, + { + "name": "CONTROL_API_PORT", + "value": "8080" + } + ], + "resources": { + "cpu": "[json('2')]", + "memory": "4.0Gi" + } + } + ] + }, + "scaleSettings": { + "value": { + "maxReplicas": 1, + "minReplicas": 1 + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "16738571182591726351" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the container app." + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for deployment." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Resource tags." + } + }, + "environmentResourceId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Container Apps Environment." + } + }, + "containers": { + "type": "array", + "metadata": { + "description": "Container definitions." + } + }, + "ingressExternal": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable external ingress." + } + }, + "ingressTargetPort": { + "type": "int", + "defaultValue": 80, + "metadata": { + "description": "Target port for ingress." + } + }, + "ingressTransport": { + "type": "string", + "defaultValue": "auto", + "allowedValues": [ + "auto", + "http", + "http2", + "tcp" + ], + "metadata": { + "description": "Ingress transport protocol." + } + }, + "ingressAllowInsecure": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to allow insecure ingress connections." + } + }, + "disableIngress": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Disable ingress entirely (for background workers)." + } + }, + "registries": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Container registry configurations." + } + }, + "secrets": { + "type": "array", + "nullable": true, + "metadata": { + "description": "Secret definitions." + } + }, + "managedIdentities": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Managed identity configuration." + } + }, + "corsPolicy": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "CORS policy configuration." + } + }, + "activeRevisionsMode": { + "type": "string", + "defaultValue": "Single", + "allowedValues": [ + "Single", + "Multiple" + ], + "metadata": { + "description": "Active revision mode." + } + }, + "scaleSettings": { + "type": "object", + "defaultValue": { + "maxReplicas": 10, + "minReplicas": 0 + }, + "metadata": { + "description": "Scale settings (maxReplicas, minReplicas, rules)." + } + }, + "workloadProfileName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Workload profile name." + } + } + }, + "variables": { + "identityConfig": "[if(empty(parameters('managedIdentities')), createObject('type', 'None'), createObject('type', if(contains(parameters('managedIdentities'), 'userAssignedResourceIds'), if(and(contains(parameters('managedIdentities'), 'systemAssigned'), parameters('managedIdentities').systemAssigned), 'SystemAssigned,UserAssigned', 'UserAssigned'), 'SystemAssigned'), 'userAssignedIdentities', if(contains(parameters('managedIdentities'), 'userAssignedResourceIds'), reduce(parameters('managedIdentities').userAssignedResourceIds, createObject(), lambda('cur', 'id', union(lambdaVariables('cur'), createObject(format('{0}', lambdaVariables('id')), createObject())))), null())))]", + "ingressConfig": "[if(parameters('disableIngress'), null(), createObject('external', parameters('ingressExternal'), 'targetPort', parameters('ingressTargetPort'), 'transport', parameters('ingressTransport'), 'allowInsecure', parameters('ingressAllowInsecure'), 'corsPolicy', if(not(empty(parameters('corsPolicy'))), parameters('corsPolicy'), null())))]" + }, + "resources": { + "containerApp": { + "type": "Microsoft.App/containerApps", + "apiVersion": "2024-10-02-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identityConfig')]", + "properties": { + "managedEnvironmentId": "[parameters('environmentResourceId')]", + "workloadProfileName": "[parameters('workloadProfileName')]", + "configuration": { + "activeRevisionsMode": "[parameters('activeRevisionsMode')]", + "ingress": "[variables('ingressConfig')]", + "registries": "[parameters('registries')]", + "secrets": "[parameters('secrets')]" + }, + "template": { + "containers": "[parameters('containers')]", + "scale": { + "minReplicas": "[parameters('scaleSettings').minReplicas]", + "maxReplicas": "[parameters('scaleSettings').maxReplicas]", + "rules": "[if(contains(parameters('scaleSettings'), 'rules'), parameters('scaleSettings').rules, null())]" + } + } + } + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the container app." + }, + "value": "[parameters('name')]" + }, + "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 FQDN of the container app." + }, + "value": "[if(not(parameters('disableIngress')), reference('containerApp').configuration.ingress.fqdn, '')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "System-assigned identity principal ID." + }, + "value": "[if(contains(reference('containerApp', '2024-10-02-preview', 'full').identity.type, 'SystemAssigned'), reference('containerApp', '2024-10-02-preview', 'full').identity.principalId, '')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.container-app-env.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.role-assignments.{0}', parameters('solutionName')), 64)]", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "solutionName": { + "value": "[variables('solutionSuffix')]" + }, + "useExistingAIProject": { + "value": "[variables('useExistingAIProject')]" + }, + "existingFoundryProjectResourceId": { + "value": "[parameters('existingFoundryProjectResourceId')]" + }, + "aiFoundryResourceId": "[if(not(variables('useExistingAIProject')), if(not(variables('useExistingAIProject')), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value), createObject('value', '')), createObject('value', ''))]", + "aiSearchResourceId": { + "value": "" + }, + "storageAccountResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.resourceId.value]" + }, + "aiProjectPrincipalId": "[if(variables('useExistingAIProject'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiProjectPrincipalId.value), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectIdentityPrincipalId.value))]", + "aiSearchPrincipalId": { + "value": "" + }, + "deployerPrincipalId": { + "value": "[variables('deployingUserPrincipalId')]" + }, + "deployerPrincipalType": { + "value": "[variables('deployingUserPrincipalType')]" + }, + "backendAppServicePrincipalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.principalId.value]" + }, + "cosmosDbAccountName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "existingAiProjectPrincipalId": "[if(not(empty(parameters('existingFoundryProjectResourceId'))), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiProjectPrincipalId.value), createObject('value', ''))]" + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "9572951001648196352" + } + }, + "parameters": { + "solutionName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Solution name suffix for generating unique role assignment GUIDs." + } + }, + "useExistingAIProject": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Whether to use an existing AI project (true) or create new (false)." + } + }, + "existingFoundryProjectResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the existing AI project (for deriving AI Services name/sub/RG)." + } + }, + "aiProjectPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the AI project identity." + } + }, + "existingAiProjectPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the existing AI project identity (for cross-service roles)." + } + }, + "aiSearchPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the AI Search identity." + } + }, + "backendAppServicePrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the backend App Service system-assigned identity (empty if not deployed)." + } + }, + "deployerPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the deploying user (for user access roles)." + } + }, + "deployerPrincipalType": { + "type": "string", + "defaultValue": "User", + "allowedValues": [ + "User", + "ServicePrincipal" + ], + "metadata": { + "description": "Principal type of the deploying user." + } + }, + "aiFoundryResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the AI Foundry account (empty if not deployed — new project path)." + } + }, + "aiSearchResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the AI Search service (empty if not deployed)." + } + }, + "storageAccountResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the Storage Account (empty if not deployed)." + } + }, + "cosmosDbAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Cosmos DB account (empty if not deployed)." + } + } + }, + "variables": { + "existingAIFoundryName": "[if(parameters('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[8], '')]", + "existingAIFoundrySubscription": "[if(parameters('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[2], subscription().subscriptionId)]", + "existingAIFoundryResourceGroup": "[if(parameters('useExistingAIProject'), split(parameters('existingFoundryProjectResourceId'), '/')[4], resourceGroup().name)]", + "roleDefinitions": { + "azureAiUser": "53ca6127-db72-4b80-b1b0-d745d6d5456d", + "cognitiveServicesUser": "a97b65f3-24c7-4388-baec-2e87135dc908", + "cognitiveServicesOpenAIUser": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", + "searchIndexDataReader": "1407120a-92aa-4202-b7e9-c0e197c71c8f", + "searchIndexDataContributor": "8ebe5a00-799e-43f5-93ac-243d3dce84a7", + "searchServiceContributor": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "storageBlobDataContributor": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "storageBlobDataReader": "2a2b9908-6ea1-4ae2-8e65-a410df84e7d1" + } + }, + "resources": [ + { + "condition": "[and(and(not(parameters('useExistingAIProject')), not(empty(parameters('aiSearchPrincipalId')))), not(empty(parameters('aiFoundryResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/'))), variables('roleDefinitions').cognitiveServicesOpenAIUser, 'search-openai')]", + "properties": { + "principalId": "[parameters('aiSearchPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').cognitiveServicesOpenAIUser)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(not(parameters('useExistingAIProject')), not(empty(parameters('aiFoundryResourceId')))), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('backendAppServicePrincipalId'), variables('roleDefinitions').azureAiUser, 'backend-ai-services')]", + "properties": { + "principalId": "[parameters('backendAppServicePrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').azureAiUser)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('aiSearchResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').searchIndexDataReader, 'project-search')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('aiSearchResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').searchServiceContributor, 'project-search')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchServiceContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('aiSearchResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').searchIndexDataReader, 'existing-project-search')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('aiSearchResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').searchServiceContributor, 'existing-project-search')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchServiceContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('aiSearchResourceId'))), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('backendAppServicePrincipalId'), variables('roleDefinitions').searchIndexDataReader, 'backend-search')]", + "properties": { + "principalId": "[parameters('backendAppServicePrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('storageAccountResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataContributor, 'project-storage')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('storageAccountResourceId'))), not(empty(parameters('aiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataReader, 'project-storage')]", + "properties": { + "principalId": "[parameters('aiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('storageAccountResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataContributor, 'existing-project-storage')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataContributor)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(and(parameters('useExistingAIProject'), not(empty(parameters('storageAccountResourceId')))), not(empty(parameters('existingAiProjectPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('existingAiProjectPrincipalId'), variables('roleDefinitions').storageBlobDataReader, 'existing-project-storage')]", + "properties": { + "principalId": "[parameters('existingAiProjectPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('storageAccountResourceId'))), not(empty(parameters('aiSearchPrincipalId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceGroup().id, parameters('aiSearchPrincipalId'), variables('roleDefinitions').storageBlobDataReader, 'search-storage')]", + "properties": { + "principalId": "[parameters('aiSearchPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataReader)]", + "principalType": "ServicePrincipal" + } + }, + { + "condition": "[and(not(empty(parameters('cosmosDbAccountName'))), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2025-10-15", + "name": "[format('{0}/{1}', parameters('cosmosDbAccountName'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('cosmosDbAccountName'), '00000000-0000-0000-0000-000000000002'), resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbAccountName')), parameters('backendAppServicePrincipalId')))]", + "properties": { + "principalId": "[parameters('backendAppServicePrincipalId')]", + "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', parameters('cosmosDbAccountName'), '00000000-0000-0000-0000-000000000002')]", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbAccountName'))]" + } + }, + { + "condition": "[and(and(not(parameters('useExistingAIProject')), not(empty(parameters('deployerPrincipalId')))), not(empty(parameters('aiFoundryResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/')))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/'))), parameters('deployerPrincipalId'), variables('roleDefinitions').cognitiveServicesUser)]", + "properties": { + "principalId": "[parameters('deployerPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').cognitiveServicesUser)]", + "principalType": "[parameters('deployerPrincipalType')]" + } + }, + { + "condition": "[and(and(not(parameters('useExistingAIProject')), not(empty(parameters('deployerPrincipalId')))), not(empty(parameters('aiFoundryResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/')))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', last(split(parameters('aiFoundryResourceId'), '/'))), parameters('deployerPrincipalId'), variables('roleDefinitions').azureAiUser)]", + "properties": { + "principalId": "[parameters('deployerPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').azureAiUser)]", + "principalType": "[parameters('deployerPrincipalType')]" + } + }, + { + "condition": "[and(not(empty(parameters('deployerPrincipalId'))), not(empty(parameters('aiSearchResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/'))), parameters('deployerPrincipalId'), variables('roleDefinitions').searchIndexDataContributor)]", + "properties": { + "principalId": "[parameters('deployerPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchIndexDataContributor)]", + "principalType": "[parameters('deployerPrincipalType')]" + } + }, + { + "condition": "[and(not(empty(parameters('deployerPrincipalId'))), not(empty(parameters('aiSearchResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/')))]", + "name": "[guid(resourceId('Microsoft.Search/searchServices', last(split(parameters('aiSearchResourceId'), '/'))), parameters('deployerPrincipalId'), variables('roleDefinitions').searchServiceContributor)]", + "properties": { + "principalId": "[parameters('deployerPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').searchServiceContributor)]", + "principalType": "[parameters('deployerPrincipalType')]" + } + }, + { + "condition": "[and(not(empty(parameters('deployerPrincipalId'))), not(empty(parameters('storageAccountResourceId'))))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/')))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', last(split(parameters('storageAccountResourceId'), '/'))), parameters('deployerPrincipalId'), variables('roleDefinitions').storageBlobDataContributor)]", + "properties": { + "principalId": "[parameters('deployerPrincipalId')]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').storageBlobDataContributor)]", + "principalType": "[parameters('deployerPrincipalType')]" + } + }, + { + "condition": "[and(parameters('useExistingAIProject'), not(empty(parameters('aiSearchPrincipalId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "assignOpenAIRoleToAISearchExisting", + "subscriptionId": "[variables('existingAIFoundrySubscription')]", + "resourceGroup": "[variables('existingAIFoundryResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('aiSearchPrincipalId')]" + }, + "roleDefinitionId": { + "value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').cognitiveServicesOpenAIUser)]" + }, + "roleAssignmentName": { + "value": "[guid(parameters('solutionName'), 'search-openai', variables('existingAIFoundryName'), variables('roleDefinitions').cognitiveServicesOpenAIUser)]" + }, + "aiFoundryName": { + "value": "[variables('existingAIFoundryName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "8140699401604799898" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The resource ID of the role definition to assign." + } + }, + "roleAssignmentName": { + "type": "string", + "metadata": { + "description": "A unique name for the role assignment." + } + }, + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "The name of the AI Services account to scope the role assignment to." + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "allowedValues": [ + "ServicePrincipal", + "User" + ], + "metadata": { + "description": "The principal type of the identity being assigned." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName'))]", + "name": "[parameters('roleAssignmentName')]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + } + }, + { + "condition": "[and(parameters('useExistingAIProject'), not(empty(parameters('backendAppServicePrincipalId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "assignAiUserRoleToBackendExisting", + "subscriptionId": "[variables('existingAIFoundrySubscription')]", + "resourceGroup": "[variables('existingAIFoundryResourceGroup')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('backendAppServicePrincipalId')]" + }, + "roleDefinitionId": { + "value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('roleDefinitions').azureAiUser)]" + }, + "roleAssignmentName": { + "value": "[guid(parameters('solutionName'), 'backend-aiuser', variables('existingAIFoundryName'), variables('roleDefinitions').azureAiUser)]" + }, + "aiFoundryName": { + "value": "[variables('existingAIFoundryName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.43.8.12551", + "templateHash": "8140699401604799898" + } + }, + "parameters": { + "principalId": { + "type": "string", + "metadata": { + "description": "The principal ID to assign the role to." + } + }, + "roleDefinitionId": { + "type": "string", + "metadata": { + "description": "The resource ID of the role definition to assign." + } + }, + "roleAssignmentName": { + "type": "string", + "metadata": { + "description": "A unique name for the role assignment." + } + }, + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "The name of the AI Services account to scope the role assignment to." + } + }, + "principalType": { + "type": "string", + "defaultValue": "ServicePrincipal", + "allowedValues": [ + "ServicePrincipal", + "User" + ], + "metadata": { + "description": "The principal type of the identity being assigned." + } + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName'))]", + "name": "[parameters('roleAssignmentName')]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64))]", + "[resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.storage-account.{0}', parameters('solutionName')), 64))]" + ] + } + ], + "outputs": { + "SOLUTION_NAME": { + "type": "string", + "metadata": { + "description": "Solution suffix used for naming resources" + }, + "value": "[variables('solutionSuffix')]" + }, + "RESOURCE_GROUP_NAME": { + "type": "string", + "metadata": { + "description": "Name of the deployed resource group" + }, + "value": "[resourceGroup().name]" + }, + "CONTAINER_WEB_APP_NAME": { + "type": "string", + "metadata": { + "description": "The name of the web app container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-frontend.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "CONTAINER_WEB_APP_FQDN": { + "type": "string", + "metadata": { + "description": "The FQDN of the web app container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-frontend.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.fqdn.value]" + }, + "CONTAINER_API_APP_NAME": { + "type": "string", + "metadata": { + "description": "The name of the API container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "CONTAINER_API_APP_FQDN": { + "type": "string", + "metadata": { + "description": "The FQDN of the API container app." + }, + "value": "[reference(resourceId('Microsoft.Resources/deployments', take(format('module.ca-backend-api.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.fqdn.value]" + }, + "AZURE_OPENAI_ENDPOINT": { + "type": "string", + "metadata": { + "description": "Azure OpenAI service endpoint URL" + }, + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.aiFoundryEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.endpoint.value)]" + }, + "AZURE_ENV_GPT_MODEL_NAME": { + "type": "string", + "metadata": { + "description": "GPT model deployment name" + }, + "value": "[parameters('gptModelName')]" + }, + "AZURE_ENV_EMBEDDING_DEPLOYMENT_NAME": { + "type": "string", + "metadata": { + "description": "Embedding model deployment name" + }, + "value": "[parameters('embeddingModel')]" + }, + "AZURE_COSMOSDB_ACCOUNT": { + "type": "string", + "metadata": { + "description": "Cosmos DB account name" + }, + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.cosmos-db.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.name.value]" + }, + "AZURE_COSMOSDB_DATABASE": { + "type": "string", + "metadata": { + "description": "Cosmos DB database name" + }, + "value": "[variables('cosmosDatabaseName')]" + }, + "AZURE_SUBSCRIPTION_ID": { + "type": "string", + "metadata": { + "description": "The Azure subscription ID." + }, + "value": "[subscription().subscriptionId]" + }, + "AZURE_RESOURCE_GROUP": { + "type": "string", + "metadata": { + "description": "The Azure resource group name." + }, + "value": "[resourceGroup().name]" + }, + "AZURE_AI_AGENT_ENDPOINT": { + "type": "string", + "metadata": { + "description": "Azure AI Agent service endpoint URL" + }, + "value": "[if(variables('useExistingAIProject'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', variables('aiServiceSubscription'), variables('aiServiceResourceGroup')), 'Microsoft.Resources/deployments', take(format('module.existing-project-setup.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectEndpoint.value, reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroup().name), 'Microsoft.Resources/deployments', take(format('module.ai-foundry-project.{0}', parameters('solutionName')), 64)), '2025-04-01').outputs.projectEndpoint.value)]" + } + } +} \ No newline at end of file diff --git a/infra/bicep/main.parameters.json b/infra/bicep/main.parameters.json new file mode 100644 index 00000000..5682ee53 --- /dev/null +++ b/infra/bicep/main.parameters.json @@ -0,0 +1,42 @@ +{ + "$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}" + }, + "containerRegistryEndpoint": { + "value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}" + }, + "azureAiServiceLocation": { + "value": "${AZURE_ENV_AI_SERVICE_LOCATION}" + }, + "deploymentType": { + "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" + }, + "gptModelName": { + "value": "${AZURE_ENV_GPT_MODEL_NAME}" + }, + "gptModelVersion": { + "value": "${AZURE_ENV_GPT_MODEL_VERSION}" + }, + "gptDeploymentCapacity": { + "value": "${AZURE_ENV_GPT_MODEL_CAPACITY}" + }, + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID}" + }, + "existingFoundryProjectResourceId": { + "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" + }, + "imageTag": { + "value": "${AZURE_ENV_IMAGE_TAG}" + }, + "tags": { + "value": "${AZURE_ENV_TAGS}" + } + } +} diff --git a/infra/bicep/main.waf.parameters.json b/infra/bicep/main.waf.parameters.json new file mode 100644 index 00000000..5682ee53 --- /dev/null +++ b/infra/bicep/main.waf.parameters.json @@ -0,0 +1,42 @@ +{ + "$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}" + }, + "containerRegistryEndpoint": { + "value": "${AZURE_ENV_CONTAINER_REGISTRY_ENDPOINT}" + }, + "azureAiServiceLocation": { + "value": "${AZURE_ENV_AI_SERVICE_LOCATION}" + }, + "deploymentType": { + "value": "${AZURE_ENV_MODEL_DEPLOYMENT_TYPE}" + }, + "gptModelName": { + "value": "${AZURE_ENV_GPT_MODEL_NAME}" + }, + "gptModelVersion": { + "value": "${AZURE_ENV_GPT_MODEL_VERSION}" + }, + "gptDeploymentCapacity": { + "value": "${AZURE_ENV_GPT_MODEL_CAPACITY}" + }, + "existingLogAnalyticsWorkspaceId": { + "value": "${AZURE_ENV_EXISTING_LOG_ANALYTICS_WORKSPACE_RID}" + }, + "existingFoundryProjectResourceId": { + "value": "${AZURE_EXISTING_AIPROJECT_RESOURCE_ID}" + }, + "imageTag": { + "value": "${AZURE_ENV_IMAGE_TAG}" + }, + "tags": { + "value": "${AZURE_ENV_TAGS}" + } + } +} diff --git a/infra/bicep/main_custom.bicep b/infra/bicep/main_custom.bicep new file mode 100644 index 00000000..dd1b66b5 --- /dev/null +++ b/infra/bicep/main_custom.bicep @@ -0,0 +1,513 @@ +// ========== main_custom.bicep ========== // +// Variant for azd custom deployment — accepts container image names as parameters. +targetScope = 'resourceGroup' + +// ============================================================================== +// Parameters +// ============================================================================== + +// ── Core ── + +@minLength(3) +@maxLength(16) +@description('Required. A unique application/solution name for all resources in this deployment.') +param solutionName string = 'containermig' + +@maxLength(5) +@description('Optional. A unique text suffix appended to resource names for uniqueness.') +param solutionUniqueText string = substring(uniqueString(subscription().id, resourceGroup().name, solutionName), 0, 5) + +@description('Optional. Primary Azure region for resource deployment. Defaults to resource group location.') +param location string = '' + +@allowed([ + 'australiaeast' + 'eastus' + 'eastus2' + 'francecentral' + 'japaneast' + 'norwayeast' + 'southindia' + 'swedencentral' + 'uksouth' + 'westus' + 'westus3' +]) +@metadata({ + azd: { + type: 'location' + usageName: [ + 'OpenAI.GlobalStandard.gpt-5.1,500' + ] + } +}) +@description('Required. Location for AI Foundry and model deployments.') +param azureAiServiceLocation string + +@description('Optional. Secondary location for Cosmos DB resources.') +param cosmosLocation string = 'eastus2' + +// ── AI Configuration ── + +@allowed([ + 'Standard' + 'GlobalStandard' +]) +@description('Optional. GPT model deployment type.') +param deploymentType string = 'GlobalStandard' + +@description('Optional. Name of the GPT model to deploy.') +param gptModelName string = 'gpt-5.1' + +@description('Optional. Version of the GPT model.') +param gptModelVersion string = '2025-11-13' + +@description('Optional. GPT model deployment capacity (tokens per minute in thousands).') +param gptDeploymentCapacity int = 500 + +@description('Optional. Name of the embedding model to deploy.') +param embeddingModel string = 'text-embedding-3-large' + +@description('Optional. Version of the embedding model.') +param embeddingModelVersion string = '1' + +@allowed([ + 'Standard' + 'GlobalStandard' +]) +@description('Optional. Embedding model deployment type.') +param embeddingDeploymentType string = 'GlobalStandard' + +@description('Optional. Embedding model deployment capacity.') +param embeddingDeploymentCapacity int = 500 + +// ── Container Image Parameters (azd-supplied) ── + +@secure() +@description('The full image name (including tag) for the backend API container, generated by azd.') +param backendImageName string = '' + +@secure() +@description('The full image name (including tag) for the processor container, generated by azd.') +param processorImageName string = '' + +@secure() +@description('The full image name (including tag) for the frontend container, generated by azd.') +param frontendImageName string = '' + +// ── Identity ── + +@description('Optional. Resource ID of an existing Foundry project.') +param existingFoundryProjectResourceId string = '' + +@description('Optional. Existing Log Analytics Workspace Resource ID.') +param existingLogAnalyticsWorkspaceId string = '' + +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags object = {} + +// ============================================================================== +// Variables +// ============================================================================== + +var solutionLocation = empty(location) ? resourceGroup().location : location + +var solutionSuffix = toLower(trim(replace( + replace( + replace(replace(replace(replace('${solutionName}${solutionUniqueText}', '-', ''), '_', ''), '.', ''), '/', ''), + ' ', + '' + ), + '*', + '' +))) + +var deployerInfo = deployer() +var deployingUserPrincipalId = deployerInfo.objectId +var deployingUserPrincipalType = contains(deployerInfo, 'userPrincipalName') ? 'User' : 'ServicePrincipal' + +var createdBy = contains(deployerInfo, 'userPrincipalName') + ? split(deployerInfo.userPrincipalName, '@')[0] + : deployerInfo.objectId + +var existingTags = resourceGroup().tags ?? {} +var useExistingLogAnalytics = !empty(existingLogAnalyticsWorkspaceId) + +var aiModelDeployments = [ + { + name: gptModelName + model: gptModelName + sku: { + name: deploymentType + capacity: gptDeploymentCapacity + } + version: gptModelVersion + raiPolicyName: 'Microsoft.Default' + } + { + name: embeddingModel + model: embeddingModel + sku: { + name: embeddingDeploymentType + capacity: embeddingDeploymentCapacity + } + version: embeddingModelVersion + raiPolicyName: 'Microsoft.Default' + } +] + +var cosmosDatabaseName = 'migration_db' +var cosmosContainers = [ + { name: 'processes', partitionKeyPath: '/_partitionKey' } + { name: 'agent_telemetry', partitionKeyPath: '/_partitionKey' } + { name: 'processcontrol', partitionKeyPath: '/_partitionKey' } + { name: 'files', partitionKeyPath: '/_partitionKey' } + { name: 'process_statuses', partitionKeyPath: '/_partitionKey' } +] + +var processBlobContainerName = 'processes' +var processQueueName = 'processes-queue' + +// Default placeholder image for azd initial deploy +var defaultImage = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + +// ========== Resource Group Tag ========== // +resource resourceGroupTags 'Microsoft.Resources/tags@2023-07-01' = { + name: 'default' + properties: { + tags: union( + existingTags, + tags, + { + TemplateName: 'Container Migration' + CreatedBy: createdBy + DeploymentName: deployment().name + Type: 'Non-WAF' + } + ) + } +} + +// ========== Monitoring (Log Analytics) ========== // + +module log_analytics './modules/monitoring/log-analytics.bicep' = if (!useExistingLogAnalytics) { + name: take('module.log-analytics.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + } + scope: resourceGroup(resourceGroup().name) +} + +var logAnalyticsWorkspaceResourceId = useExistingLogAnalytics + ? existingLogAnalyticsWorkspaceId + : log_analytics!.outputs.resourceId + +// ========== AI Foundry and related resources ========== // + +module ai_foundry_project './modules/ai/ai-foundry-project.bicep' = if (empty(existingFoundryProjectResourceId)) { + name: take('module.ai-foundry-project.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: azureAiServiceLocation + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Unified AI Foundry resource name vars ========== // +var useExistingAIProject = !empty(existingFoundryProjectResourceId) +var aiFoundryResourceName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[8] : ai_foundry_project!.outputs.name +var aiProjectResourceName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[10] : ai_foundry_project!.outputs.projectName +var aiServiceSubscription = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[2] : subscription().subscriptionId +var aiServiceResourceGroup = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[4] : resourceGroup().name + +module existing_project_setup './modules/ai/existing-project-setup.bicep' = if (useExistingAIProject) { + name: take('module.existing-project-setup.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + name: aiFoundryResourceName + projectName: aiProjectResourceName + } +} + +module foundry_storage_connection './modules/ai/ai-foundry-connection.bicep' = { + name: take('module.foundry-storage-conn.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + solutionName: solutionSuffix + aiServicesAccountName: aiFoundryResourceName + projectName: aiProjectResourceName + category: 'AzureBlob' + target: storage_account.outputs.blobEndpoint + authType: 'AAD' + metadata: { + ResourceId: storage_account.outputs.resourceId + AccountName: storage_account.outputs.name + ContainerName: 'default' + } + } +} + +@batchSize(1) +module model_deployments './modules/ai/ai-foundry-model-deployment.bicep' = [for (deployment, i) in aiModelDeployments: { + name: take('module.model-deployment-${i}.${solutionName}', 64) + scope: resourceGroup(aiServiceSubscription, aiServiceResourceGroup) + params: { + aiServicesAccountName: aiFoundryResourceName + deploymentName: deployment.name + modelName: deployment.model + modelVersion: deployment.version + raiPolicyName: deployment.raiPolicyName + skuName: deployment.sku.name + skuCapacity: deployment.sku.capacity + } +}] + +var aiFoundryEndpoint = useExistingAIProject ? existing_project_setup!.outputs.aiFoundryEndpoint : ai_foundry_project!.outputs.endpoint +var projectEndpoint = useExistingAIProject ? existing_project_setup!.outputs.projectEndpoint : ai_foundry_project!.outputs.projectEndpoint +var aiFoundryResourceId = !useExistingAIProject ? ai_foundry_project!.outputs.resourceId : '' +var aiProjectPrincipalId = useExistingAIProject ? existing_project_setup!.outputs.aiProjectPrincipalId : ai_foundry_project!.outputs.projectIdentityPrincipalId + +// ========== Storage Account module ========== // +module storage_account './modules/data/storage-account.bicep' = { + name: take('module.storage-account.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + tags: tags + containers: [ + { name: 'data', publicAccess: 'None' } + ] + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Cosmos DB module ========== // +module cosmosDBModule './modules/data/cosmos-db.bicep' = { + name: take('module.cosmos-db.${solutionName}', 64) + params: { + solutionName: solutionSuffix + name: 'cosmos-${solutionSuffix}' + location: cosmosLocation + databaseName: cosmosDatabaseName + containers: cosmosContainers + } + scope: resourceGroup(resourceGroup().name) +} + +// ========== Container App Environment ========== // +module containerAppEnv './modules/compute/container-app-environment.bicep' = { + name: take('module.container-app-env.${solutionName}', 64) + params: { + solutionName: solutionSuffix + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + } +} + +// ========== Container Apps ========== // +var backendContainerAppName = take('ca-backend-api-${solutionSuffix}', 32) +var processorContainerAppName = take('ca-processor-${solutionSuffix}', 32) +var frontEndContainerAppName = take('ca-frontend-${solutionSuffix}', 32) + +// ========== Backend API Container App ========== // +module ca_backend_api './modules/compute/container-app.bicep' = { + name: take('module.ca-backend-api.${solutionName}', 64) + params: { + name: backendContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: true + ingressTargetPort: 80 + containers: [ + { + name: 'backend-api' + image: !empty(backendImageName) ? backendImageName : defaultImage + env: [ + { name: 'AZURE_OPENAI_ENDPOINT', value: aiFoundryEndpoint } + { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT_NAME', value: gptModelName } + { name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME', value: embeddingModel } + { name: 'AZURE_OPENAI_API_VERSION', value: '2025-03-01-preview' } + { name: 'COSMOS_DB_ACCOUNT_URL', value: cosmosDBModule.outputs.endpoint } + { name: 'COSMOS_DB_DATABASE_NAME', value: cosmosDatabaseName } + { name: 'COSMOS_DB_CONTAINER_NAME', value: 'agent_telemetry' } + { name: 'COSMOS_DB_CONTROL_CONTAINER_NAME', value: 'processcontrol' } + { name: 'COSMOS_DB_PROCESS_CONTAINER', value: 'processes' } + { name: 'COSMOS_DB_PROCESS_LOG_CONTAINER', value: 'agent_telemetry' } + { name: 'STORAGE_ACCOUNT_BLOB_URL', value: storage_account.outputs.blobEndpoint } + { name: 'STORAGE_ACCOUNT_NAME', value: storage_account.outputs.name } + { name: 'STORAGE_ACCOUNT_PROCESS_CONTAINER', value: processBlobContainerName } + { name: 'STORAGE_ACCOUNT_PROCESS_QUEUE', value: processQueueName } + { name: 'STORAGE_ACCOUNT_QUEUE_URL', value: '${storage_account.outputs.serviceEndpoints.queue}' } + { name: 'GLOBAL_LLM_SERVICE', value: 'AzureOpenAI' } + { name: 'PROCESSOR_CONTROL_URL', value: 'https://${processorContainerAppName}.internal.${containerAppEnv.outputs.defaultDomain}' } + { name: 'APP_ENV', value: 'Prod' } + ] + resources: { + cpu: json('1') + memory: '2.0Gi' + } + } + ] + corsPolicy: { + allowedOrigins: ['*'] + allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] + allowedHeaders: ['Authorization', 'Content-Type', '*'] + } + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Frontend Container App ========== // +module ca_frontend './modules/compute/container-app.bicep' = { + name: take('module.ca-frontend.${solutionName}', 64) + params: { + name: frontEndContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: true + ingressTargetPort: 3000 + containers: [ + { + name: 'frontend' + image: !empty(frontendImageName) ? frontendImageName : defaultImage + env: [ + { name: 'API_URL', value: 'https://${ca_backend_api.outputs.fqdn}' } + { name: 'APP_ENV', value: 'prod' } + { name: 'REACT_APP_MSAL_POST_REDIRECT_URL', value: '/' } + { name: 'REACT_APP_MSAL_REDIRECT_URL', value: '/' } + { name: 'ALLOWED_ORIGINS', value: 'https://${frontEndContainerAppName}.${containerAppEnv.outputs.defaultDomain}' } + ] + resources: { + cpu: json('1') + memory: '2.0Gi' + } + } + ] + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Processor Container App ========== // +module ca_processor './modules/compute/container-app.bicep' = { + name: take('module.ca-processor.${solutionName}', 64) + params: { + name: processorContainerAppName + location: solutionLocation + tags: union(existingTags, tags, { TemplateName: 'Container Migration' }) + environmentResourceId: containerAppEnv.outputs.resourceId + ingressExternal: false + ingressTargetPort: 8080 + ingressAllowInsecure: true + containers: [ + { + name: 'processor' + image: !empty(processorImageName) ? processorImageName : defaultImage + env: [ + { name: 'AZURE_OPENAI_ENDPOINT', value: aiFoundryEndpoint } + { name: 'AZURE_OPENAI_CHAT_DEPLOYMENT_NAME', value: gptModelName } + { name: 'AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME', value: embeddingModel } + { name: 'AZURE_OPENAI_API_VERSION', value: '2025-03-01-preview' } + { name: 'COSMOS_DB_ACCOUNT_URL', value: cosmosDBModule.outputs.endpoint } + { name: 'COSMOS_DB_DATABASE_NAME', value: cosmosDatabaseName } + { name: 'COSMOS_DB_CONTAINER_NAME', value: 'agent_telemetry' } + { name: 'COSMOS_DB_CONTROL_CONTAINER_NAME', value: 'processcontrol' } + { name: 'COSMOS_DB_PROCESS_CONTAINER', value: 'processes' } + { name: 'COSMOS_DB_PROCESS_LOG_CONTAINER', value: 'agent_telemetry' } + { name: 'STORAGE_ACCOUNT_BLOB_URL', value: storage_account.outputs.blobEndpoint } + { name: 'STORAGE_ACCOUNT_NAME', value: storage_account.outputs.name } + { name: 'STORAGE_ACCOUNT_PROCESS_CONTAINER', value: processBlobContainerName } + { name: 'STORAGE_ACCOUNT_PROCESS_QUEUE', value: processQueueName } + { name: 'STORAGE_ACCOUNT_QUEUE_URL', value: '${storage_account.outputs.serviceEndpoints.queue}' } + { name: 'GLOBAL_LLM_SERVICE', value: 'AzureOpenAI' } + { name: 'CONTROL_API_ENABLED', value: '1' } + { name: 'CONTROL_API_PORT', value: '8080' } + ] + resources: { + cpu: json('2') + memory: '4.0Gi' + } + } + ] + scaleSettings: { + maxReplicas: 1 + minReplicas: 1 + } + } +} + +// ========== Role Assignments ========== // +module role_assignments './modules/identity/role-assignments.bicep' = { + name: take('module.role-assignments.${solutionName}', 64) + params: { + solutionName: solutionSuffix + useExistingAIProject: useExistingAIProject + existingFoundryProjectResourceId: existingFoundryProjectResourceId + aiFoundryResourceId: !useExistingAIProject ? aiFoundryResourceId : '' + aiSearchResourceId: '' + storageAccountResourceId: storage_account.outputs.resourceId + aiProjectPrincipalId: aiProjectPrincipalId + aiSearchPrincipalId: '' + deployerPrincipalId: deployingUserPrincipalId + deployerPrincipalType: deployingUserPrincipalType + backendAppServicePrincipalId: ca_backend_api.outputs.principalId + cosmosDbAccountName: cosmosDBModule.outputs.name + existingAiProjectPrincipalId: !empty(existingFoundryProjectResourceId) ? existing_project_setup!.outputs.aiProjectPrincipalId : '' + } + scope: resourceGroup(resourceGroup().name) +} + +// ============================================================================== +// Outputs +// ============================================================================== + +@description('Solution suffix used for naming resources') +output SOLUTION_NAME string = solutionSuffix + +@description('Name of the deployed resource group') +output RESOURCE_GROUP_NAME string = resourceGroup().name + +@description('The name of the web app container app.') +output CONTAINER_WEB_APP_NAME string = ca_frontend.outputs.name + +@description('The FQDN of the web app container app.') +output CONTAINER_WEB_APP_FQDN string = ca_frontend.outputs.fqdn + +@description('The name of the API container app.') +output CONTAINER_API_APP_NAME string = ca_backend_api.outputs.name + +@description('The FQDN of the API container app.') +output CONTAINER_API_APP_FQDN string = ca_backend_api.outputs.fqdn + +@description('Azure OpenAI service endpoint URL') +output AZURE_OPENAI_ENDPOINT string = aiFoundryEndpoint + +@description('GPT model deployment name') +output AZURE_ENV_GPT_MODEL_NAME string = gptModelName + +@description('Embedding model deployment name') +output AZURE_ENV_EMBEDDING_DEPLOYMENT_NAME string = embeddingModel + +@description('Cosmos DB account name') +output AZURE_COSMOSDB_ACCOUNT string = cosmosDBModule.outputs.name + +@description('Cosmos DB database name') +output AZURE_COSMOSDB_DATABASE string = cosmosDatabaseName + +@description('The Azure subscription ID.') +output AZURE_SUBSCRIPTION_ID string = subscription().subscriptionId + +@description('The Azure resource group name.') +output AZURE_RESOURCE_GROUP string = resourceGroup().name + +@description('Azure AI Agent service endpoint URL') +output AZURE_AI_AGENT_ENDPOINT string = projectEndpoint diff --git a/infra/bicep/modules/ai/ai-foundry-connection.bicep b/infra/bicep/modules/ai/ai-foundry-connection.bicep new file mode 100644 index 00000000..b0af1f6d --- /dev/null +++ b/infra/bicep/modules/ai/ai-foundry-connection.bicep @@ -0,0 +1,84 @@ +// ============================================================================ +// Module: AI Foundry Project Connection (Single) — Vanilla Bicep +// Description: Creates a single connection on an AI Foundry project. +// Generic, reusable — call once per connection type from main.bicep. +// Supports any connection category (CognitiveSearch, AzureBlob, +// AppInsights, RemoteTool, etc.) via parameterized properties. +// ============================================================================ + +targetScope = 'resourceGroup' + +@description('Required. Name of the parent AI Services account.') +param aiServicesAccountName string + +@description('Required. Name of the AI Foundry project.') +param projectName string + +@description('Required. Solution name suffix used to generate the connection name.') +param solutionName string + +@description('Optional. Connection name. Defaults to lowercase category with solution suffix.') +param connectionName string = toLower('${category}-connection-${solutionName}') + +@description('Required. Connection category (e.g., CognitiveSearch, AzureBlob, AppInsights, RemoteTool).') +param category string + +@description('Required. Connection target (URL or resource ID).') +param target string + +@description('Required. Authentication type (e.g., AAD, ApiKey, ProjectManagedIdentity).') +param authType string + +@description('Optional. Whether the connection is shared to all project users.') +param isSharedToAll bool = true + +@description('Optional. Whether this is the default connection for its category.') +param isDefault bool = false + +@description('Optional. Connection metadata object.') +param metadata object = {} + +@secure() +@description('Optional. Credentials key (for ApiKey auth type).') +param credentialsKey string = '' + +// ============================================================================ +// Existing Resource References +// ============================================================================ +resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: aiServicesAccountName +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' existing = { + parent: aiServicesAccount + name: projectName +} + +// ============================================================================ +// Connection +// ============================================================================ +var baseProperties = { + category: category + target: target + authType: authType + isSharedToAll: isSharedToAll + metadata: metadata +} + +var optionalDefault = isDefault ? { isDefault: true } : {} +var optionalCredentials = !empty(credentialsKey) ? { credentials: { key: credentialsKey } } : {} + +resource connection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-12-01' = { + parent: aiProject + name: connectionName + properties: any(union(baseProperties, optionalDefault, optionalCredentials)) +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Connection name.') +output connectionName string = connection.name + +@description('Connection resource ID.') +output connectionId string = connection.id diff --git a/infra/bicep/modules/ai/ai-foundry-model-deployment.bicep b/infra/bicep/modules/ai/ai-foundry-model-deployment.bicep new file mode 100644 index 00000000..4ed69a72 --- /dev/null +++ b/infra/bicep/modules/ai/ai-foundry-model-deployment.bicep @@ -0,0 +1,66 @@ +// ============================================================================ +// Module: Model Deployment — Vanilla Bicep +// Description: Deploys a single AI model to an existing AI Services account. +// Called repetitively from main.bicep for each model in the array. +// Generic, reusable across GSAs. +// ============================================================================ + +targetScope = 'resourceGroup' + +@description('Required. Name of the parent AI Services account.') +param aiServicesAccountName string + +@description('Required. Name for this model deployment.') +param deploymentName string + +@description('Optional. Model format (e.g., OpenAI).') +param modelFormat string = 'OpenAI' + +@description('Required. Model name (e.g., gpt-4o, text-embedding-ada-002).') +param modelName string + +@description('Optional. Model version. Empty string means latest.') +param modelVersion string = '' + +@description('Optional. RAI policy name.') +param raiPolicyName string = 'Microsoft.Default' + +@description('Required. SKU name (e.g., Standard, GlobalStandard).') +param skuName string + +@description('Required. SKU capacity (tokens per minute in thousands).') +param skuCapacity int + +// ============================================================================ +// Model Deployment +// ============================================================================ +resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: aiServicesAccountName +} + +resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-12-01' = { + parent: aiServicesAccount + name: deploymentName + properties: { + model: { + format: modelFormat + name: modelName + version: !empty(modelVersion) ? modelVersion : null + } + raiPolicyName: raiPolicyName + } + sku: { + name: skuName + capacity: skuCapacity + } +} + +// ============================================================================ +// Outputs +// ============================================================================ + +@description('Name of the deployed model.') +output name string = modelDeployment.name + +@description('Resource ID of the model deployment.') +output resourceId string = modelDeployment.id diff --git a/infra/bicep/modules/ai/ai-foundry-project.bicep b/infra/bicep/modules/ai/ai-foundry-project.bicep new file mode 100644 index 00000000..9d0dd61f --- /dev/null +++ b/infra/bicep/modules/ai/ai-foundry-project.bicep @@ -0,0 +1,111 @@ +// ============================================================================ +// Module: AI Foundry Project (Account + Project) — Vanilla Bicep +// Description: Creates an Azure AI Services account and AI Foundry project. +// Generic, reusable across GSAs — no app-specific parameters. +// ============================================================================ + +targetScope = 'resourceGroup' + +@description('Required. Solution name suffix used to generate resource names.') +param solutionName string + +@description('Optional. Override name for the AI Services account. Defaults to aif-{solutionName}.') +param name string = 'aif-${solutionName}' + +@description('Optional. Override name for the AI Foundry project. Defaults to proj-{solutionName}.') +param projectName string = 'proj-${solutionName}' + +@description('Required. Azure region for the resources.') +param location string + +@description('Optional. Tags to apply to resources.') +param tags object = {} + +@description('Optional. SKU name for the AI Services account.') +param skuName string = 'S0' + +@description('Optional. Whether to disable local (key-based) authentication.') +param disableLocalAuth bool = true + +@description('Optional. Whether to allow project management (AI Foundry hub).') +param allowProjectManagement bool = true + +@description('Optional. Public network access setting.') +param publicNetworkAccess string = 'Enabled' + +@description('Optional. Managed identity type for the resources.') +@allowed(['SystemAssigned', 'UserAssigned', 'SystemAssigned, UserAssigned', 'None']) +param identityType string = 'SystemAssigned' + +@description('Optional. Network ACLs default action.') +@allowed(['Allow', 'Deny']) +param networkAclsDefaultAction string = 'Allow' + +// ============================================================================ +// AI Services Account +// ============================================================================ +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-12-01' = { + name: name + location: location + tags: tags + sku: { + name: skuName + } + kind: 'AIServices' + identity: { + type: identityType + } + properties: { + allowProjectManagement: allowProjectManagement + customSubDomainName: name + networkAcls: { + defaultAction: networkAclsDefaultAction + virtualNetworkRules: [] + ipRules: [] + } + publicNetworkAccess: publicNetworkAccess + disableLocalAuth: disableLocalAuth + } +} + +// ============================================================================ +// AI Foundry Project +// ============================================================================ +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' = { + parent: aiServices + name: projectName + location: location + kind: 'AIServices' + identity: { + type: identityType + } + properties: {} +} + +// ============================================================================ +// Outputs +// ============================================================================ + +@description('Resource ID of the AI Services account.') +output resourceId string = aiServices.id + +@description('Name of the AI Services account.') +output name string = aiServices.name + +@description('Endpoint of the AI Services account (OpenAI Language Model Instance API).') +output endpoint string = aiServices.properties.endpoints['OpenAI Language Model Instance API'] + +@description('System-assigned identity principal ID of the AI Services account.') +output principalId string = aiServices.identity.principalId + +@description('Resource ID of the AI Foundry project.') +output projectResourceId string = aiProject.id + +@description('Name of the AI Foundry project.') +output projectName string = aiProject.name + +@description('AI Foundry project endpoint.') +output projectEndpoint string = aiProject.properties.endpoints['AI Foundry API'] + +@description('System-assigned identity principal ID of the project.') +output projectIdentityPrincipalId string = aiProject.identity.principalId diff --git a/infra/bicep/modules/ai/existing-project-setup.bicep b/infra/bicep/modules/ai/existing-project-setup.bicep new file mode 100644 index 00000000..37cd666d --- /dev/null +++ b/infra/bicep/modules/ai/existing-project-setup.bicep @@ -0,0 +1,51 @@ +// ============================================================================ +// Module: Existing AI Foundry Project Reference — Vanilla Bicep +// Description: References an existing AI Services account and project to +// retrieve their identities. No deployments, no connections. +// Use generic ai-foundry-connection and ai-foundry-model-deployment +// modules for those concerns. +// ============================================================================ + +@description('Required. The name of the existing Cognitive Services account.') +param name string + +@description('Required. The name of the existing AI project.') +param projectName string + +// ============================================================================ +// Existing Resource References +// ============================================================================ + +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: name +} + +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' existing = { + parent: aiServices + name: projectName +} + +// ============================================================================ +// Outputs +// ============================================================================ + +@description('The principal ID of the AI Foundry system-assigned managed identity.') +output aiFoundryPrincipalId string = contains(aiServices, 'identity') && contains(aiServices.identity, 'principalId') ? aiServices.identity.principalId : '' + +@description('The principal ID of the AI Project system-assigned managed identity.') +output aiProjectPrincipalId string = contains(aiProject, 'identity') && contains(aiProject.identity, 'principalId') ? aiProject.identity.principalId : '' + +@description('The name of the AI Services account.') +output aiServicesAccountName string = aiServices.name + +@description('The name of the AI project.') +output aiProjectName string = aiProject.name + +@description('The endpoint URL for the Azure OpenAI service.') +output aiFoundryEndpoint string = 'https://${name}.openai.azure.com/' + +@description('The endpoint URL for the AI Foundry project.') +output projectEndpoint string = 'https://${name}.services.ai.azure.com/api/projects/${projectName}' + +@description('The resource ID of the AI Services account.') +output aiFoundryResourceId string = aiServices.id diff --git a/infra/bicep/modules/compute/container-app-environment.bicep b/infra/bicep/modules/compute/container-app-environment.bicep new file mode 100644 index 00000000..c565a158 --- /dev/null +++ b/infra/bicep/modules/compute/container-app-environment.bicep @@ -0,0 +1,63 @@ +// ============================================================================ +// Module: Azure Container Apps Environment +// Description: Creates an Azure Container Apps managed environment +// API: Microsoft.App/managedEnvironments@2024-03-01 +// ============================================================================ + +@description('Solution name used for naming convention.') +param solutionName string + +@description('Name of the Container Apps Environment.') +param name string = 'cae-${solutionName}' + +@description('Azure region for deployment.') +param location string + +@description('Resource tags.') +param tags object = {} + +@description('Resource ID of the Log Analytics workspace.') +param logAnalyticsWorkspaceResourceId string + +@description('Subnet resource ID for VNet integration (optional).') +param infrastructureSubnetId string = '' + +@description('Enable zone redundancy.') +param zoneRedundant bool = false + +// ============================================================================ +// Resource Deployment +// ============================================================================ +resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: reference(logAnalyticsWorkspaceResourceId, '2023-09-01').customerId + sharedKey: listKeys(logAnalyticsWorkspaceResourceId, '2023-09-01').primarySharedKey + } + } + vnetConfiguration: empty(infrastructureSubnetId) ? null : { + infrastructureSubnetId: infrastructureSubnetId + } + zoneRedundant: zoneRedundant + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('The name of the Container Apps Environment.') +output name string = containerAppEnvironment.name + +@description('The resource ID of the Container Apps Environment.') +output resourceId string = containerAppEnvironment.id + +@description('The default domain of the Container Apps Environment.') +output defaultDomain string = containerAppEnvironment.properties.defaultDomain + +@description('The static IP address of the Container Apps Environment.') +output staticIp string = containerAppEnvironment.properties.staticIp diff --git a/infra/bicep/modules/compute/container-app.bicep b/infra/bicep/modules/compute/container-app.bicep new file mode 100644 index 00000000..15596c7d --- /dev/null +++ b/infra/bicep/modules/compute/container-app.bicep @@ -0,0 +1,117 @@ +// ============================================================================ +// Module: Azure Container App +// Description: Creates an Azure Container App +// API: Microsoft.App/containerApps@2024-10-02-preview +// ============================================================================ + +@description('Name of the container app.') +param name string + +@description('Azure region for deployment.') +param location string + +@description('Resource tags.') +param tags object = {} + +@description('Resource ID of the Container Apps Environment.') +param environmentResourceId string + +@description('Container definitions.') +param containers array + +@description('Enable external ingress.') +param ingressExternal bool = true + +@description('Target port for ingress.') +param ingressTargetPort int = 80 + +@description('Ingress transport protocol.') +@allowed(['auto', 'http', 'http2', 'tcp']) +param ingressTransport string = 'auto' + +@description('Whether to allow insecure ingress connections.') +param ingressAllowInsecure bool = false + +@description('Disable ingress entirely (for background workers).') +param disableIngress bool = false + +@description('Container registry configurations.') +param registries array? + +@description('Secret definitions.') +param secrets array? + +@description('Managed identity configuration.') +param managedIdentities object = {} + +@description('CORS policy configuration.') +param corsPolicy object = {} + +@description('Active revision mode.') +@allowed(['Single', 'Multiple']) +param activeRevisionsMode string = 'Single' + +@description('Scale settings (maxReplicas, minReplicas, rules).') +param scaleSettings object = { + maxReplicas: 10 + minReplicas: 0 +} + +@description('Workload profile name.') +param workloadProfileName string? + +// ============================================================================ +// Resource Deployment +// ============================================================================ +var identityConfig = empty(managedIdentities) ? { type: 'None' } : { + type: contains(managedIdentities, 'userAssignedResourceIds') ? (contains(managedIdentities, 'systemAssigned') && managedIdentities.systemAssigned ? 'SystemAssigned,UserAssigned' : 'UserAssigned') : 'SystemAssigned' + userAssignedIdentities: contains(managedIdentities, 'userAssignedResourceIds') ? reduce(managedIdentities.userAssignedResourceIds, {}, (cur, id) => union(cur, { '${id}': {} })) : null +} + +var ingressConfig = disableIngress ? null : { + external: ingressExternal + targetPort: ingressTargetPort + transport: ingressTransport + allowInsecure: ingressAllowInsecure + corsPolicy: !empty(corsPolicy) ? corsPolicy : null +} + +resource containerApp 'Microsoft.App/containerApps@2024-10-02-preview' = { + name: name + location: location + tags: tags + identity: identityConfig + properties: { + managedEnvironmentId: environmentResourceId + workloadProfileName: workloadProfileName + configuration: { + activeRevisionsMode: activeRevisionsMode + ingress: ingressConfig + registries: registries + secrets: secrets + } + template: { + containers: containers + scale: { + minReplicas: scaleSettings.minReplicas + maxReplicas: scaleSettings.maxReplicas + rules: contains(scaleSettings, 'rules') ? scaleSettings.rules : null + } + } + } +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('The name of the container app.') +output name string = containerApp.name + +@description('The resource ID of the container app.') +output resourceId string = containerApp.id + +@description('The FQDN of the container app.') +output fqdn string = !disableIngress ? containerApp.properties.configuration.ingress.fqdn : '' + +@description('System-assigned identity principal ID.') +output principalId string = contains(containerApp.identity.type, 'SystemAssigned') ? containerApp.identity.principalId : '' diff --git a/infra/bicep/modules/data/cosmos-db.bicep b/infra/bicep/modules/data/cosmos-db.bicep new file mode 100644 index 00000000..9c758c2e --- /dev/null +++ b/infra/bicep/modules/data/cosmos-db.bicep @@ -0,0 +1,90 @@ +// ============================================================================ +// Module: Cosmos DB +// Description: Creates an Azure Cosmos DB (NoSQL) account with database/container +// API: Microsoft.DocumentDB/databaseAccounts@2025-10-15 +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Name of the Cosmos DB account.') +param name string = 'cosmos-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Database name.') +param databaseName string = 'db_conversation_history' + +@description('Container definitions.') +param containers array = [ + { + name: 'conversations' + partitionKeyPath: '/userId' + } +] + +// ============================================================================ +// Resource Deployment +// ============================================================================ +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2025-10-15' = { + name: name + location: location + tags: tags + kind: 'GlobalDocumentDB' + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + databaseAccountOfferType: 'Standard' + enableAutomaticFailover: false + enableMultipleWriteLocations: false + disableLocalAuth: true + capabilities: [ { name: 'EnableServerless' } ] + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2025-10-15' = { + parent: cosmos + name: databaseName + properties: { + resource: { id: databaseName } + } + + resource list 'containers' = [for container in containers: { + name: container.name + properties: { + resource: { + id: container.name + partitionKey: { paths: [ container.partitionKeyPath ] } + } + options: {} + } + }] +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Cosmos DB account.') +output resourceId string = cosmos.id + +@description('Name of the Cosmos DB account.') +output name string = cosmos.name + +@description('Endpoint of the Cosmos DB account.') +output endpoint string = 'https://${name}.documents.azure.com:443/' + +@description('Database name.') +output databaseName string = databaseName + +@description('Container name (first container).') +output containerName string = containers[0].name diff --git a/infra/bicep/modules/data/storage-account.bicep b/infra/bicep/modules/data/storage-account.bicep new file mode 100644 index 00000000..ea9d5386 --- /dev/null +++ b/infra/bicep/modules/data/storage-account.bicep @@ -0,0 +1,101 @@ +// ============================================================================ +// Module: Storage Account +// Description: Creates an Azure Storage Account with blob container +// API: Microsoft.Storage/storageAccounts@2025-08-01 +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Name of the storage account.') +param name string = take('st${toLower(replace(solutionName, '-', ''))}', 24) + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Storage account SKU.') +param skuName string = 'Standard_LRS' + +@description('Storage account kind.') +param kind string = 'StorageV2' + +@description('Access tier.') +@allowed(['Hot', 'Cool']) +param accessTier string = 'Hot' + +@description('Allow blob public access.') +param allowBlobPublicAccess bool = false + +@description('Allow shared key access.') +param allowSharedKeyAccess bool = true + +@description('Blob containers to create.') +param containers array = [ + { + name: 'default' + publicAccess: 'None' + } +] + +// ============================================================================ +// Resource Deployment +// ============================================================================ +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-08-01' = { + name: name + location: location + tags: tags + kind: kind + sku: { + name: skuName + } + properties: { + accessTier: accessTier + allowBlobPublicAccess: allowBlobPublicAccess + allowSharedKeyAccess: allowSharedKeyAccess + minimumTlsVersion: 'TLS1_2' + supportsHttpsTrafficOnly: true + encryption: { + services: { + blob: { + enabled: true + } + file: { + enabled: true + } + } + keySource: 'Microsoft.Storage' + requireInfrastructureEncryption: true + } + } +} + +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2025-08-01' = { + parent: storageAccount + name: 'default' +} + +resource blobContainers 'Microsoft.Storage/storageAccounts/blobServices/containers@2025-08-01' = [for container in containers: { + parent: blobService + name: container.name + properties: { + publicAccess: container.publicAccess + } +}] + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the Storage Account.') +output resourceId string = storageAccount.id + +@description('Name of the Storage Account.') +output name string = storageAccount.name + +@description('Primary blob endpoint.') +output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob + +@description('All service endpoints.') +output serviceEndpoints object = storageAccount.properties.primaryEndpoints diff --git a/infra/bicep/modules/identity/cross-scope-role-assignment.bicep b/infra/bicep/modules/identity/cross-scope-role-assignment.bicep new file mode 100644 index 00000000..19e43cc0 --- /dev/null +++ b/infra/bicep/modules/identity/cross-scope-role-assignment.bicep @@ -0,0 +1,37 @@ +// ============================================================================ +// cross-scope-role-assignment.bicep +// Description: Reusable helper that creates a single role assignment scoped +// to an existing AI Services resource. Used for cross-resource- +// group RBAC where the AI Services lives in a different RG. +// ============================================================================ + +@description('The principal ID to assign the role to.') +param principalId string + +@description('The resource ID of the role definition to assign.') +param roleDefinitionId string + +@description('A unique name for the role assignment.') +param roleAssignmentName string + +@description('The name of the AI Services account to scope the role assignment to.') +param aiFoundryName string + +@description('The principal type of the identity being assigned.') +@allowed(['ServicePrincipal', 'User']) +param principalType string = 'ServicePrincipal' + +// Reference the existing AI Services resource in this resource group +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = { + name: aiFoundryName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: roleAssignmentName + scope: aiServices + properties: { + roleDefinitionId: roleDefinitionId + principalId: principalId + principalType: principalType + } +} diff --git a/infra/bicep/modules/identity/managed-identity.bicep b/infra/bicep/modules/identity/managed-identity.bicep new file mode 100644 index 00000000..e8accb80 --- /dev/null +++ b/infra/bicep/modules/identity/managed-identity.bicep @@ -0,0 +1,43 @@ +// ============================================================================ +// Module: User-Assigned Managed Identity (Generic) +// Description: Creates a user-assigned managed identity. +// This module is NOT called from main.bicep by default. +// Use it when you need a user-assigned identity for specific scenarios +// (e.g., Container Apps, cross-tenant access, pre-provisioned RBAC). +// ============================================================================ + +@description('Solution name used for resource naming.') +param solutionName string + +@description('Name of the managed identity.') +param identityName string = 'id-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +// ============================================================================ +// Resource Deployment +// ============================================================================ +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: identityName + location: location + tags: tags +} + +// ============================================================================ +// Outputs +// ============================================================================ +@description('Resource ID of the managed identity.') +output resourceId string = managedIdentity.id + +@description('Principal ID (object ID) of the managed identity.') +output principalId string = managedIdentity.properties.principalId + +@description('Client ID of the managed identity.') +output clientId string = managedIdentity.properties.clientId + +@description('Name of the managed identity.') +output name string = managedIdentity.name diff --git a/infra/bicep/modules/identity/role-assignments.bicep b/infra/bicep/modules/identity/role-assignments.bicep new file mode 100644 index 00000000..716233ec --- /dev/null +++ b/infra/bicep/modules/identity/role-assignments.bicep @@ -0,0 +1,350 @@ +// ============================================================================ +// Module: Role Assignments (centralized — all cross-service + data plane RBAC) +// Description: RG-level, cross-service, and data-plane role assignments. +// One place to audit "who has access to what". +// ============================================================================ + +// ============================================================================ +// Parameters +// ============================================================================ + +@description('Solution name suffix for generating unique role assignment GUIDs.') +param solutionName string = '' + +@description('Whether to use an existing AI project (true) or create new (false).') +param useExistingAIProject bool = false + +@description('Resource ID of the existing AI project (for deriving AI Services name/sub/RG).') +param existingFoundryProjectResourceId string = '' + +// --- Identity Principal IDs --- + +@description('Principal ID of the AI project identity.') +param aiProjectPrincipalId string = '' + +@description('Principal ID of the existing AI project identity (for cross-service roles).') +param existingAiProjectPrincipalId string = '' + +@description('Principal ID of the AI Search identity.') +param aiSearchPrincipalId string = '' + +@description('Principal ID of the backend App Service system-assigned identity (empty if not deployed).') +param backendAppServicePrincipalId string = '' + +@description('Principal ID of the deploying user (for user access roles).') +param deployerPrincipalId string = '' + +@description('Principal type of the deploying user.') +@allowed(['User', 'ServicePrincipal']) +param deployerPrincipalType string = 'User' + +// --- Resource References --- + +@description('Resource ID of the AI Foundry account (empty if not deployed — new project path).') +param aiFoundryResourceId string = '' + +@description('Resource ID of the AI Search service (empty if not deployed).') +param aiSearchResourceId string = '' + +@description('Resource ID of the Storage Account (empty if not deployed).') +param storageAccountResourceId string = '' + +@description('Name of the Cosmos DB account (empty if not deployed).') +param cosmosDbAccountName string = '' + +// ============================================================================ +// Derived Variables +// ============================================================================ + +var existingAIFoundryName = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[8] : '' +var existingAIFoundrySubscription = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[2] : subscription().subscriptionId +var existingAIFoundryResourceGroup = useExistingAIProject ? split(existingFoundryProjectResourceId, '/')[4] : resourceGroup().name + +// ============================================================================ +// Role Definitions +// ============================================================================ + +var roleDefinitions = { + azureAiUser: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Foundry User + cognitiveServicesUser: 'a97b65f3-24c7-4388-baec-2e87135dc908' + cognitiveServicesOpenAIUser: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' + searchIndexDataReader: '1407120a-92aa-4202-b7e9-c0e197c71c8f' + searchIndexDataContributor: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' + searchServiceContributor: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' + storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' + storageBlobDataReader: '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1' +} + +// ============================================================================ +// Existing Resource References +// ============================================================================ + +resource aiFoundryAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = if (!empty(aiFoundryResourceId)) { + name: last(split(aiFoundryResourceId, '/')) +} + +resource aiSearchService 'Microsoft.Search/searchServices@2025-05-01' existing = if (!empty(aiSearchResourceId)) { + name: last(split(aiSearchResourceId, '/')) +} + +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-08-01' existing = if (!empty(storageAccountResourceId)) { + name: last(split(storageAccountResourceId, '/')) +} + +resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2025-10-15' existing = if (!empty(cosmosDbAccountName)) { + name: cosmosDbAccountName +} + +resource cosmosContributorRoleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2025-10-15' existing = if (!empty(cosmosDbAccountName)) { + parent: cosmosAccount + name: '00000000-0000-0000-0000-000000000002' // Cosmos DB Built-in Data Contributor +} + +// ============================================================================ +// 1. AI SERVICES ROLE ASSIGNMENTS +// Cross-service roles scoped to AI Foundry account +// ============================================================================ + +// AI Search → Cognitive Services OpenAI User on AI Foundry (new project, same RG) +resource assignOpenAIRoleToAISearch 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAIProject && !empty(aiSearchPrincipalId) && !empty(aiFoundryResourceId)) { + name: guid(resourceGroup().id, aiFoundryAccount.id, roleDefinitions.cognitiveServicesOpenAIUser, 'search-openai') + scope: aiFoundryAccount + properties: { + principalId: aiSearchPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.cognitiveServicesOpenAIUser) + principalType: 'ServicePrincipal' + } +} + +// AI Search → Cognitive Services OpenAI User on existing AI Foundry (cross-scope) +module assignOpenAIToSearchExisting './cross-scope-role-assignment.bicep' = if (useExistingAIProject && !empty(aiSearchPrincipalId)) { + name: 'assignOpenAIRoleToAISearchExisting' + scope: resourceGroup(existingAIFoundrySubscription, existingAIFoundryResourceGroup) + params: { + principalId: aiSearchPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.cognitiveServicesOpenAIUser) + roleAssignmentName: guid(solutionName, 'search-openai', existingAIFoundryName, roleDefinitions.cognitiveServicesOpenAIUser) + aiFoundryName: existingAIFoundryName + } +} + +// Backend App Service → Foundry User on AI Foundry (new project, same RG) +resource backendAppAiUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAIProject && !empty(aiFoundryResourceId) && !empty(backendAppServicePrincipalId)) { + name: guid(resourceGroup().id, backendAppServicePrincipalId, roleDefinitions.azureAiUser, 'backend-ai-services') + scope: aiFoundryAccount + properties: { + principalId: backendAppServicePrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.azureAiUser) + principalType: 'ServicePrincipal' + } +} + +// Backend App Service → Foundry User on existing AI Foundry (cross-scope) +module backendAppAiUserExisting './cross-scope-role-assignment.bicep' = if (useExistingAIProject && !empty(backendAppServicePrincipalId)) { + name: 'assignAiUserRoleToBackendExisting' + scope: resourceGroup(existingAIFoundrySubscription, existingAIFoundryResourceGroup) + params: { + principalId: backendAppServicePrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.azureAiUser) + roleAssignmentName: guid(solutionName, 'backend-aiuser', existingAIFoundryName, roleDefinitions.azureAiUser) + aiFoundryName: existingAIFoundryName + } +} + +// ============================================================================ +// 2. SEARCH SERVICE ROLE ASSIGNMENTS +// AI Project and Backend identities → AI Search +// ============================================================================ + +// AI Project → Search Index Data Reader on AI Search (new project) +resource projectSearchReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiSearchResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.searchIndexDataReader, 'project-search') + scope: aiSearchService + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataReader) + principalType: 'ServicePrincipal' + } +} + +// AI Project → Search Service Contributor on AI Search (new project) +resource projectSearchContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiSearchResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.searchServiceContributor, 'project-search') + scope: aiSearchService + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchServiceContributor) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Search Index Data Reader on AI Search +resource existingProjectSearchReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(aiSearchResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.searchIndexDataReader, 'existing-project-search') + scope: aiSearchService + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataReader) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Search Service Contributor on AI Search +resource existingProjectSearchContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(aiSearchResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.searchServiceContributor, 'existing-project-search') + scope: aiSearchService + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchServiceContributor) + principalType: 'ServicePrincipal' + } +} + +// Backend App Service → Search Index Data Reader on AI Search +resource backendAppSearchReaderAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(aiSearchResourceId) && !empty(backendAppServicePrincipalId)) { + name: guid(resourceGroup().id, backendAppServicePrincipalId, roleDefinitions.searchIndexDataReader, 'backend-search') + scope: aiSearchService + properties: { + principalId: backendAppServicePrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataReader) + principalType: 'ServicePrincipal' + } +} + +// ============================================================================ +// 3. STORAGE ROLE ASSIGNMENTS +// AI Project, AI Search, and Existing Project identities → Storage +// ============================================================================ + +// AI Project → Storage Blob Data Contributor (new project) +resource projectStorageContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(storageAccountResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.storageBlobDataContributor, 'project-storage') + scope: storageAccount + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataContributor) + principalType: 'ServicePrincipal' + } +} + +// AI Project → Storage Blob Data Reader (new project) +resource projectStorageReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(storageAccountResourceId) && !empty(aiProjectPrincipalId)) { + name: guid(resourceGroup().id, aiProjectPrincipalId, roleDefinitions.storageBlobDataReader, 'project-storage') + scope: storageAccount + properties: { + principalId: aiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataReader) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Storage Blob Data Contributor +resource existingProjectStorageContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(storageAccountResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.storageBlobDataContributor, 'existing-project-storage') + scope: storageAccount + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataContributor) + principalType: 'ServicePrincipal' + } +} + +// Existing AI Project → Storage Blob Data Reader +resource existingProjectStorageReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (useExistingAIProject && !empty(storageAccountResourceId) && !empty(existingAiProjectPrincipalId)) { + name: guid(resourceGroup().id, existingAiProjectPrincipalId, roleDefinitions.storageBlobDataReader, 'existing-project-storage') + scope: storageAccount + properties: { + principalId: existingAiProjectPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataReader) + principalType: 'ServicePrincipal' + } +} + +// AI Search → Storage Blob Data Reader +resource searchStorageReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(storageAccountResourceId) && !empty(aiSearchPrincipalId)) { + name: guid(resourceGroup().id, aiSearchPrincipalId, roleDefinitions.storageBlobDataReader, 'search-storage') + scope: storageAccount + properties: { + principalId: aiSearchPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataReader) + principalType: 'ServicePrincipal' + } +} + +// ============================================================================ +// 4. COSMOS DB ROLE ASSIGNMENTS +// Backend App Service → Cosmos DB (data-plane, uses sqlRoleAssignments) +// ============================================================================ + +resource backendAppCosmosRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2025-10-15' = if (!empty(cosmosDbAccountName) && !empty(backendAppServicePrincipalId)) { + parent: cosmosAccount + name: guid(cosmosContributorRoleDefinition.id, cosmosAccount.id, backendAppServicePrincipalId) + properties: { + principalId: backendAppServicePrincipalId + roleDefinitionId: cosmosContributorRoleDefinition.id + scope: cosmosAccount.id + } +} + +// ============================================================================ +// 5. DEPLOYER (USER) ROLE ASSIGNMENTS +// Deploying user → AI Services, Search, Storage (Bicep-only) +// ============================================================================ + +// Deploying User → Cognitive Services User on AI Services +resource deployerAiServicesAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAIProject && !empty(deployerPrincipalId) && !empty(aiFoundryResourceId)) { + scope: aiFoundryAccount + name: guid(aiFoundryAccount.id, deployerPrincipalId, roleDefinitions.cognitiveServicesUser) + properties: { + principalId: deployerPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.cognitiveServicesUser) + principalType: deployerPrincipalType + } +} + +// Deploying User → Foundry User on AI Services +resource deployerAzureAIAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!useExistingAIProject && !empty(deployerPrincipalId) && !empty(aiFoundryResourceId)) { + scope: aiFoundryAccount + name: guid(aiFoundryAccount.id, deployerPrincipalId, roleDefinitions.azureAiUser) + properties: { + principalId: deployerPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.azureAiUser) + principalType: deployerPrincipalType + } +} + +// Deploying User → Search Index Data Contributor on AI Search +resource deployerSearchIndexContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(deployerPrincipalId) && !empty(aiSearchResourceId)) { + scope: aiSearchService + name: guid(aiSearchService.id, deployerPrincipalId, roleDefinitions.searchIndexDataContributor) + properties: { + principalId: deployerPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchIndexDataContributor) + principalType: deployerPrincipalType + } +} + +// Deploying User → Search Service Contributor on AI Search +resource deployerSearchServiceContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(deployerPrincipalId) && !empty(aiSearchResourceId)) { + scope: aiSearchService + name: guid(aiSearchService.id, deployerPrincipalId, roleDefinitions.searchServiceContributor) + properties: { + principalId: deployerPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.searchServiceContributor) + principalType: deployerPrincipalType + } +} + +// Deploying User → Storage Blob Data Contributor +resource deployerStorageBlobContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(deployerPrincipalId) && !empty(storageAccountResourceId)) { + scope: storageAccount + name: guid(storageAccount.id, deployerPrincipalId, roleDefinitions.storageBlobDataContributor) + properties: { + principalId: deployerPrincipalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.storageBlobDataContributor) + principalType: deployerPrincipalType + } +} + +// NOTE: Deployer roles on existing AI Foundry (cross-scope) are assigned via +// 00_build_solution.py to avoid conflicts when the deployer already has the roles. diff --git a/infra/bicep/modules/monitoring/log-analytics.bicep b/infra/bicep/modules/monitoring/log-analytics.bicep new file mode 100644 index 00000000..87d79740 --- /dev/null +++ b/infra/bicep/modules/monitoring/log-analytics.bicep @@ -0,0 +1,58 @@ +// ============================================================================ +// Module: Log Analytics Workspace +// Description: Vanilla Bicep module for Log Analytics Workspace +// Resource: Microsoft.OperationalInsights/workspaces@2023-09-01 +// Docs: https://learn.microsoft.com/azure/templates/microsoft.operationalinsights/workspaces +// Note: This module only handles NEW workspace creation. +// Existing workspace logic is handled in main.bicep. +// ============================================================================ + +@description('Solution name suffix used to derive the resource name.') +param solutionName string + +@description('Optional. Override name for the Log Analytics workspace. Defaults to log-{solutionName}.') +param name string = 'log-${solutionName}' + +@description('Azure region for the resource.') +param location string + +@description('Tags to apply to the resource.') +param tags object = {} + +@description('Retention period in days.') +param retentionInDays int = 365 + +@description('SKU name for the workspace.') +param skuName string = 'PerGB2018' + +// ============================================================================ +// Resource +// ============================================================================ + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { + name: name + location: location + tags: tags + properties: { + retentionInDays: retentionInDays + sku: { + name: skuName + } + } +} + +// ============================================================================ +// Outputs +// ============================================================================ + +@description('Resource ID of the Log Analytics workspace.') +output resourceId string = logAnalytics.id + +@description('Name of the Log Analytics workspace.') +output name string = logAnalytics.name + +@description('Location of the workspace.') +output location string = logAnalytics.location + +@description('Log Analytics workspace customer ID.') +output logAnalyticsWorkspaceId string = logAnalytics.properties.customerId diff --git a/infra/build_bicep.md b/infra/build_bicep.md new file mode 100644 index 00000000..8b19d4f1 --- /dev/null +++ b/infra/build_bicep.md @@ -0,0 +1,53 @@ +# Build Bicep -> ARM + +This repo ships both `main.bicep` (authoring source) and `main.json` (compiled +ARM template) for each deployable root template. After editing any `.bicep` +file you must regenerate the corresponding `.json` so the two stay in sync. + +## Prerequisites + +- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) `>= 2.55` +- Bicep CLI: `az bicep install` or `az bicep upgrade` + +## Top-level templates + +```bash +# From the repository root +az bicep build --file infra/main.bicep +az bicep build --file infra/main_custom.bicep +``` + +## Sub-folder templates + +The `infra/bicep/` folder mirrors the top-level templates (the "custom Bicep" +implementation kept side-by-side with future AVM templates under +`infra/avm/`). + +```bash +az bicep build --file infra/bicep/main.bicep +az bicep build --file infra/bicep/main_custom.bicep +``` + +When the AVM rewrite under `infra/avm/` is added, build it the same way: + +```bash +az bicep build --file infra/avm/main.bicep +``` + +## Validation + +```bash +# What-if against an existing resource group +az deployment group what-if \ + --resource-group \ + --template-file infra/main.bicep \ + --parameters @infra/main.parameters.json +``` + +## Notes + +- The compiled `main.json` files are committed because some downstream tooling + (e.g. portal "Deploy to Azure" buttons, Azure DevOps `AzureResourceManagerTemplateDeployment@3` + tasks) prefer ARM JSON. +- Do not hand-edit `main.json` — always edit the Bicep source and + regenerate. diff --git a/infra/scripts/build/.gitkeep b/infra/scripts/build/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/infra/scripts/post-provision/.gitkeep b/infra/scripts/post-provision/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/scripts/checkquota.sh b/infra/scripts/pre-provision/checkquota.sh similarity index 100% rename from scripts/checkquota.sh rename to infra/scripts/pre-provision/checkquota.sh diff --git a/scripts/quota_check_params.sh b/infra/scripts/pre-provision/quota_check_params.sh similarity index 100% rename from scripts/quota_check_params.sh rename to infra/scripts/pre-provision/quota_check_params.sh diff --git a/scripts/validate_bicep_params.py b/infra/scripts/utilities/validate_bicep_params.py similarity index 100% rename from scripts/validate_bicep_params.py rename to infra/scripts/utilities/validate_bicep_params.py