diff --git a/.env.sample b/.env.sample index 004362d45..7c0e45ba0 100644 --- a/.env.sample +++ b/.env.sample @@ -17,7 +17,7 @@ USE_FOUNDRY=false AZURE_AI_PROJECT_ENDPOINT= # Image model deployment name in Foundry (e.g., gpt-image-1-mini) -AZURE_AI_IMAGE_DEPLOYMENT=gpt-image-1-mini +AZURE_AI_IMAGE_MODEL_DEPLOYMENT=gpt-image-1-mini # ============================================================================= # Azure OpenAI Configuration @@ -113,3 +113,13 @@ WORKERS=4 # Feature flags AUTH_ENABLED=false SANITIZE_ANSWER=false + +# ============================================================================= +# Logging Configuration +# ============================================================================= +# Basic logging level (DEBUG, INFO, WARNING, ERROR) +AZURE_BASIC_LOGGING_LEVEL=INFO +# Logging level for Azure SDK and third-party packages (DEBUG, INFO, WARNING, ERROR) +AZURE_PACKAGE_LOGGING_LEVEL=WARNING +# Comma-separated list of Python logger names to apply package logging level to +AZURE_LOGGING_PACKAGES= diff --git a/azure.yaml b/azure.yaml index 1898b4e37..521778a24 100644 --- a/azure.yaml +++ b/azure.yaml @@ -7,7 +7,7 @@ metadata: template: content-generation@1.22 requiredVersions: - azd: '>= 1.18.0' + azd: '>= 1.18.0 != 1.23.9' parameters: solutionPrefix: diff --git a/azure_custom.yaml b/azure_custom.yaml index af8bae654..3b7bb0261 100644 --- a/azure_custom.yaml +++ b/azure_custom.yaml @@ -7,7 +7,7 @@ metadata: template: document-generation@1.0 requiredVersions: - azd: '>= 1.18.0' + azd: '>= 1.18.0 != 1.23.9' parameters: solutionPrefix: diff --git a/docs/AVMPostDeploymentGuide.md b/docs/AVMPostDeploymentGuide.md new file mode 100644 index 000000000..f8b1ef094 --- /dev/null +++ b/docs/AVMPostDeploymentGuide.md @@ -0,0 +1,211 @@ +# AVM Post Deployment Guide + +> **📋 Note**: This guide is specifically for post-deployment steps after using the AVM template. For complete deployment from scratch, see the main [Deployment Guide](./DEPLOYMENT.md). + +--- + +This document provides guidance on post-deployment steps after deploying the Content Generation solution accelerator from the [AVM (Azure Verified Modules) repository](https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/sa/content-generation). + +## Overview + +After successfully deploying the Content Generation Solution Accelerator using the AVM template, you'll need to complete some configuration steps to make the solution fully operational. The AVM deployment provisions all required Azure resources, and the post-deployment process will upload sample data, create search indexes, and verify the application is ready to use. + +--- + +## Prerequisites + +Before starting the post-deployment process, ensure you have the following: + +### 1. Azure Subscription & Permissions + +You need access to an [Azure subscription](https://azure.microsoft.com/free/) with permissions to: +- Create resource groups and resources +- Create app registrations +- Assign roles at the resource group level (Contributor + RBAC) + +📖 Follow the steps in [Azure Account Set Up](./AzureAccountSetUp.md) for detailed instructions. + +### 2. Deployed Infrastructure + +A successful Content Generation solution accelerator deployment from the [AVM repository](https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/sa/content-generation). + +The deployment should have created the following resources: +- Azure App Service (frontend web app) +- Azure Container Instance (backend API) +- Azure AI Foundry (AI orchestration) +- Azure OpenAI Service (GPT and Image models) +- Azure Cosmos DB (product catalog and conversations) +- Azure Blob Storage (product images and generated images) +- Azure AI Search (product search index) +- User Assigned Managed Identity +- App Service Plan + +**Optional resources** (depending on deployment parameters): +- Log Analytics Workspace and Application Insights (if monitoring is enabled) +- Virtual Network, Private DNS Zones, and Private Endpoints (if private networking is enabled) +- Azure Bastion and Jumpbox VM (if enabled for private network administration) + +**Important:** The deployment references an **existing Azure Container Registry** (specified via the `acrName` parameter) that must contain pre-built container images (`content-gen-app` and `content-gen-api`). The ACR is not created by this deployment. + +### 3. Required Tools + +Ensure the following tools are installed on your machine: + +| Tool | Version | Download Link | +|------|---------|---------------| +| PowerShell | v7.0+ | [Install PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.5) | +| Azure CLI | v2.50+ | [Install Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) | +| Python | 3.11+ | [Download Python](https://www.python.org/downloads/) | +| Git | Latest | [Download Git](https://git-scm.com/downloads) | + +#### Important Note for PowerShell Users + +If you encounter issues running PowerShell scripts due to execution policy restrictions, you can temporarily adjust the `ExecutionPolicy` by running the following command in an elevated PowerShell session: + +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass +``` + +This will allow the scripts to run for the current session without permanently changing your system's policy. + +--- + +## Post-Deployment Steps + +### Step 1: Clone the Repository + +Clone this repository to access the post-deployment scripts and sample data: + +```powershell +git clone https://github.com/microsoft/content-generation-solution-accelerator.git +cd content-generation-solution-accelerator +``` + +--- + +### Step 2: Run the Post-Deployment Script + +The AVM deployment provisions the Azure infrastructure but does NOT include automated post-deployment hooks. You need to manually run the post-deployment script to upload sample data and create search indexes. + +> **📝 Note**: Unlike `azd up` deployments which run post-deployment hooks automatically via `azure.yaml`, AVM deployments require manual execution of the post-deployment script. + +#### 2.1 Login to Azure + +```shell +az login +``` + +> 💡 **Tip**: If using VS Code Web or environments without browser access, use device code authentication: +> ```shell +> az login --use-device-code +> ``` + +#### 2.2 Set Up Environment + +Navigate to the repository root directory and create a Python virtual environment: + +**For Windows (PowerShell):** +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +pip install -r ./scripts/requirements-post-deploy.txt +``` + +**For Linux/Mac (bash):** +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r ./scripts/requirements-post-deploy.txt +``` + +#### 2.3 Execute the Post-Deployment Script + +Run the post-deployment script with your resource group name. The script will automatically retrieve resource names from the Azure deployment outputs. + +**For Windows (PowerShell):** +```powershell +python ./scripts/post_deploy.py -g --skip-tests +``` + +**For Linux/Mac (bash):** +```bash +python3 ./scripts/post_deploy.py -g --skip-tests +``` + +**Example:** +```powershell +python ./scripts/post_deploy.py -g rg-contentgen-prod --skip-tests +``` + +> ⚠️ **Important**: The script uses Azure CLI authentication and will automatically discover resource names from deployment outputs. Ensure you're logged in with `az login` before running. + +**How it works:** +- The script queries the deployment outputs using the resource group name +- It automatically retrieves App Service, Storage Account, Cosmos DB, and AI Search names +- No need to manually specify individual resource names + +**Alternative:** If you prefer to specify resources explicitly, you can use environment variables or command-line arguments: +```powershell +# Using environment variables +$env:RESOURCE_GROUP_NAME = "rg-contentgen-prod" +$env:APP_SERVICE_NAME = "app-contentgen-abc123" +$env:AZURE_BLOB_ACCOUNT_NAME = "stcontentgenabc123" +$env:COSMOSDB_ACCOUNT_NAME = "cosmos-contentgen-abc123" +$env:AI_SEARCH_SERVICE_NAME = "search-contentgen-abc123" +python ./scripts/post_deploy.py --skip-tests +``` + +The script will: +- Upload sample product data to Cosmos DB +- Upload sample product images to Blob Storage +- Create and populate the Azure AI Search index +- Verify all connections and configurations + +--- + +### Step 3: Access the Application + +1. Navigate to the [Azure Portal](https://portal.azure.com) +2. Open the **resource group** created during deployment +3. Locate the **App Service** (name typically starts with `app-contentgen-`) +4. Copy the **URL** from the Overview page (format: `https://app-contentgen-.azurewebsites.net`) +5. Open the URL in your browser to access the application + +> 📝 **Note**: It may take a few minutes for the App Service to start up after deployment. + +--- + +### Step 4: Configure Authentication (Optional) + +If you want to enable authentication for your application, follow the [App Authentication Guide](./AppAuthentication.md). + +> **⚠️ Important**: Authentication changes can take up to 10 minutes to propagate. + +--- + +### Step 5: Verify Data Processing + +Confirm your deployment is working correctly: + +| Check | Location | How to Verify | +|-------|----------|---------------| +| ✅ Sample data uploaded | Azure Cosmos DB | Navigate to Cosmos DB → Data Explorer → Check `products` and `conversations` containers | +| ✅ Sample images uploaded | Azure Blob Storage | Navigate to Storage Account → Containers → Check `product-images` container | +| ✅ AI Search index created | Azure AI Search | Navigate to AI Search → Indexes → Verify `products-index` exists and has documents | +| ✅ Application loads | App Service URL | Open the web app URL and verify the welcome screen appears | + +--- + +## Getting Started + +To learn how to use the Content Generation solution and try sample workflows, see the [Sample Workflow](./DEPLOYMENT.md#sample-workflow) section in the main Deployment Guide. + +--- + +## Clean Up Resources + +If you need to delete the resources after testing or a failed deployment: + +Follow the steps in [Delete Resource Group](./DeleteResourceGroup.md) to clean up all deployed resources. + +> ⚠️ **Warning**: Deleting the resource group will permanently delete all resources and data. This action cannot be undone. \ No newline at end of file diff --git a/docs/CustomizingAzdParameters.md b/docs/CustomizingAzdParameters.md index f2adcca3a..e3c6bb117 100644 --- a/docs/CustomizingAzdParameters.md +++ b/docs/CustomizingAzdParameters.md @@ -10,14 +10,14 @@ By default this template will use the environment name as the prefix to prevent | -------------------------------------- | ------- | ---------------------------- | ----------------------------------------------------------------------------- | | `AZURE_LOCATION` | string | `` | Sets the Azure region for resource deployment. Allowed: `australiaeast`, `centralus`, `eastasia`, `eastus`, `eastus2`, `japaneast`, `northeurope`, `southeastasia`, `swedencentral`, `uksouth`, `westus`, `westus3`. | | `AZURE_ENV_NAME` | string | `contentgen` | Sets the environment name prefix for all Azure resources (3-15 characters). | -| `secondaryLocation` | string | `uksouth` | Specifies a secondary Azure region for database creation. | -| `gptModelName` | string | `gpt-5.1` | Specifies the GPT model name to deploy. | -| `gptModelVersion` | string | `2025-11-13` | Sets the GPT model version. | -| `gptModelDeploymentType` | string | `GlobalStandard` | Defines the model deployment type (allowed: `Standard`, `GlobalStandard`). | -| `gptModelCapacity` | integer | `150` | Sets the GPT model token capacity (minimum: `10`). | -| `imageModelChoice` | string | `gpt-image-1-mini` | Image model to deploy (allowed: `gpt-image-1-mini`, `gpt-image-1.5`, `none`). | -| `imageModelCapacity` | integer | `1` | Sets the image model deployment capacity in RPM (minimum: `1`). | -| `azureOpenaiAPIVersion` | string | `2025-01-01-preview` | Specifies the API version for Azure OpenAI service. | +| `SECONDARY_LOCATION` | string | `uksouth` | Specifies a secondary Azure region for database creation. | +| `AZURE_OPENAI_GPT_MODEL` | string | `gpt-5.1` | Specifies the GPT model name to deploy. | +| `GPT_MODEL_VERSION` | string | `2025-11-13` | Sets the GPT model version. | +| `GPT_MODEL_DEPLOYMENT_TYPE` | string | `GlobalStandard` | Defines the model deployment type (allowed: `Standard`, `GlobalStandard`). | +| `GPT_MODEL_CAPACITY` | integer | `150` | Sets the GPT model token capacity (minimum: `10`). | +| `AZURE_OPENAI_IMAGE_MODEL` | string | `gpt-image-1-mini` | Image model to deploy (allowed: `gpt-image-1-mini`, `gpt-image-1.5`, `none`). | +| `IMAGE_MODEL_CAPACITY` | integer | `1` | Sets the image model deployment capacity in RPM (minimum: `1`). | +| `AZURE_OPENAI_API_VERSION` | string | `2025-01-01-preview` | Specifies the API version for Azure OpenAI service. | | `AZURE_ENV_OPENAI_LOCATION` | string | `` | Sets the Azure region for OpenAI resource deployment. | | `AZURE_ENV_LOG_ANALYTICS_WORKSPACE_ID` | string | `""` | Reuses an existing Log Analytics Workspace instead of creating a new one. | | `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID`| string | `""` | Reuses an existing AI Foundry Project instead of creating a new one. | @@ -36,8 +36,8 @@ azd env set ```bash azd env set AZURE_LOCATION westus2 -azd env set gptModelName gpt-5.1 -azd env set gptModelDeploymentType Standard -azd env set imageModelChoice gpt-image-1-mini +azd env set AZURE_OPENAI_GPT_MODEL gpt-5.1 +azd env set GPT_MODEL_DEPLOYMENT_TYPE Standard +azd env set AZURE_OPENAI_IMAGE_MODEL gpt-image-1-mini azd env set ACR_NAME contentgencontainerreg ``` diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 25d0202c5..187640f92 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -137,7 +137,7 @@ When you start the deployment, most parameters will have **default values**, but | **GPT Model Version** | The version of the selected GPT model. | 2025-11-13 | | **OpenAI API Version** | The Azure OpenAI API version to use. | 2025-01-01-preview | | **GPT Model Deployment Capacity** | Configure capacity for **GPT models** (in thousands). | 150k | -| **Image Model** | Choose from **gpt-image-1-mini, gpt-image-1.5** | gpt-image-1-mini | +| **Image Model** | Choose from **gpt-image-1-mini, gpt-image-1.5** | gpt-image-1-mini | | **Image Tag** | Docker image tag to deploy. Common values: `latest`, `dev`, `hotfix`. | latest | | **Existing Log Analytics Workspace** | To reuse an existing Log Analytics Workspace ID. | *(empty)* | | **Existing Azure AI Foundry Project** | To reuse an existing Azure AI Foundry Project ID instead of creating a new one. | *(empty)* | diff --git a/docs/TECHNICAL_GUIDE.md b/docs/TECHNICAL_GUIDE.md index 78ffb4984..ce41f325e 100644 --- a/docs/TECHNICAL_GUIDE.md +++ b/docs/TECHNICAL_GUIDE.md @@ -159,9 +159,9 @@ See `src/backend/settings.py` for all configuration options. Key settings: | `AZURE_OPENAI_GPT_MODEL` | GPT model deployment name | | `AZURE_OPENAI_GPT_IMAGE_ENDPOINT` | Azure OpenAI endpoint for GPT image model (if separate) | | `AZURE_OPENAI_IMAGE_MODEL` | GPT image model deployment name (gpt-image-1-mini) | -| `COSMOS_ENDPOINT` | Azure Cosmos DB endpoint | -| `COSMOS_DATABASE` | Cosmos DB database name | -| `AZURE_STORAGE_ACCOUNT_NAME` | Storage account name | +| `AZURE_COSMOS_ENDPOINT` | Azure Cosmos DB endpoint | +| `AZURE_COSMOS_DATABASE_NAME` | Cosmos DB database name | +| `AZURE_BLOB_ACCOUNT_NAME` | Storage account name | | `BRAND_*` | Brand guideline parameters | ### Brand Guidelines diff --git a/infra/main.bicep b/infra/main.bicep index 6c8de7860..bea0f6861 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -128,13 +128,13 @@ param enableRedundancy bool = false @description('Optional. Enable private networking for applicable resources (WAF-aligned).') param enablePrivateNetworking bool = false -@description('Required. The existing Container Registry name (without .azurecr.io). Must contain pre-built images: content-gen-app and content-gen-api.') +@description('Optional. The existing Container Registry name (without .azurecr.io). Must contain pre-built images: content-gen-app and content-gen-api.') param acrName string = 'contentgencontainerreg' @description('Optional. Image Tag.') param imageTag string = 'latest' -@description('Optional. Enable/Disable usage telemetry.') +@description('Optional. Enable/Disable usage telemetry for module.') param enableTelemetry bool = true @description('Optional. Created by user name.') @@ -261,7 +261,14 @@ var imageModelDeployment = imageModelChoice != 'none' ? [ var aiFoundryAiServicesModelDeployment = concat(baseModelDeployments, imageModelDeployment) var aiFoundryAiProjectDescription = 'Content Generation AI Foundry Project' -var existingTags = resourceGroup().tags ?? {} + +// Reference existing resource group to access current tags +resource existingResourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' existing = { + scope: subscription() + name: resourceGroup().name +} + +var existingTags = existingResourceGroup.tags ?? {} // ============== // // Resources // @@ -269,7 +276,7 @@ var existingTags = resourceGroup().tags ?? {} #disable-next-line no-deployments-resources resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { - name: '46d3xbcp.ptn.sa-contentgen.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, solutionLocation), 0, 4)}' + name: '46d3xbcp.ptn.sa-contentgeneration.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, solutionLocation), 0, 4)}' properties: { mode: 'Incremental' template: { @@ -287,7 +294,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } // ========== Resource Group Tag ========== // -resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { +resource resourceGroupTags 'Microsoft.Resources/tags@2025-04-01' = { name: 'default' properties: { tags: union( @@ -304,7 +311,7 @@ resource resourceGroupTags 'Microsoft.Resources/tags@2021-04-01' = { // ========== Log Analytics Workspace ========== // var logAnalyticsWorkspaceResourceName = 'log-${solutionSuffix}' -module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.14.2' = if (enableMonitoring && !useExistingLogAnalytics) { +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.15.0' = if (enableMonitoring && !useExistingLogAnalytics) { name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) params: { name: logAnalyticsWorkspaceResourceName @@ -315,7 +322,7 @@ module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0 dataRetention: 365 features: { enableLogAccessUsingOnlyResourcePermissions: true } diagnosticSettings: [{ useThisWorkspace: true }] - dailyQuotaGb: enableRedundancy ? 10 : null + dailyQuotaGb: enableRedundancy ? '10' : null replication: enableRedundancy ? { enabled: true @@ -344,13 +351,12 @@ module applicationInsights 'br/public:avm/res/insights/component:0.7.1' = if (en disableIpMasking: false flowType: 'Bluefield' workspaceResourceId: logAnalyticsWorkspaceResourceId - diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] } } // ========== User Assigned Identity ========== // var userAssignedIdentityResourceName = 'id-${solutionSuffix}' -module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.3' = { +module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.5.0' = { name: take('avm.res.managed-identity.user-assigned-identity.${userAssignedIdentityResourceName}', 64) params: { name: userAssignedIdentityResourceName @@ -373,7 +379,7 @@ module virtualNetwork 'modules/virtualNetwork.bicep' = if (enablePrivateNetworki resourceSuffix: solutionSuffix deployBastionAndJumpbox: deployBastionAndJumpbox } - dependsOn: enableMonitoring ? [logAnalyticsWorkspace] : [] + dependsOn: (enableMonitoring && !useExistingLogAnalytics) ? [logAnalyticsWorkspace] : [] } // ========== Private DNS Zones ========== // @@ -415,12 +421,13 @@ module avmPrivateDnsZones 'br/public:avm/res/network/private-dns-zone:0.8.0' = [ ] // ========== AI Foundry: AI Services ========== // -module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.14.0' = if (!useExistingAiFoundryAiProject) { +module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.14.1' = if (!useExistingAiFoundryAiProject) { name: take('avm.res.cognitive-services.account.${aiFoundryAiServicesResourceName}', 64) params: { name: aiFoundryAiServicesResourceName location: azureAiServiceLocation tags: tags + enableTelemetry: enableTelemetry sku: 'S0' kind: 'AIServices' disableLocalAuth: true @@ -447,8 +454,8 @@ module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.14.0' virtualNetworkRules: [] ipRules: [] } - managedIdentities: { - userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] + managedIdentities: { + userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] } roleAssignments: [ { @@ -478,12 +485,13 @@ module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.14.0' } // Create private endpoint for AI Services AFTER the account is fully provisioned -module aiServicesPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.0' = if (!useExistingAiFoundryAiProject && enablePrivateNetworking) { +module aiServicesPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.11.1' = if (!useExistingAiFoundryAiProject && enablePrivateNetworking) { name: take('pep-ai-services-${aiFoundryAiServicesResourceName}', 64) params: { name: 'pep-${aiFoundryAiServicesResourceName}' location: solutionLocation tags: tags + enableTelemetry: enableTelemetry subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId privateLinkServiceConnections: [ { @@ -496,13 +504,13 @@ module aiServicesPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.1 ] privateDnsZoneGroup: { privateDnsZoneGroupConfigs: [ - { + { name: 'cognitiveservices' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId } - { + { name: 'openai' - privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.openAI]!.outputs.resourceId + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.openAI]!.outputs.resourceId } ] } @@ -539,8 +547,33 @@ module existingAiServicesRoleAssignments 'modules/deploy_foundry_role_assignment } } +// ========== Model Deployments for Existing AI Services ========== // +module existingAiServicesModelDeployments 'modules/deploy_ai_model.bicep' = if (useExistingAiFoundryAiProject) { + name: take('module.model-deployments-existing.${aiFoundryAiServicesResourceName}', 64) + scope: resourceGroup(aiFoundryAiServicesSubscriptionId, aiFoundryAiServicesResourceGroupName) + params: { + aiServicesName: aiFoundryAiServicesResourceName + deployments: [ + for deployment in aiFoundryAiServicesModelDeployment: { + name: deployment.name + format: deployment.format + model: deployment.model + sku: { + name: deployment.sku.name + capacity: deployment.sku.capacity + } + version: deployment.version + raiPolicyName: deployment.raiPolicyName + } + ] + } + dependsOn: [ + existingAiServicesRoleAssignments + ] +} + // ========== AI Search ========== // -module aiSearch 'br/public:avm/res/search/search-service:0.11.1' = { +module aiSearch 'br/public:avm/res/search/search-service:0.12.0' = { name: take('avm.res.search.search-service.${aiSearchName}', 64) params: { name: aiSearchName @@ -548,9 +581,9 @@ module aiSearch 'br/public:avm/res/search/search-service:0.11.1' = { tags: tags enableTelemetry: enableTelemetry sku: enableScalability ? 'standard' : 'basic' - replicaCount: enableRedundancy ? 2 : 1 + replicaCount: enableRedundancy ? 3 : 1 partitionCount: 1 - hostingMode: 'default' + hostingMode: 'Default' semanticSearch: 'free' authOptions: { aadOrApiKey: { @@ -577,7 +610,7 @@ module aiSearch 'br/public:avm/res/search/search-service:0.11.1' = { } // ========== AI Search Connection to AI Services ========== // -resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (!useExistingAiFoundryAiProject) { +resource aiSearchFoundryConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-09-01' = if (!useExistingAiFoundryAiProject) { name: '${aiFoundryAiServicesResourceName}/${aiFoundryAiProjectResourceName}/${aiSearchConnectionName}' properties: { category: 'CognitiveSearch' @@ -598,7 +631,7 @@ var productImagesContainer = 'product-images' var generatedImagesContainer = 'generated-images' var dataContainer = 'data' -module storageAccount 'br/public:avm/res/storage/storage-account:0.30.0' = { +module storageAccount 'br/public:avm/res/storage/storage-account:0.31.1' = { name: take('avm.res.storage.storage-account.${storageAccountName}', 64) params: { name: storageAccountName @@ -643,17 +676,19 @@ module storageAccount 'br/public:avm/res/storage/storage-account:0.30.0' = { } allowBlobPublicAccess: false publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' - privateEndpoints: enablePrivateNetworking ? [ - { - service: 'blob' - subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId } - ] - } - } - ] : null + privateEndpoints: enablePrivateNetworking + ? [ + { + service: 'blob' + subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.storageBlob]!.outputs.resourceId } + ] + } + } + ] + : null diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null } } @@ -738,23 +773,25 @@ module cosmosDB 'br/public:avm/res/document-db/database-account:0.18.0' = { isZoneRedundant: false } ] - privateEndpoints: enablePrivateNetworking ? [ - { - service: 'Sql' - subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId - privateDnsZoneGroup: { - privateDnsZoneGroupConfigs: [ - { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cosmosDB]!.outputs.resourceId } - ] - } - } - ] : null + privateEndpoints: enablePrivateNetworking + ? [ + { + service: 'Sql' + subnetResourceId: virtualNetwork!.outputs.pepsSubnetResourceId + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cosmosDB]!.outputs.resourceId } + ] + } + } + ] + : null } } // ========== App Service Plan ========== // var webServerFarmResourceName = 'asp-${solutionSuffix}' -module webServerFarm 'br/public:avm/res/web/serverfarm:0.5.0' = { +module webServerFarm 'br/public:avm/res/web/serverfarm:0.7.0' = { name: take('avm.res.web.serverfarm.${webServerFarmResourceName}', 64) params: { name: webServerFarmResourceName @@ -765,7 +802,7 @@ module webServerFarm 'br/public:avm/res/web/serverfarm:0.5.0' = { kind: 'linux' diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null skuName: enableScalability || enableRedundancy ? 'P1v3' : 'B1' - skuCapacity: 1 + skuCapacity: enableRedundancy ? 2 : 1 zoneRedundant: enableRedundancy ? true : false } scope: resourceGroup(resourceGroup().name) @@ -777,7 +814,7 @@ var webSiteResourceName = 'app-${solutionSuffix}' var aciPrivateIpFallback = '10.0.4.4' var aciPublicFqdnFallback = '${containerInstanceName}.${solutionLocation}.azurecontainer.io' // For private networking use IP, for public use FQDN -var aciBackendUrl = enablePrivateNetworking +var aciBackendUrl = enablePrivateNetworking ? 'http://${aciPrivateIpFallback}:8000' : 'http://${aciPublicFqdnFallback}:8000' module webSite 'modules/web-sites.bicep' = { @@ -797,24 +834,30 @@ module webSite 'modules/web-sites.bicep' = { ftpsState: 'FtpsOnly' } virtualNetworkSubnetId: enablePrivateNetworking ? virtualNetwork!.outputs.webSubnetResourceId : null - configs: concat([ - { - // Frontend container proxies to ACI backend (both modes) - name: 'appsettings' - properties: { - DOCKER_REGISTRY_SERVER_URL: 'https://${acrResourceName}.azurecr.io' - BACKEND_URL: aciBackendUrl - AZURE_CLIENT_ID: userAssignedIdentity.outputs.clientId + configs: concat( + [ + { + // Frontend container proxies to ACI backend (both modes) + name: 'appsettings' + properties: { + DOCKER_REGISTRY_SERVER_URL: 'https://${acrResourceName}.azurecr.io' + BACKEND_URL: aciBackendUrl + AZURE_CLIENT_ID: userAssignedIdentity.outputs.clientId + } + applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null } - applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null - } - ], enableMonitoring ? [ - { - name: 'logs' - properties: {} - } - ] : []) + ], + enableMonitoring + ? [ + { + name: 'logs' + properties: {} + } + ] + : [] + ) enableMonitoring: enableMonitoring + enableTelemetry: enableTelemetry diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null vnetRouteAllEnabled: enablePrivateNetworking vnetImagePullEnabled: enablePrivateNetworking @@ -836,7 +879,6 @@ module containerInstance 'modules/container-instance.bicep' = { port: 8000 // Only pass subnetResourceId when private networking is enabled subnetResourceId: enablePrivateNetworking ? virtualNetwork!.outputs.aciSubnetResourceId : '' - registryServer: '${acrResourceName}.azurecr.io' userAssignedIdentityResourceId: userAssignedIdentity.outputs.resourceId enableTelemetry: enableTelemetry environmentVariables: [ @@ -869,6 +911,12 @@ module containerInstance 'modules/container-instance.bicep' = { { name: 'AZURE_AI_PROJECT_ENDPOINT', value: aiFoundryAiProjectEndpoint } { name: 'AZURE_AI_MODEL_DEPLOYMENT_NAME', value: gptModelName } { name: 'AZURE_AI_IMAGE_MODEL_DEPLOYMENT', value: imageModelConfig[imageModelChoice].name } + // Logging Settings + { name: 'AZURE_BASIC_LOGGING_LEVEL', value: 'INFO' } + { name: 'AZURE_PACKAGE_LOGGING_LEVEL', value: 'WARNING' } + { name: 'AZURE_LOGGING_PACKAGES', value: '' } + // Application Insights + { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: enableMonitoring ? applicationInsights!.outputs.connectionString : '' } ] } } diff --git a/infra/main.json b/infra/main.json index 1132011bf..84293fdce 100644 --- a/infra/main.json +++ b/infra/main.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "15058520269193157842" + "templateHash": "15389484880957971429" }, "name": "Intelligent Content Generation Accelerator", "description": "Solution Accelerator for multimodal marketing content generation using Microsoft Agent Framework.\n" @@ -222,7 +222,7 @@ "type": "string", "defaultValue": "contentgencontainerreg", "metadata": { - "description": "Required. The existing Container Registry name (without .azurecr.io). Must contain pre-built images: content-gen-app and content-gen-api." + "description": "Optional. The existing Container Registry name (without .azurecr.io). Must contain pre-built images: content-gen-app and content-gen-api." } }, "imageTag": { @@ -236,7 +236,7 @@ "type": "bool", "defaultValue": true, "metadata": { - "description": "Optional. Enable/Disable usage telemetry." + "description": "Optional. Enable/Disable usage telemetry for module." } }, "createdBy": { @@ -321,7 +321,6 @@ "imageModelDeployment": "[if(not(equals(parameters('imageModelChoice'), 'none')), createArray(createObject('format', 'OpenAI', 'name', variables('imageModelConfig')[parameters('imageModelChoice')].name, 'model', variables('imageModelConfig')[parameters('imageModelChoice')].name, 'sku', createObject('name', variables('imageModelConfig')[parameters('imageModelChoice')].sku, 'capacity', parameters('imageModelCapacity')), 'version', variables('imageModelConfig')[parameters('imageModelChoice')].version, 'raiPolicyName', 'Microsoft.Default')), createArray())]", "aiFoundryAiServicesModelDeployment": "[concat(variables('baseModelDeployments'), variables('imageModelDeployment'))]", "aiFoundryAiProjectDescription": "Content Generation AI Foundry Project", - "existingTags": "[coalesce(resourceGroup().tags, createObject())]", "logAnalyticsWorkspaceResourceName": "[format('log-{0}', variables('solutionSuffix'))]", "applicationInsightsResourceName": "[format('appi-{0}', variables('solutionSuffix'))]", "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]", @@ -353,11 +352,18 @@ "containerInstanceName": "[format('aci-{0}', variables('solutionSuffix'))]" }, "resources": { + "existingResourceGroup": { + "existing": true, + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2024-03-01", + "subscriptionId": "[subscription().subscriptionId]", + "name": "[resourceGroup().name]" + }, "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.ptn.sa-contentgen.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, variables('solutionLocation')), 0, 4))]", + "name": "[format('46d3xbcp.ptn.sa-contentgeneration.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, variables('solutionLocation')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -375,16 +381,19 @@ }, "resourceGroupTags": { "type": "Microsoft.Resources/tags", - "apiVersion": "2021-04-01", + "apiVersion": "2025-04-01", "name": "default", "properties": { - "tags": "[union(variables('existingTags'), parameters('tags'), createObject('TemplateName', 'ContentGen', 'Type', if(parameters('enablePrivateNetworking'), 'WAF', 'Non-WAF'), 'CreatedBy', parameters('createdBy')))]" - } + "tags": "[union(coalesce(reference('existingResourceGroup', '2024-03-01', 'full').tags, createObject()), parameters('tags'), createObject('TemplateName', 'ContentGen', 'Type', if(parameters('enablePrivateNetworking'), 'WAF', 'Non-WAF'), 'CreatedBy', parameters('createdBy')))]" + }, + "dependsOn": [ + "existingResourceGroup" + ] }, "aiSearchFoundryConnection": { "condition": "[not(variables('useExistingAiFoundryAiProject'))]", "type": "Microsoft.CognitiveServices/accounts/projects/connections", - "apiVersion": "2025-04-01-preview", + "apiVersion": "2025-09-01", "name": "[format('{0}/{1}/{2}', variables('aiFoundryAiServicesResourceName'), variables('aiFoundryAiProjectResourceName'), variables('aiSearchConnectionName'))]", "properties": { "category": "CognitiveSearch", @@ -442,7 +451,7 @@ } ] }, - "dailyQuotaGb": "[if(parameters('enableRedundancy'), createObject('value', 10), createObject('value', null()))]", + "dailyQuotaGb": "[if(parameters('enableRedundancy'), createObject('value', '10'), createObject('value', null()))]", "replication": "[if(parameters('enableRedundancy'), createObject('value', createObject('enabled', true(), 'location', variables('replicaLocation'))), createObject('value', null()))]", "publicNetworkAccessForIngestion": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]", "publicNetworkAccessForQuery": "[if(parameters('enablePrivateNetworking'), createObject('value', 'Disabled'), createObject('value', 'Enabled'))]" @@ -455,7 +464,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "3322296220118676013" + "templateHash": "14099489006827800075" }, "name": "Log Analytics Workspaces", "description": "This module deploys a Log Analytics Workspace." @@ -896,7 +905,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-07-01#properties/tags" }, "description": "Optional. Tags to configure in the resource." }, @@ -1557,11 +1566,10 @@ } }, "dailyQuotaGb": { - "type": "int", - "defaultValue": -1, - "minValue": -1, + "type": "string", + "defaultValue": "-1", "metadata": { - "description": "Optional. The workspace daily quota for ingestion." + "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": { @@ -1654,7 +1662,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces@2025-02-01#properties/tags" + "source": "Microsoft.OperationalInsights/workspaces@2025-07-01#properties/tags" }, "description": "Optional. Tags of the resource." }, @@ -1698,7 +1706,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.14.2', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.operationalinsights-workspace.{0}.{1}', replace('0.15.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -1734,7 +1742,7 @@ }, "retentionInDays": "[parameters('dataRetention')]", "workspaceCapping": { - "dailyQuotaGb": "[parameters('dailyQuotaGb')]" + "dailyQuotaGb": "[json(parameters('dailyQuotaGb'))]" }, "publicNetworkAccessForIngestion": "[parameters('publicNetworkAccessForIngestion')]", "publicNetworkAccessForQuery": "[parameters('publicNetworkAccessForQuery')]", @@ -1867,7 +1875,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "15555486835943827858" + "templateHash": "140290971998938797" }, "name": "Log Analytics Workspace Storage Insight Configs", "description": "This module deploys a Log Analytics Workspace Storage Insight Config." @@ -1916,7 +1924,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-02-01#properties/tags" + "source": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs@2025-07-01#properties/tags" }, "description": "Optional. Tags to configure in the resource." }, @@ -1927,18 +1935,18 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" }, "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('logAnalyticsWorkspaceName')]" }, "storageinsightconfig": { "type": "Microsoft.OperationalInsights/workspaces/storageInsightConfigs", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", "tags": "[parameters('tags')]", "properties": { @@ -1946,7 +1954,7 @@ "tables": "[parameters('tables')]", "storageAccount": { "id": "[parameters('storageAccountResourceId')]", - "key": "[listKeys('storageAccount', '2024-01-01').keys[0].value]" + "key": "[listKeys('storageAccount', '2025-06-01').keys[0].value]" } } } @@ -2015,7 +2023,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "8517561285031465616" + "templateHash": "14482465616812596213" }, "name": "Log Analytics Workspace Linked Services", "description": "This module deploys a Log Analytics Workspace Linked Service." @@ -2051,7 +2059,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-02-01#properties/tags" + "source": "Microsoft.OperationalInsights/workspaces/linkedServices@2025-07-01#properties/tags" }, "description": "Optional. Tags to configure in the resource." }, @@ -2062,12 +2070,12 @@ "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('logAnalyticsWorkspaceName')]" }, "linkedService": { "type": "Microsoft.OperationalInsights/workspaces/linkedServices", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", "tags": "[parameters('tags')]", "properties": { @@ -2137,7 +2145,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "3889914477453955601" + "templateHash": "14864721709229272590" }, "name": "Log Analytics Workspace Linked Storage Accounts", "description": "This module deploys a Log Analytics Workspace Linked Storage Account." @@ -2176,12 +2184,12 @@ "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('logAnalyticsWorkspaceName')]" }, "linkedStorageAccount": { "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", "properties": { "storageAccountIds": "[parameters('storageAccountIds')]" @@ -2270,7 +2278,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "3727577253995784529" + "templateHash": "17904092372918022238" }, "name": "Log Analytics Workspace Saved Searches", "description": "This module deploys a Log Analytics Workspace Saved Search." @@ -2310,7 +2318,7 @@ "type": "array", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-02-01#properties/properties/properties/tags" + "source": "Microsoft.OperationalInsights/workspaces/savedSearches@2025-07-01#properties/properties/properties/tags" }, "description": "Optional. Tags to configure in the resource." }, @@ -2349,12 +2357,12 @@ "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('logAnalyticsWorkspaceName')]" }, "savedSearch": { "type": "Microsoft.OperationalInsights/workspaces/savedSearches", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", "properties": { "etag": "[parameters('etag')]", @@ -2436,7 +2444,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "6434695407713682713" + "templateHash": "17943947755417749524" }, "name": "Log Analytics Workspace Data Exports", "description": "This module deploys a Log Analytics Workspace Data Export." @@ -2518,12 +2526,12 @@ "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('workspaceName')]" }, "dataExport": { "type": "Microsoft.OperationalInsights/workspaces/dataExports", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", "properties": { "destination": "[parameters('destination')]", @@ -2629,7 +2637,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "8636447638029661740" + "templateHash": "15360290236166491819" }, "name": "Log Analytics Workspace Datasources", "description": "This module deploys a Log Analytics Workspace Data Source." @@ -2668,7 +2676,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-02-01#properties/tags" + "source": "Microsoft.OperationalInsights/workspaces/dataSources@2025-07-01#properties/tags" }, "description": "Optional. Tags to configure in the resource." }, @@ -2756,12 +2764,12 @@ "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('logAnalyticsWorkspaceName')]" }, "dataSource": { "type": "Microsoft.OperationalInsights/workspaces/dataSources", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('logAnalyticsWorkspaceName'), parameters('name'))]", "kind": "[parameters('kind')]", "tags": "[parameters('tags')]", @@ -2859,7 +2867,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "13928174215528939368" + "templateHash": "18383178824663161801" }, "name": "Log Analytics Workspace Tables", "description": "This module deploys a Log Analytics Workspace Table." @@ -3208,12 +3216,12 @@ "workspace": { "existing": true, "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[parameters('workspaceName')]" }, "table": { "type": "Microsoft.OperationalInsights/workspaces/tables", - "apiVersion": "2025-02-01", + "apiVersion": "2025-07-01", "name": "[format('{0}/{1}', parameters('workspaceName'), parameters('name'))]", "properties": { "plan": "[parameters('plan')]", @@ -3562,14 +3570,7 @@ "flowType": { "value": "Bluefield" }, - "workspaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), if(parameters('enableMonitoring'), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value), createObject('value', '')))]", - "diagnosticSettings": { - "value": [ - { - "workspaceResourceId": "[if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))]" - } - ] - } + "workspaceResourceId": "[if(variables('useExistingLogAnalytics'), createObject('value', parameters('existingLogAnalyticsWorkspaceId')), if(parameters('enableMonitoring'), createObject('value', reference('logAnalyticsWorkspace').outputs.resourceId.value), createObject('value', '')))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -4326,7 +4327,7 @@ "_generator": { "name": "bicep", "version": "0.39.26.7824", - "templateHash": "1856697743030659118" + "templateHash": "7591858083424858339" }, "name": "User Assigned Identities", "description": "This module deploys a User Assigned Identity." @@ -4538,6 +4539,17 @@ "metadata": { "description": "Optional. Enable/Disable usage telemetry for module." } + }, + "isolationScope": { + "type": "string", + "nullable": true, + "allowedValues": [ + "None", + "Regional" + ], + "metadata": { + "description": "Optional. Enum to configure regional restrictions on identity assignment, as necessary. Allowed values: \"None\", \"Regional\"." + } } }, "variables": { @@ -4563,7 +4575,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.managedidentity-userassignedidentity.{0}.{1}', replace('0.4.3', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.managedidentity-userassignedidentity.{0}.{1}', replace('0.5.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -4584,7 +4596,8 @@ "apiVersion": "2024-11-30", "name": "[parameters('name')]", "location": "[parameters('location')]", - "tags": "[parameters('tags')]" + "tags": "[parameters('tags')]", + "properties": "[if(not(equals(parameters('isolationScope'), null())), createObject('isolationScope', parameters('isolationScope')), createObject())]" }, "userAssignedIdentity_lock": { "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", @@ -10775,6 +10788,9 @@ "tags": { "value": "[parameters('tags')]" }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, "sku": { "value": "S0" }, @@ -10849,8 +10865,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.38.33.27573", - "templateHash": "7191594406492701501" + "version": "0.39.26.7824", + "templateHash": "6544538318162038728" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service." @@ -12212,7 +12228,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.cognitiveservices-account.{0}.{1}', replace('0.14.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.cognitiveservices-account.{0}.{1}', replace('0.14.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -13076,8 +13092,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.38.33.27573", - "templateHash": "1394089926798493893" + "version": "0.39.26.7824", + "templateHash": "356315690886888607" } }, "definitions": { @@ -13273,6 +13289,22 @@ "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())]" } } } @@ -13302,6 +13334,9 @@ "tags": { "value": "[parameters('tags')]" }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, "subnetResourceId": { "value": "[reference('virtualNetwork').outputs.pepsSubnetResourceId.value]" }, @@ -13340,8 +13375,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "12389807800450456797" + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" }, "name": "Private Endpoints", "description": "This module deploys a Private Endpoint." @@ -13368,115 +13403,8 @@ } }, "metadata": { - "__bicep_export!": true - } - }, - "ipConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the resource that is unique within a resource group." - } - }, - "properties": { - "type": "object", - "properties": { - "groupId": { - "type": "string", - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "memberName": { - "type": "string", - "metadata": { - "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "privateIPAddress": { - "type": "string", - "metadata": { - "description": "Required. A private IP address obtained from the private endpoint's subnet." - } - } - }, - "metadata": { - "description": "Required. Properties of private endpoint IP configurations." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "privateLinkServiceConnectionType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the private link service connection." - } - }, - "properties": { - "type": "object", - "properties": { - "groupIds": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." - } - }, - "privateLinkServiceId": { - "type": "string", - "metadata": { - "description": "Required. The resource id of private link service." - } - }, - "requestMessage": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." - } - } - }, - "metadata": { - "description": "Required. Properties of private link service connection." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "customDnsConfigType": { - "type": "object", - "properties": { - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN that resolves to private endpoint IP address." - } - }, - "ipAddresses": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. A list of private IP addresses of the private endpoint." - } - } - }, - "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private dns zone group." } }, "lockType": { @@ -13500,12 +13428,19 @@ "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.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -13527,6 +13462,7 @@ } }, "metadata": { + "description": "The type of a private DNS zone group configuration.", "__bicep_imported_from!": { "sourceTemplate": "private-dns-zone-group/main.bicep" } @@ -13603,7 +13539,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } } @@ -13640,13 +13576,13 @@ }, "ipConfigurations": { "type": "array", - "items": { - "$ref": "#/definitions/ipConfigurationType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." - } + }, + "nullable": true }, "privateDnsZoneGroup": { "$ref": "#/definitions/privateDnsZoneGroupType", @@ -13681,40 +13617,43 @@ }, "tags": { "type": "object", - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." - } + }, + "nullable": true }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, "description": "Optional. Custom DNS configurations." - } + }, + "nullable": true }, "manualPrivateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "privateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "enableTelemetry": { "type": "bool", @@ -13749,8 +13688,8 @@ "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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": { @@ -13768,7 +13707,7 @@ }, "privateEndpoint": { "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -13800,7 +13739,7 @@ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", "properties": { "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", - "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + "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" @@ -13831,7 +13770,7 @@ "privateEndpoint_privateDnsZoneGroup": { "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", "properties": { "expressionEvaluationOptions": { @@ -13856,8 +13795,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "13997305779829540948" + "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." @@ -13881,7 +13820,8 @@ } }, "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." } } }, @@ -13911,33 +13851,30 @@ } } }, - "variables": { - "copy": [ - { - "name": "privateDnsZoneConfigsVar", - "count": "[length(parameters('privateDnsZoneConfigs'))]", - "input": { - "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", - "properties": { - "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" - } - } - } - ] - }, "resources": { "privateEndpoint": { "existing": true, "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('privateEndpointName')]" }, "privateDnsZoneGroup": { "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", "properties": { - "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + "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]" + } + } + } + ] } } }, @@ -13998,14 +13935,15 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]" + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, "description": "The custom DNS configurations of the private endpoint." }, "value": "[reference('privateEndpoint').customDnsConfigs]" @@ -14075,7 +14013,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "14729448306021818204" + "templateHash": "5579055444657114163" } }, "parameters": { @@ -14127,7 +14065,7 @@ "resources": [ { "type": "Microsoft.CognitiveServices/accounts/projects", - "apiVersion": "2025-06-01", + "apiVersion": "2025-10-01-preview", "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('name'))]", "tags": "[parameters('tags')]", "location": "[parameters('location')]", @@ -14160,21 +14098,21 @@ "metadata": { "description": "Required. API endpoint for the AI project." }, - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), '2025-06-01').endpoints['AI Foundry API']]" + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), '2025-10-01-preview').endpoints['AI Foundry API']]" }, "aoaiEndpoint": { "type": "string", "metadata": { "description": "Contains AI Endpoint." }, - "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2025-06-01').endpoints['OpenAI Language Model Instance API'])]" + "value": "[if(not(empty(variables('existingOpenAIEndpoint'))), variables('existingOpenAIEndpoint'), reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2025-10-01-preview').endpoints['OpenAI Language Model Instance API'])]" }, "systemAssignedMIPrincipalId": { "type": "string", "metadata": { "description": "Required. Principal ID of the AI project system-assigned managed identity." }, - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), '2025-06-01', 'full').identity.principalId]" + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('name')), '2025-10-01-preview', 'full').identity.principalId]" } } } @@ -14213,7 +14151,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "9237732984597233866" + "templateHash": "4896504561894393634" } }, "parameters": { @@ -14288,14 +14226,14 @@ "metadata": { "description": "The endpoint of the existing AI Services account." }, - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2025-04-01-preview').endpoint]" + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('aiServicesName')), '2025-10-01-preview').endpoint]" }, "aiProjectPrincipalId": { "type": "string", "metadata": { "description": "The principal ID of the existing AI Project (if provided)." }, - "value": "[if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('aiProjectName')), '2025-04-01-preview', 'full').identity.principalId, '')]" + "value": "[if(not(empty(parameters('aiProjectName'))), coalesce(tryGet(tryGet(if(not(empty(parameters('aiProjectName'))), reference(resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiServicesName'), parameters('aiProjectName')), '2025-10-01-preview', 'full'), null()), 'identity'), 'principalId'), ''), '')]" } } } @@ -14304,6 +14242,100 @@ "userAssignedIdentity" ] }, + "existingAiServicesModelDeployments": { + "condition": "[variables('useExistingAiFoundryAiProject')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[take(format('module.model-deployments-existing.{0}', variables('aiFoundryAiServicesResourceName')), 64)]", + "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", + "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiServicesName": { + "value": "[variables('aiFoundryAiServicesResourceName')]" + }, + "deployments": { + "copy": [ + { + "name": "value", + "count": "[length(variables('aiFoundryAiServicesModelDeployment'))]", + "input": "[createObject('name', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].name, 'format', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].format, 'model', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].model, 'sku', createObject('name', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].sku.name, 'capacity', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].sku.capacity), 'version', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].version, 'raiPolicyName', variables('aiFoundryAiServicesModelDeployment')[copyIndex('value')].raiPolicyName)]" + } + ] + } + }, + "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": "12449348145632794739" + } + }, + "parameters": { + "aiServicesName": { + "type": "string", + "metadata": { + "description": "Required. Name of the AI Services account." + } + }, + "deployments": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Required. Array of model deployments to create." + } + } + }, + "resources": [ + { + "copy": { + "name": "modelDeployments", + "count": "[length(parameters('deployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2024-10-01", + "name": "[format('{0}/{1}', parameters('aiServicesName'), parameters('deployments')[copyIndex()].name)]", + "properties": { + "model": { + "format": "[parameters('deployments')[copyIndex()].format]", + "name": "[parameters('deployments')[copyIndex()].model]", + "version": "[parameters('deployments')[copyIndex()].version]" + }, + "raiPolicyName": "[parameters('deployments')[copyIndex()].raiPolicyName]" + }, + "sku": { + "name": "[parameters('deployments')[copyIndex()].sku.name]", + "capacity": "[parameters('deployments')[copyIndex()].sku.capacity]" + } + } + ], + "outputs": { + "deployedModelNames": { + "type": "array", + "metadata": { + "description": "The names of the deployed models." + }, + "copy": { + "count": "[length(parameters('deployments'))]", + "input": "[parameters('deployments')[copyIndex()].name]" + } + } + } + } + }, + "dependsOn": [ + "existingAiServicesRoleAssignments" + ] + }, "aiSearch": { "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", @@ -14327,12 +14359,12 @@ "value": "[parameters('enableTelemetry')]" }, "sku": "[if(parameters('enableScalability'), createObject('value', 'standard'), createObject('value', 'basic'))]", - "replicaCount": "[if(parameters('enableRedundancy'), createObject('value', 2), createObject('value', 1))]", + "replicaCount": "[if(parameters('enableRedundancy'), createObject('value', 3), createObject('value', 1))]", "partitionCount": { "value": 1 }, "hostingMode": { - "value": "default" + "value": "Default" }, "semanticSearch": { "value": "free" @@ -14373,8 +14405,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "10902281417196168235" + "version": "0.39.26.7824", + "templateHash": "6207719545398489494" }, "name": "Search Services", "description": "This module deploys a Search Service." @@ -14478,122 +14510,6 @@ } } }, - "authOptionsType": { - "type": "object", - "properties": { - "aadOrApiKey": { - "type": "object", - "properties": { - "aadAuthFailureMode": { - "type": "string", - "allowedValues": [ - "http401WithBearerChallenge", - "http403" - ], - "nullable": true, - "metadata": { - "description": "Optional. Describes what response the data plane API of a search service would send for requests that failed authentication." - } - } - }, - "nullable": true, - "metadata": { - "description": "Optional. Indicates that either the API key or an access token from a Microsoft Entra ID tenant can be used for authentication." - } - }, - "apiKeyOnly": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Indicates that only the API key can be used for authentication." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "networkRuleSetType": { - "type": "object", - "properties": { - "bypass": { - "type": "string", - "allowedValues": [ - "AzurePortal", - "AzureServices", - "None" - ], - "nullable": true, - "metadata": { - "description": "Optional. Network specific rules that determine how the Azure AI Search service may be reached." - } - }, - "ipRules": { - "type": "array", - "items": { - "$ref": "#/definitions/ipRuleType" - }, - "nullable": true, - "metadata": { - "description": "Optional. A list of IP restriction rules that defines the inbound network(s) with allowing access to the search service endpoint. At the meantime, all other public IP networks are blocked by the firewall. These restriction rules are applied only when the 'publicNetworkAccess' of the search service is 'enabled'; otherwise, traffic over public interface is not allowed even with any public IP rules, and private endpoint connections would be the exclusive access method." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "ipRuleType": { - "type": "object", - "properties": { - "value": { - "type": "string", - "metadata": { - "description": "Required. Value corresponding to a single IPv4 address (eg., 123.1.2.3) or an IP range in CIDR format (eg., 123.1.2.3/24) to be allowed." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "_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" - } - } - }, "_1.privateEndpointCustomDnsConfigType": { "type": "object", "properties": { @@ -14703,81 +14619,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" - } - } - }, "diagnosticSettingFullType": { "type": "object", "properties": { @@ -14896,7 +14737,7 @@ "metadata": { "description": "An AVM-aligned type for a diagnostic setting. To be used if both logs & metrics are supported by the resource provider.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -14933,7 +14774,7 @@ "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" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -14961,7 +14802,7 @@ "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" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -15069,7 +14910,7 @@ } }, "lock": { - "$ref": "#/definitions/_1.lockType", + "$ref": "#/definitions/lockType", "nullable": true, "metadata": { "description": "Optional. Specify the type of lock." @@ -15078,7 +14919,7 @@ "roleAssignments": { "type": "array", "items": { - "$ref": "#/definitions/_1.roleAssignmentType" + "$ref": "#/definitions/roleAssignmentType" }, "nullable": true, "metadata": { @@ -15181,7 +15022,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -15216,11 +15057,14 @@ } }, "authOptions": { - "$ref": "#/definitions/authOptionsType", - "nullable": true, + "type": "object", "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Search/searchServices@2025-05-01#properties/properties/properties/authOptions" + }, "description": "Optional. Defines the options for how the data plane API of a Search service authenticates requests. Must remain an empty object {} if 'disableLocalAuth' is set to true." - } + }, + "nullable": true }, "disableLocalAuth": { "type": "bool", @@ -15236,6 +15080,17 @@ "description": "Optional. Enable/Disable usage telemetry for module." } }, + "computeType": { + "type": "string", + "defaultValue": "Default", + "allowedValues": [ + "Confidential", + "Default" + ], + "metadata": { + "description": "Optional. The compute type of the search service." + } + }, "cmkEnforcement": { "type": "string", "defaultValue": "Unspecified", @@ -15248,12 +15103,25 @@ "description": "Optional. Describes a policy that determines how resources within the search service are to be encrypted with Customer Managed Keys." } }, + "dataExfiltrationProtections": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "allowedValues": [ + "All" + ], + "metadata": { + "description": "Optional. A list of data exfiltration scenarios that are explicitly disallowed for the search service. Currently, the only supported value is 'All' to disable all possible data export scenarios with more fine grained controls planned for the future." + } + }, "hostingMode": { "type": "string", - "defaultValue": "default", + "defaultValue": "Default", "allowedValues": [ - "default", - "highDensity" + "Default", + "HighDensity" ], "metadata": { "description": "Optional. Applicable only for the standard3 SKU. You can set this property to enable up to 3 high density partitions that allow up to 1000 indexes, which is much higher than the maximum indexes allowed for any other SKU. For the standard3 SKU, the value is either 'default' or 'highDensity'. For all other SKUs, this value must be 'default'." @@ -15274,11 +15142,14 @@ } }, "networkRuleSet": { - "$ref": "#/definitions/networkRuleSetType", - "nullable": true, + "type": "object", "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Search/searchServices@2025-05-01#properties/properties/properties/networkRuleSet" + }, "description": "Optional. Network specific rules that determine how the Azure Cognitive Search service may be reached." - } + }, + "nullable": true }, "partitionCount": { "type": "int", @@ -15392,7 +15263,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Search/searchServices@2025-02-01-preview#properties/tags" + "source": "Microsoft.Search/searchServices@2025-05-01#properties/tags" }, "description": "Optional. Tags to help categorize the resource in the Azure portal." }, @@ -15426,7 +15297,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.search-searchservice.{0}.{1}', replace('0.11.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.search-searchservice.{0}.{1}', replace('0.12.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -15444,7 +15315,7 @@ }, "searchService": { "type": "Microsoft.Search/searchServices", - "apiVersion": "2025-02-01-preview", + "apiVersion": "2025-05-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "sku": { @@ -15463,7 +15334,9 @@ "partitionCount": "[parameters('partitionCount')]", "replicaCount": "[parameters('replicaCount')]", "publicNetworkAccess": "[toLower(parameters('publicNetworkAccess'))]", - "semanticSearch": "[parameters('semanticSearch')]" + "semanticSearch": "[parameters('semanticSearch')]", + "computeType": "[parameters('computeType')]", + "dataExfiltrationProtections": "[parameters('dataExfiltrationProtections')]" } }, "searchService_diagnosticSettings": { @@ -15549,7 +15422,7 @@ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-searchService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", @@ -15605,8 +15478,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "12389807800450456797" + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" }, "name": "Private Endpoints", "description": "This module deploys a Private Endpoint." @@ -15633,115 +15506,8 @@ } }, "metadata": { - "__bicep_export!": true - } - }, - "ipConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the resource that is unique within a resource group." - } - }, - "properties": { - "type": "object", - "properties": { - "groupId": { - "type": "string", - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "memberName": { - "type": "string", - "metadata": { - "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "privateIPAddress": { - "type": "string", - "metadata": { - "description": "Required. A private IP address obtained from the private endpoint's subnet." - } - } - }, - "metadata": { - "description": "Required. Properties of private endpoint IP configurations." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "privateLinkServiceConnectionType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the private link service connection." - } - }, - "properties": { - "type": "object", - "properties": { - "groupIds": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." - } - }, - "privateLinkServiceId": { - "type": "string", - "metadata": { - "description": "Required. The resource id of private link service." - } - }, - "requestMessage": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." - } - } - }, - "metadata": { - "description": "Required. Properties of private link service connection." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "customDnsConfigType": { - "type": "object", - "properties": { - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN that resolves to private endpoint IP address." - } - }, - "ipAddresses": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. A list of private IP addresses of the private endpoint." - } - } - }, - "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private dns zone group." } }, "lockType": { @@ -15765,12 +15531,19 @@ "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.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -15792,6 +15565,7 @@ } }, "metadata": { + "description": "The type of a private DNS zone group configuration.", "__bicep_imported_from!": { "sourceTemplate": "private-dns-zone-group/main.bicep" } @@ -15868,7 +15642,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } } @@ -15905,13 +15679,13 @@ }, "ipConfigurations": { "type": "array", - "items": { - "$ref": "#/definitions/ipConfigurationType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." - } + }, + "nullable": true }, "privateDnsZoneGroup": { "$ref": "#/definitions/privateDnsZoneGroupType", @@ -15946,40 +15720,43 @@ }, "tags": { "type": "object", - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." - } + }, + "nullable": true }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, "description": "Optional. Custom DNS configurations." - } + }, + "nullable": true }, "manualPrivateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "privateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "enableTelemetry": { "type": "bool", @@ -16014,8 +15791,8 @@ "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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": { @@ -16033,7 +15810,7 @@ }, "privateEndpoint": { "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -16065,7 +15842,7 @@ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", "properties": { "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", - "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + "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" @@ -16096,7 +15873,7 @@ "privateEndpoint_privateDnsZoneGroup": { "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", "properties": { "expressionEvaluationOptions": { @@ -16121,8 +15898,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "13997305779829540948" + "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." @@ -16146,7 +15923,8 @@ } }, "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." } } }, @@ -16176,33 +15954,30 @@ } } }, - "variables": { - "copy": [ - { - "name": "privateDnsZoneConfigsVar", - "count": "[length(parameters('privateDnsZoneConfigs'))]", - "input": { - "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", - "properties": { - "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" - } - } - } - ] - }, "resources": { "privateEndpoint": { "existing": true, "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('privateEndpointName')]" }, "privateDnsZoneGroup": { "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", "properties": { - "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + "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]" + } + } + } + ] } } }, @@ -16263,14 +16038,15 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]" + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, "description": "The custom DNS configurations of the private endpoint." }, "value": "[reference('privateEndpoint').customDnsConfigs]" @@ -16308,7 +16084,7 @@ "batchSize": 1 }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-searchService-SharedPrvLink-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { @@ -16342,8 +16118,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "557730297583881254" + "version": "0.39.26.7824", + "templateHash": "2115224445601868607" }, "name": "Search Services Private Link Resources", "description": "This module deploys a Search Service Private Link Resource." @@ -16391,12 +16167,12 @@ "searchService": { "existing": true, "type": "Microsoft.Search/searchServices", - "apiVersion": "2025-02-01-preview", + "apiVersion": "2025-05-01", "name": "[parameters('searchServiceName')]" }, "sharedPrivateLinkResource": { "type": "Microsoft.Search/searchServices/sharedPrivateLinkResources", - "apiVersion": "2025-02-01-preview", + "apiVersion": "2025-05-01", "name": "[format('{0}/{1}', parameters('searchServiceName'), parameters('name'))]", "properties": { "privateLinkResourceId": "[parameters('privateLinkResourceId')]", @@ -16438,7 +16214,7 @@ "secretsExport": { "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "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]]", @@ -16452,7 +16228,7 @@ "value": "[last(split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/'))]" }, "secretsToSet": { - "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-02-01-preview').primaryKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-02-01-preview').secondaryKey)), createArray()))]" + "value": "[union(createArray(), if(contains(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'primaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-05-01').primaryKey)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'secondaryAdminKeyName'), 'value', listAdminKeys('searchService', '2025-05-01').secondaryKey)), createArray()))]" } }, "template": { @@ -16462,8 +16238,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.37.4.10188", - "templateHash": "7634110751636246703" + "version": "0.39.26.7824", + "templateHash": "696453183181258843" } }, "definitions": { @@ -16595,14 +16371,14 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[tryGet(tryGet(reference('searchService', '2025-02-01-preview', 'full'), 'identity'), 'principalId')]" + "value": "[tryGet(tryGet(reference('searchService', '2025-05-01', 'full'), 'identity'), 'principalId')]" }, "location": { "type": "string", "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('searchService', '2025-02-01-preview', 'full').location]" + "value": "[reference('searchService', '2025-05-01', 'full').location]" }, "endpoint": { "type": "string", @@ -16642,14 +16418,14 @@ "metadata": { "description": "The primary admin API key of the search service." }, - "value": "[listAdminKeys('searchService', '2025-02-01-preview').primaryKey]" + "value": "[listAdminKeys('searchService', '2025-05-01').primaryKey]" }, "secondaryKey": { "type": "securestring", "metadata": { "description": "The secondaryKey admin API key of the search service." }, - "value": "[listAdminKeys('searchService', '2025-02-01-preview').secondaryKey]" + "value": "[listAdminKeys('searchService', '2025-05-01').secondaryKey]" } } } @@ -16747,8 +16523,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8444048237705693390" + "version": "0.40.2.10011", + "templateHash": "9428266357530474061" }, "name": "Storage Accounts", "description": "This module deploys a Storage Account." @@ -17336,14 +17112,14 @@ "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." + "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." + "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." } } }, @@ -18016,6 +17792,22 @@ "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": { @@ -19091,7 +18883,7 @@ "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", - "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.30.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.storage-storageaccount.{0}.{1}', replace('0.31.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -19127,7 +18919,7 @@ }, "storageAccount": { "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2025-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "extendedLocation": "[if(not(empty(parameters('extendedLocationZone'))), createObject('name', parameters('extendedLocationZone'), 'type', 'EdgeZone'), null())]", @@ -19150,7 +18942,7 @@ }, "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]", "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", "properties": { "copy": [ @@ -19179,7 +18971,7 @@ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", "type": "Microsoft.Authorization/locks", "apiVersion": "2020-05-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "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'), '')]", @@ -19196,7 +18988,7 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "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]", @@ -19895,8 +19687,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4538661605890101674" + "version": "0.40.2.10011", + "templateHash": "10458708345324133836" }, "name": "Storage Account Management Policies", "description": "This module deploys a Storage Account Management Policy." @@ -19913,7 +19705,7 @@ "type": "array", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Storage/storageAccounts/managementPolicies@2024-01-01#properties/properties/properties/policy/properties/rules" + "source": "Microsoft.Storage/storageAccounts/managementPolicies@2025-06-01#properties/properties/properties/policy/properties/rules" }, "description": "Required. The Storage Account ManagementPolicies Rules." } @@ -19922,7 +19714,7 @@ "resources": [ { "type": "Microsoft.Storage/storageAccounts/managementPolicies", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", "properties": { "policy": { @@ -20007,8 +19799,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17421429164012186211" + "version": "0.40.2.10011", + "templateHash": "18432545368646748336" }, "name": "Storage Account Local Users", "description": "This module deploys a Storage Account Local User, which is used for SFTP authentication." @@ -20126,12 +19918,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('storageAccountName')]" }, "localUsers": { "type": "Microsoft.Storage/storageAccounts/localUsers", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), parameters('name'))]", "properties": { "hasSharedKey": "[parameters('hasSharedKey')]", @@ -20245,8 +20037,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4804748808287128942" + "version": "0.40.2.10011", + "templateHash": "10857560398903249320" }, "name": "Storage Account blob Services", "description": "This module deploys a Storage Account Blob Service." @@ -20537,14 +20329,14 @@ "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." + "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." + "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." } } }, @@ -20820,7 +20612,7 @@ }, "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', parameters('storageAccountName'), variables('name'))]", + "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": [ @@ -20915,8 +20707,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4737601268768949442" + "version": "0.40.2.10011", + "templateHash": "11027676755461897101" }, "name": "Storage Account Blob Containers", "description": "This module deploys a Storage Account Blob Container." @@ -20936,14 +20728,14 @@ "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." + "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." + "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." } } }, @@ -21166,7 +20958,7 @@ "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))]", + "name": "[format('46d3xbcp.res.storage-blobcontainer.{0}.{1}', replace('0.3.3', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -21209,7 +21001,7 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}/containers/{2}', parameters('storageAccountName'), parameters('blobServiceName'), parameters('name'))]", + "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]", @@ -21257,8 +21049,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "9416359146780945405" + "version": "0.40.2.10011", + "templateHash": "7095313291458772891" }, "name": "Storage Account Blob Container Immutability Policies", "description": "This module deploys a Storage Account Blob Container Immutability Policy." @@ -21286,27 +21078,24 @@ }, "allowProtectedAppendWrites": { "type": "bool", - "defaultValue": true, + "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." + "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": true, + "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." + "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." } } }, - "variables": { - "name": "default" - }, "resources": [ { "type": "Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies", "apiVersion": "2025-01-01", - "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), variables('name'))]", + "name": "[format('{0}/{1}/{2}/{3}', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]", "properties": { "immutabilityPeriodSinceCreationInDays": "[parameters('immutabilityPeriodSinceCreationInDays')]", "allowProtectedAppendWrites": "[parameters('allowProtectedAppendWrites')]", @@ -21320,14 +21109,14 @@ "metadata": { "description": "The name of the deployed immutability policy." }, - "value": "[variables('name')]" + "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'), variables('name'))]" + "value": "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers/immutabilityPolicies', parameters('storageAccountName'), 'default', parameters('containerName'), 'default')]" }, "resourceGroupName": { "type": "string", @@ -21440,8 +21229,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8847095544204825048" + "version": "0.40.2.10011", + "templateHash": "17257177273818228540" }, "name": "Storage Account File Share Services", "description": "This module deploys a Storage Account File Share Service." @@ -21559,6 +21348,22 @@ "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": { @@ -21849,12 +21654,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('storageAccountName')]" }, "fileServices": { "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2024-01-01", + "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())]", @@ -21869,7 +21674,7 @@ }, "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/fileServices/{1}', parameters('storageAccountName'), parameters('name'))]", + "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": [ @@ -21927,7 +21732,7 @@ "value": "[coalesce(parameters('shares'), createArray())[copyIndex()].name]" }, "accessTier": { - "value": "[coalesce(tryGet(coalesce(parameters('shares'), createArray())[copyIndex()], 'accessTier'), if(equals(reference('storageAccount', '2024-01-01', 'full').kind, 'FileStorage'), 'Premium', 'TransactionOptimized'))]" + "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')]" @@ -21938,6 +21743,12 @@ "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')]" }, @@ -21952,8 +21763,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1953115828549574279" + "version": "0.40.2.10011", + "templateHash": "3816110293032807861" }, "name": "Storage Account File Shares", "description": "This module deploys a Storage Account File Share." @@ -22058,7 +21869,7 @@ }, "accessTier": { "type": "string", - "defaultValue": "TransactionOptimized", + "nullable": true, "allowedValues": [ "Premium", "Hot", @@ -22066,7 +21877,7 @@ "TransactionOptimized" ], "metadata": { - "description": "Conditional. Access tier for specific share. Required if the Storage Account kind is set to FileStorage (should be set to \"Premium\"). GpV2 account can choose between TransactionOptimized (default), Hot, and Cool." + "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": { @@ -22099,6 +21910,24 @@ "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, @@ -22144,14 +21973,14 @@ "storageAccount::fileService": { "existing": true, "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2024-01-01", + "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))]", + "name": "[format('46d3xbcp.res.storage-fileshare.{0}.{1}', replace('0.1.3', '.', '-'), substring(uniqueString(deployment().name), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -22170,19 +21999,24 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-01-01", "name": "[parameters('storageAccountName')]" }, "fileShare": { "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2024-01-01", + "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')]" - } + "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": { @@ -22414,8 +22248,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "762865197442503763" + "version": "0.40.2.10011", + "templateHash": "4565703002375249410" }, "name": "Storage Account Queue Services", "description": "This module deploys a Storage Account Queue Service." @@ -22760,12 +22594,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('storageAccountName')]" }, "queueServices": { "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2024-01-01", + "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())]" @@ -22778,7 +22612,7 @@ }, "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}', parameters('storageAccountName'), variables('name'))]", + "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": [ @@ -22846,8 +22680,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2653192815476217627" + "version": "0.40.2.10011", + "templateHash": "7074748362797990420" }, "name": "Storage Account Queues", "description": "This module deploys a Storage Account Queue." @@ -22992,18 +22826,18 @@ "storageAccount::queueServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/queueServices", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]" }, "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('storageAccountName')]" }, "queue": { "type": "Microsoft.Storage/storageAccounts/queueServices/queues", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", "properties": { "metadata": "[parameters('metadata')]" @@ -23016,7 +22850,7 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/queueServices/{1}/queues/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "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]", @@ -23119,8 +22953,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17140438874562378925" + "version": "0.40.2.10011", + "templateHash": "6375163985707233003" }, "name": "Storage Account Table Services", "description": "This module deploys a Storage Account Table Service." @@ -23455,12 +23289,12 @@ "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('storageAccountName')]" }, "tableServices": { "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2024-01-01", + "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())]" @@ -23473,7 +23307,7 @@ }, "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}', parameters('storageAccountName'), variables('name'))]", + "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": [ @@ -23538,8 +23372,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "11466809443516053137" + "version": "0.40.2.10011", + "templateHash": "5944147787326354061" }, "name": "Storage Account Table", "description": "This module deploys a Storage Account Table." @@ -23672,18 +23506,18 @@ "storageAccount::tableServices": { "existing": true, "type": "Microsoft.Storage/storageAccounts/tableServices", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]" }, "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[parameters('storageAccountName')]" }, "table": { "type": "Microsoft.Storage/storageAccounts/tableServices/tables", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', parameters('name'))]" }, "table_roleAssignments": { @@ -23693,7 +23527,7 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/tableServices/{1}/tables/{2}', parameters('storageAccountName'), 'default', parameters('name'))]", + "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]", @@ -23782,7 +23616,7 @@ "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-01-01').keys[0].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString1Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString1Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-01-01').keys[0].value, environment().suffixes.storage))), createArray()), if(contains(parameters('secretsExportConfiguration'), 'accessKey2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'accessKey2Name'), 'value', listKeys('storageAccount', '2025-01-01').keys[1].value)), createArray()), if(contains(parameters('secretsExportConfiguration'), 'connectionString2Name'), createArray(createObject('name', tryGet(parameters('secretsExportConfiguration'), 'connectionString2Name'), 'value', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-01-01').keys[1].value, environment().suffixes.storage))), createArray()))]" + "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": { @@ -23792,8 +23626,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13614544361780789643" + "version": "0.40.2.10011", + "templateHash": "16028661694229002815" } }, "definitions": { @@ -23946,8 +23780,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3528396711847214833" + "version": "0.40.2.10011", + "templateHash": "10883422529455341146" }, "name": "Storage Account Object Replication Policy", "description": "This module deploys a Storage Account Object Replication Policy for both the source account and destination account." @@ -24100,8 +23934,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4325417308313318683" + "version": "0.40.2.10011", + "templateHash": "28895254110549746" }, "name": "Storage Account Object Replication Policy", "description": "This module deploys a Storage Account Object Replication Policy for a provided storage account." @@ -24317,8 +24151,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4325417308313318683" + "version": "0.40.2.10011", + "templateHash": "28895254110549746" }, "name": "Storage Account Object Replication Policy", "description": "This module deploys a Storage Account Object Replication Policy for a provided storage account." @@ -24562,14 +24396,14 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[tryGet(tryGet(reference('storageAccount', '2025-01-01', 'full'), 'identity'), 'principalId')]" + "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-01-01', 'full').location]" + "value": "[reference('storageAccount', '2025-06-01', 'full').location]" }, "serviceEndpoints": { "type": "object", @@ -24609,28 +24443,28 @@ "metadata": { "description": "The primary access key of the storage account." }, - "value": "[listKeys('storageAccount', '2025-01-01').keys[0].value]" + "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-01-01').keys[1].value]" + "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-01-01').keys[0].value, environment().suffixes.storage)]" + "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-01-01').keys[1].value, environment().suffixes.storage)]" + "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', parameters('name'), listKeys('storageAccount', '2025-06-01').keys[1].value, environment().suffixes.storage)]" } } } @@ -30621,9 +30455,7 @@ }, "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]", "skuName": "[if(or(parameters('enableScalability'), parameters('enableRedundancy')), createObject('value', 'P1v3'), createObject('value', 'B1'))]", - "skuCapacity": { - "value": 1 - }, + "skuCapacity": "[if(parameters('enableRedundancy'), createObject('value', 2), createObject('value', 1))]", "zoneRedundant": "[if(parameters('enableRedundancy'), createObject('value', true()), createObject('value', false()))]" }, "template": { @@ -30633,8 +30465,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "16945786131371363466" + "version": "0.40.2.10011", + "templateHash": "17925345736511474747" }, "name": "App Service Plan", "description": "This module deploys an App Service Plan." @@ -30725,7 +30557,7 @@ "metadata": { "description": "An AVM-aligned type for a diagnostic setting. To be used if only metrics are supported by the resource provider.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -30762,7 +30594,35 @@ "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" + "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" } } }, @@ -30837,7 +30697,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.0" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } } @@ -30851,6 +30711,13 @@ "description": "Required. Name of the app service plan." } }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, "skuName": { "type": "string", "defaultValue": "P1v3", @@ -30866,75 +30733,82 @@ "description": "Optional. Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones." } }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, "kind": { "type": "string", - "defaultValue": "app", - "allowedValues": [ - "app", - "elastic", - "functionapp", - "windows", - "linux" - ], "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/kind" + }, "description": "Optional. Kind of server OS." - } + }, + "defaultValue": "app" }, "reserved": { "type": "bool", - "defaultValue": "[equals(parameters('kind'), 'linux')]", "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/reserved" + }, "description": "Conditional. Defaults to false when creating Windows/app App Service Plan. Required if creating a Linux App Service Plan and must be set to true." - } + }, + "defaultValue": "[equals(parameters('kind'), 'linux')]" }, "appServiceEnvironmentResourceId": { "type": "string", - "defaultValue": "", + "nullable": true, "metadata": { "description": "Optional. The Resource ID of the App Service Environment to use for the App Service Plan." } }, "workerTierName": { "type": "string", - "defaultValue": "", "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/workerTierName" + }, "description": "Optional. Target worker tier assigned to the App Service plan." - } + }, + "nullable": true }, "perSiteScaling": { "type": "bool", - "defaultValue": false, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/perSiteScaling" + }, "description": "Optional. If true, apps assigned to this App Service plan can be scaled independently. If false, apps assigned to this App Service plan will scale to all instances of the plan." - } + }, + "defaultValue": false }, "elasticScaleEnabled": { "type": "bool", - "defaultValue": "[greater(parameters('maximumElasticWorkerCount'), 1)]", "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/elasticScaleEnabled" + }, "description": "Optional. Enable/Disable ElasticScaleEnabled App Service Plan." - } + }, + "defaultValue": "[greater(parameters('maximumElasticWorkerCount'), 1)]" }, "maximumElasticWorkerCount": { "type": "int", - "defaultValue": 1, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/maximumElasticWorkerCount" + }, "description": "Optional. Maximum number of total workers allowed for this ElasticScaleEnabled App Service Plan." - } + }, + "defaultValue": 1 }, "targetWorkerCount": { "type": "int", - "defaultValue": 0, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/targetWorkerCount" + }, "description": "Optional. Scaling worker count." - } + }, + "defaultValue": 0 }, "targetWorkerSize": { "type": "int", @@ -30950,9 +30824,96 @@ }, "zoneRedundant": { "type": "bool", - "defaultValue": "[if(or(startsWith(parameters('skuName'), 'P'), startsWith(parameters('skuName'), 'EP')), true(), false())]", "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/zoneRedundant" + }, "description": "Optional. Zone Redundant server farms can only be used on Premium or ElasticPremium SKU tiers within ZRS Supported regions (https://learn.microsoft.com/en-us/azure/storage/common/redundancy-regions-zrs)." + }, + "defaultValue": "[if(or(startsWith(parameters('skuName'), 'P'), startsWith(parameters('skuName'), 'EP')), true(), false())]" + }, + "hyperV": { + "type": "bool", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/hyperV" + }, + "description": "Optional. If Hyper-V container app service plan true, false otherwise." + }, + "nullable": true + }, + "virtualNetworkSubnetId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource ID of the subnet to integrate the App Service Plan with for VNet integration." + } + }, + "isCustomMode": { + "type": "bool", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/isCustomMode" + }, + "description": "Optional. Set to true to enable Managed Instance custom mode. Required for App Service Managed Instance plans." + }, + "defaultValue": false + }, + "rdpEnabled": { + "type": "bool", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/rdpEnabled" + }, + "description": "Optional. Whether RDP is enabled for Managed Instance plans. Only applicable when isCustomMode is true. Requires a Bastion host deployed in the VNet." + }, + "nullable": true + }, + "installScripts": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/installScripts" + }, + "description": "Optional. A list of install scripts for Managed Instance plans. Only applicable when isCustomMode is true." + }, + "nullable": true + }, + "planDefaultIdentity": { + "type": "object", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/planDefaultIdentity" + }, + "description": "Optional. The default identity configuration for Managed Instance plans. Only applicable when isCustomMode is true." + }, + "nullable": true + }, + "registryAdapters": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/registryAdapters" + }, + "description": "Optional. A list of registry adapters for Managed Instance plans. Only applicable when isCustomMode is true." + }, + "nullable": true + }, + "storageMounts": { + "type": "array", + "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/properties/properties/storageMounts" + }, + "description": "Optional. A list of storage mounts for Managed Instance plans. Only applicable when isCustomMode is true." + }, + "nullable": true + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentityAllType", + "nullable": true, + "metadata": { + "description": "Optional. The managed identity definition for this resource." } }, "lock": { @@ -30976,19 +30937,12 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Web/serverfarms@2024-11-01#properties/tags" + "source": "Microsoft.Web/serverfarms@2025-03-01#properties/tags" }, "description": "Optional. Tags of the resource." }, "nullable": true }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." - } - }, "diagnosticSettings": { "type": "array", "items": { @@ -30998,6 +30952,13 @@ "metadata": { "description": "Optional. The diagnostic settings of the service." } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } } }, "variables": { @@ -31016,14 +30977,16 @@ "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]", "Web Plan Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2cc479cb-7b4d-49a8-b449-8c00fd0f0a4b')]", "Website Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')]" - } + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned, UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" }, "resources": { "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.web-serverfarm.{0}.{1}', replace('0.5.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "name": "[format('46d3xbcp.res.web-serverfarm.{0}.{1}', replace('0.7.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", "properties": { "mode": "Incremental", "template": { @@ -31041,22 +31004,31 @@ }, "appServicePlan": { "type": "Microsoft.Web/serverfarms", - "apiVersion": "2024-11-01", + "apiVersion": "2025-03-01", "name": "[parameters('name')]", "kind": "[parameters('kind')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", "sku": "[if(equals(parameters('skuName'), 'FC1'), createObject('name', parameters('skuName'), 'tier', 'FlexConsumption'), createObject('name', parameters('skuName'), 'capacity', parameters('skuCapacity')))]", "properties": { "workerTierName": "[parameters('workerTierName')]", - "hostingEnvironmentProfile": "[if(not(empty(parameters('appServiceEnvironmentResourceId'))), createObject('id', parameters('appServiceEnvironmentResourceId')), null())]", + "hostingEnvironmentProfile": "[if(not(equals(parameters('appServiceEnvironmentResourceId'), null())), createObject('id', parameters('appServiceEnvironmentResourceId')), null())]", "perSiteScaling": "[parameters('perSiteScaling')]", "maximumElasticWorkerCount": "[parameters('maximumElasticWorkerCount')]", "elasticScaleEnabled": "[parameters('elasticScaleEnabled')]", "reserved": "[parameters('reserved')]", "targetWorkerCount": "[parameters('targetWorkerCount')]", "targetWorkerSizeId": "[parameters('targetWorkerSize')]", - "zoneRedundant": "[parameters('zoneRedundant')]" + "zoneRedundant": "[parameters('zoneRedundant')]", + "hyperV": "[parameters('hyperV')]", + "isCustomMode": "[parameters('isCustomMode')]", + "network": "[if(not(equals(parameters('virtualNetworkSubnetId'), null())), createObject('virtualNetworkSubnetId', parameters('virtualNetworkSubnetId')), null())]", + "rdpEnabled": "[if(parameters('isCustomMode'), parameters('rdpEnabled'), null())]", + "installScripts": "[if(parameters('isCustomMode'), parameters('installScripts'), null())]", + "planDefaultIdentity": "[if(parameters('isCustomMode'), parameters('planDefaultIdentity'), null())]", + "registryAdapters": "[if(parameters('isCustomMode'), parameters('registryAdapters'), null())]", + "storageMounts": "[if(parameters('isCustomMode'), parameters('storageMounts'), null())]" } }, "appServicePlan_diagnosticSettings": { @@ -31066,7 +31038,7 @@ }, "type": "Microsoft.Insights/diagnosticSettings", "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]", + "scope": "[resourceId('Microsoft.Web/serverfarms', parameters('name'))]", "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", "properties": { "copy": [ @@ -31095,7 +31067,7 @@ "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", "type": "Microsoft.Authorization/locks", "apiVersion": "2020-05-01", - "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]", + "scope": "[resourceId('Microsoft.Web/serverfarms', parameters('name'))]", "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", "properties": { "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", @@ -31112,7 +31084,7 @@ }, "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Web/serverfarms/{0}', parameters('name'))]", + "scope": "[resourceId('Microsoft.Web/serverfarms', parameters('name'))]", "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Web/serverfarms', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", "properties": { "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", @@ -31155,7 +31127,15 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('appServicePlan', '2024-11-01', 'full').location]" + "value": "[reference('appServicePlan', '2025-03-01', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('appServicePlan', '2025-03-01', 'full'), 'identity'), 'principalId')]" } } } @@ -31211,6 +31191,9 @@ "enableMonitoring": { "value": "[parameters('enableMonitoring')]" }, + "enableTelemetry": { + "value": "[parameters('enableTelemetry')]" + }, "diagnosticSettings": "[if(parameters('enableMonitoring'), createObject('value', createArray(createObject('workspaceResourceId', if(variables('useExistingLogAnalytics'), parameters('existingLogAnalyticsWorkspaceId'), if(parameters('enableMonitoring'), reference('logAnalyticsWorkspace').outputs.resourceId.value, ''))))), createObject('value', null()))]", "vnetRouteAllEnabled": { "value": "[parameters('enablePrivateNetworking')]" @@ -31230,7 +31213,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "17215033518306564965" + "templateHash": "8373975196748337393" } }, "definitions": { @@ -31898,6 +31881,13 @@ "description": "Optional. Enable monitoring and logging configuration." } }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, "virtualNetworkSubnetId": { "type": "string", "nullable": true, @@ -31937,7 +31927,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/siteConfig" + "source": "Microsoft.Web/sites@2025-03-01#properties/properties/properties/siteConfig" }, "description": "Optional. The site config object." }, @@ -31961,7 +31951,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/functionAppConfig" + "source": "Microsoft.Web/sites@2025-03-01#properties/properties/properties/functionAppConfig" }, "description": "Optional. The Function App configuration object." }, @@ -32024,7 +32014,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/cloningInfo" + "source": "Microsoft.Web/sites@2025-03-01#properties/properties/properties/cloningInfo" }, "description": "Optional. If specified during app creation, the app is cloned from a source app." }, @@ -32055,7 +32045,7 @@ "type": "array", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/hostNameSslStates" + "source": "Microsoft.Web/sites@2025-03-01#properties/properties/properties/hostNameSslStates" }, "description": "Optional. Hostname SSL states are used to manage the SSL bindings for app's hostnames." }, @@ -32104,7 +32094,7 @@ "type": "object", "metadata": { "__bicep_resource_derived_type!": { - "source": "Microsoft.Web/sites@2024-04-01#properties/properties/properties/dnsConfiguration" + "source": "Microsoft.Web/sites@2025-03-01#properties/properties/properties/dnsConfiguration" }, "description": "Optional. Property to configure various DNS related settings for a site." }, @@ -32126,12 +32116,13 @@ }, "variables": { "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", - "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned, UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + "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())]", + "mergedSiteConfig": "[union(parameters('siteConfig'), createObject('vnetRouteAllEnabled', parameters('vnetRouteAllEnabled'), 'vnetImagePullEnabled', parameters('vnetImagePullEnabled'), 'vnetContentShareEnabled', parameters('vnetContentShareEnabled')))]" }, "resources": { "app": { "type": "Microsoft.Web/sites", - "apiVersion": "2024-04-01", + "apiVersion": "2025-03-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "kind": "[parameters('kind')]", @@ -32146,7 +32137,7 @@ "storageAccountRequired": "[parameters('storageAccountRequired')]", "keyVaultReferenceIdentity": "[parameters('keyVaultAccessIdentityResourceId')]", "virtualNetworkSubnetId": "[parameters('virtualNetworkSubnetId')]", - "siteConfig": "[parameters('siteConfig')]", + "siteConfig": "[variables('mergedSiteConfig')]", "functionAppConfig": "[parameters('functionAppConfig')]", "clientCertEnabled": "[parameters('clientCertEnabled')]", "clientCertExclusionPaths": "[parameters('clientCertExclusionPaths')]", @@ -32159,9 +32150,6 @@ "hyperV": "[parameters('hyperV')]", "redundancyMode": "[parameters('redundancyMode')]", "publicNetworkAccess": "[if(not(empty(parameters('publicNetworkAccess'))), parameters('publicNetworkAccess'), if(not(empty(parameters('privateEndpoints'))), 'Disabled', 'Enabled'))]", - "vnetContentShareEnabled": "[parameters('vnetContentShareEnabled')]", - "vnetImagePullEnabled": "[parameters('vnetImagePullEnabled')]", - "vnetRouteAllEnabled": "[parameters('vnetRouteAllEnabled')]", "scmSiteAlsoStopped": "[parameters('scmSiteAlsoStopped')]", "endToEndEncryptionEnabled": "[parameters('e2eEncryptionEnabled')]", "dnsConfiguration": "[parameters('dnsConfiguration')]", @@ -32254,7 +32242,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "26032048033705967" + "templateHash": "5518029515589119726" }, "name": "Site App Settings", "description": "This module deploys a Site App Setting." @@ -32352,7 +32340,7 @@ "condition": "[not(empty(parameters('storageAccountResourceId')))]", "existing": true, "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2024-01-01", + "apiVersion": "2025-06-01", "subscriptionId": "[split(parameters('storageAccountResourceId'), '/')[2]]", "resourceGroup": "[split(parameters('storageAccountResourceId'), '/')[4]]", "name": "[last(split(parameters('storageAccountResourceId'), '/'))]" @@ -32360,14 +32348,14 @@ "app": { "existing": true, "type": "Microsoft.Web/sites", - "apiVersion": "2023-12-01", + "apiVersion": "2025-03-01", "name": "[parameters('appName')]" }, "config": { "type": "Microsoft.Web/sites/config", - "apiVersion": "2024-04-01", + "apiVersion": "2025-03-01", "name": "[format('{0}/{1}', parameters('appName'), parameters('name'))]", - "properties": "[union(parameters('properties'), parameters('currentAppSettings'), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(parameters('storageAccountResourceId'), '/')), listKeys('storageAccount', '2024-01-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), createObject('AzureWebJobsStorage__accountName', last(split(parameters('storageAccountResourceId'), '/')), 'AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob, 'AzureWebJobsStorage__queueServiceUri', reference('storageAccount').primaryEndpoints.queue, 'AzureWebJobsStorage__tableServiceUri', reference('storageAccount').primaryEndpoints.table), createObject())), if(not(empty(parameters('applicationInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('applicationInsights').ConnectionString), createObject()), variables('loggingProperties'))]", + "properties": "[union(parameters('properties'), parameters('currentAppSettings'), if(and(not(empty(parameters('storageAccountResourceId'))), not(parameters('storageAccountUseIdentityAuthentication'))), createObject('AzureWebJobsStorage', format('DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}', last(split(parameters('storageAccountResourceId'), '/')), listKeys('storageAccount', '2025-06-01').keys[0].value, environment().suffixes.storage)), if(and(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountUseIdentityAuthentication')), createObject('AzureWebJobsStorage__accountName', last(split(parameters('storageAccountResourceId'), '/')), 'AzureWebJobsStorage__blobServiceUri', reference('storageAccount').primaryEndpoints.blob, 'AzureWebJobsStorage__queueServiceUri', reference('storageAccount').primaryEndpoints.queue, 'AzureWebJobsStorage__tableServiceUri', reference('storageAccount').primaryEndpoints.table), createObject())), if(not(empty(parameters('applicationInsightResourceId'))), createObject('APPLICATIONINSIGHTS_CONNECTION_STRING', reference('applicationInsights').ConnectionString), createObject()), variables('loggingProperties'))]", "dependsOn": [ "applicationInsights", "storageAccount" @@ -32428,7 +32416,7 @@ "value": "[coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId]" }, "enableTelemetry": { - "value": false + "value": "[parameters('enableTelemetry')]" }, "location": { "value": "[coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'location'), reference(split(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()].subnetResourceId, '/subnets/')[0], '2020-06-01', 'Full').location)]" @@ -32465,8 +32453,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "12389807800450456797" + "version": "0.38.5.1644", + "templateHash": "16604612898799598358" }, "name": "Private Endpoints", "description": "This module deploys a Private Endpoint." @@ -32493,115 +32481,8 @@ } }, "metadata": { - "__bicep_export!": true - } - }, - "ipConfigurationType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the resource that is unique within a resource group." - } - }, - "properties": { - "type": "object", - "properties": { - "groupId": { - "type": "string", - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "memberName": { - "type": "string", - "metadata": { - "description": "Required. The member name of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string." - } - }, - "privateIPAddress": { - "type": "string", - "metadata": { - "description": "Required. A private IP address obtained from the private endpoint's subnet." - } - } - }, - "metadata": { - "description": "Required. Properties of private endpoint IP configurations." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "privateLinkServiceConnectionType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the private link service connection." - } - }, - "properties": { - "type": "object", - "properties": { - "groupIds": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. The ID of a group obtained from the remote resource that this private endpoint should connect to. If used with private link service connection, this property must be defined as empty string array `[]`." - } - }, - "privateLinkServiceId": { - "type": "string", - "metadata": { - "description": "Required. The resource id of private link service." - } - }, - "requestMessage": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. A message passed to the owner of the remote resource with this connection request. Restricted to 140 chars." - } - } - }, - "metadata": { - "description": "Required. Properties of private link service connection." - } - } - }, - "metadata": { - "__bicep_export!": true - } - }, - "customDnsConfigType": { - "type": "object", - "properties": { - "fqdn": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. FQDN that resolves to private endpoint IP address." - } - }, - "ipAddresses": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. A list of private IP addresses of the private endpoint." - } - } - }, - "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private dns zone group." } }, "lockType": { @@ -32625,12 +32506,19 @@ "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.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } }, @@ -32652,6 +32540,7 @@ } }, "metadata": { + "description": "The type of a private DNS zone group configuration.", "__bicep_imported_from!": { "sourceTemplate": "private-dns-zone-group/main.bicep" } @@ -32728,7 +32617,7 @@ "metadata": { "description": "An AVM-aligned type for a role assignment.", "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.5.1" + "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.6.1" } } } @@ -32765,13 +32654,13 @@ }, "ipConfigurations": { "type": "array", - "items": { - "$ref": "#/definitions/ipConfigurationType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/ipConfigurations" + }, "description": "Optional. A list of IP configurations of the private endpoint. This will be used to map to the First Party Service endpoints." - } + }, + "nullable": true }, "privateDnsZoneGroup": { "$ref": "#/definitions/privateDnsZoneGroupType", @@ -32806,40 +32695,43 @@ }, "tags": { "type": "object", - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/tags" + }, "description": "Optional. Tags to be applied on all resources/resource groups in this deployment." - } + }, + "nullable": true }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs" + }, "description": "Optional. Custom DNS configurations." - } + }, + "nullable": true }, "manualPrivateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/manualPrivateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Used when the network admin does not have access to approve connections to the remote resource. Required if `privateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "privateLinkServiceConnections": { "type": "array", - "items": { - "$ref": "#/definitions/privateLinkServiceConnectionType" - }, - "nullable": true, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/privateLinkServiceConnections" + }, "description": "Conditional. A grouping of information about the connection to the remote resource. Required if `manualPrivateLinkServiceConnections` is empty." - } + }, + "nullable": true }, "enableTelemetry": { "type": "bool", @@ -32874,8 +32766,8 @@ "avmTelemetry": { "condition": "[parameters('enableTelemetry')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.network-privateendpoint.{0}.{1}', replace('0.11.0', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "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": { @@ -32893,7 +32785,7 @@ }, "privateEndpoint": { "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -32925,7 +32817,7 @@ "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", "properties": { "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", - "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + "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" @@ -32956,7 +32848,7 @@ "privateEndpoint_privateDnsZoneGroup": { "condition": "[not(empty(parameters('privateDnsZoneGroup')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}-PrivateEndpoint-PrivateDnsZoneGroup', uniqueString(deployment().name))]", "properties": { "expressionEvaluationOptions": { @@ -32981,8 +32873,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "13997305779829540948" + "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." @@ -33006,7 +32898,8 @@ } }, "metadata": { - "__bicep_export!": true + "__bicep_export!": true, + "description": "The type of a private DNS zone group configuration." } } }, @@ -33036,33 +32929,30 @@ } } }, - "variables": { - "copy": [ - { - "name": "privateDnsZoneConfigsVar", - "count": "[length(parameters('privateDnsZoneConfigs'))]", - "input": { - "name": "[coalesce(tryGet(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')], 'name'), last(split(parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId, '/')))]", - "properties": { - "privateDnsZoneId": "[parameters('privateDnsZoneConfigs')[copyIndex('privateDnsZoneConfigsVar')].privateDnsZoneResourceId]" - } - } - } - ] - }, "resources": { "privateEndpoint": { "existing": true, "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[parameters('privateEndpointName')]" }, "privateDnsZoneGroup": { "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2024-05-01", + "apiVersion": "2024-10-01", "name": "[format('{0}/{1}', parameters('privateEndpointName'), parameters('name'))]", "properties": { - "privateDnsZoneConfigs": "[variables('privateDnsZoneConfigsVar')]" + "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]" + } + } + } + ] } } }, @@ -33123,14 +33013,15 @@ "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('privateEndpoint', '2024-05-01', 'full').location]" + "value": "[reference('privateEndpoint', '2024-10-01', 'full').location]" }, "customDnsConfigs": { "type": "array", - "items": { - "$ref": "#/definitions/customDnsConfigType" - }, "metadata": { + "__bicep_resource_derived_type!": { + "source": "Microsoft.Network/privateEndpoints@2024-01-01#properties/properties/properties/customDnsConfigs", + "output": true + }, "description": "The custom DNS configurations of the private endpoint." }, "value": "[reference('privateEndpoint').customDnsConfigs]" @@ -33189,14 +33080,14 @@ "metadata": { "description": "The principal ID of the system assigned identity." }, - "value": "[tryGet(tryGet(reference('app', '2024-04-01', 'full'), 'identity'), 'principalId')]" + "value": "[tryGet(tryGet(reference('app', '2025-03-01', 'full'), 'identity'), 'principalId')]" }, "location": { "type": "string", "metadata": { "description": "The location the resource was deployed into." }, - "value": "[reference('app', '2024-04-01', 'full').location]" + "value": "[reference('app', '2025-03-01', 'full').location]" }, "defaultHostname": { "type": "string", @@ -33262,9 +33153,6 @@ "value": 8000 }, "subnetResourceId": "[if(parameters('enablePrivateNetworking'), createObject('value', reference('virtualNetwork').outputs.aciSubnetResourceId.value), createObject('value', ''))]", - "registryServer": { - "value": "[format('{0}.azurecr.io', variables('acrResourceName'))]" - }, "userAssignedIdentityResourceId": { "value": "[reference('userAssignedIdentity').outputs.resourceId.value]" }, @@ -33364,6 +33252,22 @@ { "name": "AZURE_AI_IMAGE_MODEL_DEPLOYMENT", "value": "[variables('imageModelConfig')[parameters('imageModelChoice')].name]" + }, + { + "name": "AZURE_BASIC_LOGGING_LEVEL", + "value": "INFO" + }, + { + "name": "AZURE_PACKAGE_LOGGING_LEVEL", + "value": "WARNING" + }, + { + "name": "AZURE_LOGGING_PACKAGES", + "value": "" + }, + { + "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.connectionString.value, '')]" } ] } @@ -33375,7 +33279,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "5828006773867864982" + "templateHash": "11066122245438821615" } }, "parameters": { @@ -33445,17 +33349,10 @@ "description": "Optional. Enable telemetry." } }, - "registryServer": { - "type": "string", - "metadata": { - "description": "Required. Container registry server." - } - }, "userAssignedIdentityResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. User-assigned managed identity resource ID for ACR pull." + "description": "Required. User-assigned managed identity resource ID for ACR pull." } } }, @@ -33479,7 +33376,7 @@ }, { "type": "Microsoft.ContainerInstance/containerGroups", - "apiVersion": "2023-05-01", + "apiVersion": "2025-09-01", "name": "[parameters('name')]", "location": "[parameters('location')]", "tags": "[parameters('tags')]", @@ -33547,20 +33444,21 @@ "metadata": { "description": "The IP address of the container (private or public depending on mode)." }, - "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name')), '2023-05-01').ipAddress.ip]" + "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name')), '2025-09-01').ipAddress.ip]" }, "fqdn": { "type": "string", "metadata": { "description": "The FQDN of the container (only available for public mode)." }, - "value": "[if(variables('isPrivateNetworking'), '', reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name')), '2023-05-01').ipAddress.fqdn)]" + "value": "[if(variables('isPrivateNetworking'), '', reference(resourceId('Microsoft.ContainerInstance/containerGroups', parameters('name')), '2025-09-01').ipAddress.fqdn)]" } } } }, "dependsOn": [ "aiFoundryAiServicesProject", + "applicationInsights", "userAssignedIdentity", "virtualNetwork" ] diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 075a266ee..00c481f2e 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -9,34 +9,28 @@ "value": "${AZURE_LOCATION}" }, "secondaryLocation": { - "value": "${secondaryLocation}" + "value": "${SECONDARY_LOCATION}" }, "gptModelName": { - "value": "${gptModelName}" + "value": "${AZURE_OPENAI_GPT_MODEL}" }, "gptModelVersion": { - "value": "${gptModelVersion}" + "value": "${GPT_MODEL_VERSION}" }, "gptModelDeploymentType": { - "value": "${gptModelDeploymentType}" + "value": "${GPT_MODEL_DEPLOYMENT_TYPE}" }, "gptModelCapacity": { - "value": "${gptModelCapacity}" + "value": "${GPT_MODEL_CAPACITY}" }, "imageModelChoice": { - "value": "${imageModelChoice}" + "value": "${AZURE_OPENAI_IMAGE_MODEL}" }, "imageModelCapacity": { - "value": "${imageModelCapacity}" - }, - "embeddingModel": { - "value": "${embeddingModel}" - }, - "embeddingDeploymentCapacity": { - "value": "${embeddingDeploymentCapacity}" + "value": "${IMAGE_MODEL_CAPACITY}" }, "azureOpenaiAPIVersion": { - "value": "${azureOpenaiAPIVersion}" + "value": "${AZURE_OPENAI_API_VERSION}" }, "azureAiServiceLocation": { "value": "${AZURE_ENV_OPENAI_LOCATION}" diff --git a/infra/main.waf.parameters.json b/infra/main.waf.parameters.json index e34bc97cc..0cc363c9d 100644 --- a/infra/main.waf.parameters.json +++ b/infra/main.waf.parameters.json @@ -9,34 +9,28 @@ "value": "${AZURE_LOCATION}" }, "secondaryLocation": { - "value": "${secondaryLocation}" + "value": "${SECONDARY_LOCATION}" }, "gptModelName": { - "value": "${gptModelName}" + "value": "${AZURE_OPENAI_GPT_MODEL}" }, "gptModelVersion": { - "value": "${gptModelVersion}" + "value": "${GPT_MODEL_VERSION}" }, "gptModelDeploymentType": { - "value": "${gptModelDeploymentType}" + "value": "${GPT_MODEL_DEPLOYMENT_TYPE}" }, "gptModelCapacity": { - "value": "${gptModelCapacity}" + "value": "${GPT_MODEL_CAPACITY}" }, "imageModelChoice": { - "value": "${imageModelChoice}" + "value": "${AZURE_OPENAI_IMAGE_MODEL}" }, "imageModelCapacity": { - "value": "${imageModelCapacity}" - }, - "embeddingModel": { - "value": "${embeddingModel}" - }, - "embeddingDeploymentCapacity": { - "value": "${embeddingDeploymentCapacity}" + "value": "${IMAGE_MODEL_CAPACITY}" }, "azureOpenaiAPIVersion": { - "value": "${azureOpenaiAPIVersion}" + "value": "${AZURE_OPENAI_API_VERSION}" }, "azureAiServiceLocation": { "value": "${AZURE_ENV_OPENAI_LOCATION}" diff --git a/infra/modules/ai-project.bicep b/infra/modules/ai-project.bicep index 6809b0aac..ee6eeeec5 100644 --- a/infra/modules/ai-project.bicep +++ b/infra/modules/ai-project.bicep @@ -22,11 +22,11 @@ var existingOpenAIEndpoint = useExistingAiFoundryAiProject : '' // Reference to cognitive service in current resource group for new projects -resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = { +resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2025-10-01-preview' existing = { name: aiServicesName } -resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-06-01' = { +resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-10-01-preview' = { parent: cogServiceReference name: name tags: tags diff --git a/infra/modules/container-instance.bicep b/infra/modules/container-instance.bicep index 53f7267db..9136d7ac2 100644 --- a/infra/modules/container-instance.bicep +++ b/infra/modules/container-instance.bicep @@ -31,11 +31,8 @@ param environmentVariables array @description('Optional. Enable telemetry.') param enableTelemetry bool = true -@description('Required. Container registry server.') -param registryServer string - -@description('Optional. User-assigned managed identity resource ID for ACR pull.') -param userAssignedIdentityResourceId string = '' +@description('Required. User-assigned managed identity resource ID for ACR pull.') +param userAssignedIdentityResourceId string var isPrivateNetworking = !empty(subnetResourceId) @@ -56,7 +53,7 @@ resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableT } } -resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2023-05-01' = { +resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2025-09-01' = { name: name location: location tags: tags diff --git a/infra/modules/deploy_ai_model.bicep b/infra/modules/deploy_ai_model.bicep new file mode 100644 index 000000000..8ac6237c1 --- /dev/null +++ b/infra/modules/deploy_ai_model.bicep @@ -0,0 +1,35 @@ +@description('Required. Name of the AI Services account.') +param aiServicesName string + +@description('Required. Array of model deployments to create.') +param deployments array = [] + +// Reference AI Services account (module is scoped to the correct resource group) +resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { + name: aiServicesName +} + +// Deploy models to AI Services account +// Using batchSize(1) to avoid concurrent deployment issues +@batchSize(1) +resource modelDeployments 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = [ + for (deployment, index) in deployments: { + parent: aiServices + name: deployment.name + properties: { + model: { + format: deployment.format + name: deployment.model + version: deployment.version + } + raiPolicyName: deployment.raiPolicyName + } + sku: { + name: deployment.sku.name + capacity: deployment.sku.capacity + } + } +] + +@description('The names of the deployed models.') +output deployedModelNames array = [for (deployment, i) in deployments: modelDeployments[i].name] diff --git a/infra/modules/deploy_foundry_role_assignment.bicep b/infra/modules/deploy_foundry_role_assignment.bicep index 3121ca932..041e60d5f 100644 --- a/infra/modules/deploy_foundry_role_assignment.bicep +++ b/infra/modules/deploy_foundry_role_assignment.bicep @@ -36,12 +36,12 @@ resource cognitiveServicesOpenAiUserRole 'Microsoft.Authorization/roleDefinition // ========== Existing Resources ========== // // Reference the existing AI Services account -resource existingAiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { +resource existingAiServices 'Microsoft.CognitiveServices/accounts@2025-10-01-preview' existing = { name: aiServicesName } // Reference the existing AI Project (if provided) -resource existingAiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' existing = if (!empty(aiProjectName)) { +resource existingAiProject 'Microsoft.CognitiveServices/accounts/projects@2025-10-01-preview' existing = if (!empty(aiProjectName)) { name: aiProjectName parent: existingAiServices } @@ -81,4 +81,4 @@ output aiServicesResourceId string = existingAiServices.id output aiServicesEndpoint string = existingAiServices.properties.endpoint @description('The principal ID of the existing AI Project (if provided).') -output aiProjectPrincipalId string = !empty(aiProjectName) ? existingAiProject.identity.principalId : '' +output aiProjectPrincipalId string = !empty(aiProjectName) ? existingAiProject.?identity.?principalId ?? '' : '' diff --git a/infra/modules/web-sites.bicep b/infra/modules/web-sites.bicep index 9f78af838..17a63ea77 100644 --- a/infra/modules/web-sites.bicep +++ b/infra/modules/web-sites.bicep @@ -49,6 +49,9 @@ param storageAccountRequired bool = false @description('Optional. Enable monitoring and logging configuration.') param enableMonitoring bool = false +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + @description('Optional. Azure Resource Manager ID of the Virtual network and subnet to be joined by Regional VNET Integration.') param virtualNetworkSubnetId string? @@ -65,7 +68,7 @@ param vnetRouteAllEnabled bool = false param scmSiteAlsoStopped bool = false @description('Optional. The site config object.') -param siteConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.siteConfig = { +param siteConfig resourceInput<'Microsoft.Web/sites@2025-03-01'>.properties.siteConfig = { alwaysOn: true minTlsVersion: '1.2' ftpsState: 'FtpsOnly' @@ -75,7 +78,7 @@ param siteConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.site param configs appSettingsConfigType[]? @description('Optional. The Function App configuration object.') -param functionAppConfig resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.functionAppConfig? +param functionAppConfig resourceInput<'Microsoft.Web/sites@2025-03-01'>.properties.functionAppConfig? import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' @description('Optional. Configuration details for private endpoints.') @@ -103,7 +106,7 @@ param clientCertExclusionPaths string? param clientCertMode string = 'Optional' @description('Optional. If specified during app creation, the app is cloned from a source app.') -param cloningInfo resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.cloningInfo? +param cloningInfo resourceInput<'Microsoft.Web/sites@2025-03-01'>.properties.cloningInfo? @description('Optional. Size of the function container.') param containerSize int? @@ -115,7 +118,7 @@ param dailyMemoryTimeQuota int? param enabled bool = true @description('Optional. Hostname SSL states are used to manage the SSL bindings for app\'s hostnames.') -param hostNameSslStates resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.hostNameSslStates? +param hostNameSslStates resourceInput<'Microsoft.Web/sites@2025-03-01'>.properties.hostNameSslStates? @description('Optional. Hyper-V sandbox.') param hyperV bool = false @@ -141,7 +144,7 @@ param publicNetworkAccess string? param e2eEncryptionEnabled bool? @description('Optional. Property to configure various DNS related settings for a site.') -param dnsConfiguration resourceInput<'Microsoft.Web/sites@2024-04-01'>.properties.dnsConfiguration? +param dnsConfiguration resourceInput<'Microsoft.Web/sites@2025-03-01'>.properties.dnsConfiguration? @description('Optional. Specifies the scope of uniqueness for the default hostname during resource creation.') @allowed([ @@ -167,7 +170,14 @@ var identity = !empty(managedIdentities) } : null -resource app 'Microsoft.Web/sites@2024-04-01' = { +// Merge vnet properties into siteConfig (these properties moved from top-level to siteConfig in newer API versions) +var mergedSiteConfig = union(siteConfig, { + vnetRouteAllEnabled: vnetRouteAllEnabled + vnetImagePullEnabled: vnetImagePullEnabled + vnetContentShareEnabled: vnetContentShareEnabled +}) + +resource app 'Microsoft.Web/sites@2025-03-01' = { name: name location: location kind: kind @@ -186,7 +196,7 @@ resource app 'Microsoft.Web/sites@2024-04-01' = { storageAccountRequired: storageAccountRequired keyVaultReferenceIdentity: keyVaultAccessIdentityResourceId virtualNetworkSubnetId: virtualNetworkSubnetId - siteConfig: siteConfig + siteConfig: mergedSiteConfig functionAppConfig: functionAppConfig clientCertEnabled: clientCertEnabled clientCertExclusionPaths: clientCertExclusionPaths @@ -201,9 +211,6 @@ resource app 'Microsoft.Web/sites@2024-04-01' = { publicNetworkAccess: !empty(publicNetworkAccess) ? any(publicNetworkAccess) : (!empty(privateEndpoints) ? 'Disabled' : 'Enabled') - vnetContentShareEnabled: vnetContentShareEnabled - vnetImagePullEnabled: vnetImagePullEnabled - vnetRouteAllEnabled: vnetRouteAllEnabled scmSiteAlsoStopped: scmSiteAlsoStopped endToEndEncryptionEnabled: e2eEncryptionEnabled dnsConfiguration: dnsConfiguration @@ -259,7 +266,7 @@ resource app_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-0 } ] -module app_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.0' = [ +module app_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.1' = [ for (privateEndpoint, index) in (privateEndpoints ?? []): { name: '${uniqueString(deployment().name, location)}-app-PrivateEndpoint-${index}' scope: resourceGroup( @@ -296,7 +303,7 @@ module app_privateEndpoints 'br/public:avm/res/network/private-endpoint:0.11.0' ] : null subnetResourceId: privateEndpoint.subnetResourceId - enableTelemetry: false + enableTelemetry: enableTelemetry location: privateEndpoint.?location ?? reference( split(privateEndpoint.subnetResourceId, '/subnets/')[0], '2020-06-01', diff --git a/infra/modules/web-sites.config.bicep b/infra/modules/web-sites.config.bicep index 34ff9e4c9..91d935d6d 100644 --- a/infra/modules/web-sites.config.bicep +++ b/infra/modules/web-sites.config.bicep @@ -96,16 +96,16 @@ resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing scope: resourceGroup(split(applicationInsightResourceId!, '/')[2], split(applicationInsightResourceId!, '/')[4]) } -resource storageAccount 'Microsoft.Storage/storageAccounts@2024-01-01' existing = if (!empty(storageAccountResourceId)) { +resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' existing = if (!empty(storageAccountResourceId)) { name: last(split(storageAccountResourceId!, '/')) scope: resourceGroup(split(storageAccountResourceId!, '/')[2], split(storageAccountResourceId!, '/')[4]) } -resource app 'Microsoft.Web/sites@2023-12-01' existing = { +resource app 'Microsoft.Web/sites@2025-03-01' existing = { name: appName } -resource config 'Microsoft.Web/sites/config@2024-04-01' = { +resource config 'Microsoft.Web/sites/config@2025-03-01' = { parent: app #disable-next-line BCP225 name: name diff --git a/scripts/post_deploy.py b/scripts/post_deploy.py index 846cb54be..d1eebb3f3 100644 --- a/scripts/post_deploy.py +++ b/scripts/post_deploy.py @@ -36,11 +36,13 @@ import argparse import asyncio import base64 +import json import os +import subprocess import sys from dataclasses import dataclass from pathlib import Path -from typing import Dict +from typing import Dict, Optional try: import httpx @@ -103,6 +105,110 @@ def print_warning(text: str): print(f"{Colors.YELLOW}⚠ {text}{Colors.END}") +def get_values_from_az_deployment(resource_group: str) -> Optional[Dict[str, str]]: + """Get deployment output values from Azure using az CLI.""" + print_step("Getting values from Azure deployment outputs...") + + try: + # First, try to get deployment name from resource group tags + result = subprocess.run( + ["az", "group", "show", "--name", resource_group, "--query", "tags.DeploymentName", "-o", "tsv"], + capture_output=True, + text=True, + check=True, + shell=(os.name == "nt") # Required on Windows to find az.cmd + ) + deployment_name = result.stdout.strip() + + # If no tag found, try to find the most recent successful deployment + if not deployment_name or deployment_name == "None": + print_warning("No DeploymentName tag found on resource group") + print_step("Searching for most recent successful deployment...") + + result = subprocess.run( + ["az", "deployment", "group", "list", + "--resource-group", resource_group, + "--query", "[?properties.provisioningState=='Succeeded'] | sort_by(@, &properties.timestamp) | [-1].name", + "-o", "tsv"], + capture_output=True, + text=True, + check=True, + shell=(os.name == "nt") # Required on Windows to find az.cmd + ) + deployment_name = result.stdout.strip() + + if not deployment_name or deployment_name == "None": + print_warning("No successful deployments found in resource group") + return None + + print(f" Found deployment: {deployment_name}") + else: + print(f" Deployment Name (from tag): {deployment_name}") + + # Get deployment outputs + print_step("Fetching deployment outputs...") + result = subprocess.run( + ["az", "deployment", "group", "show", + "--name", deployment_name, + "--resource-group", resource_group, + "--query", "properties.outputs", + "-o", "json"], + capture_output=True, + text=True, + check=True, + shell=(os.name == "nt") # Required on Windows to find az.cmd + ) + + outputs = json.loads(result.stdout) + + # Build a case-insensitive lookup map for output keys. + # ARM/Bicep preserve output names as authored in the template; this map lets + # us perform lookups without worrying about casing in our code. + outputs_lower = {k.lower(): v for k, v in outputs.items()} + + # Extract values from deployment outputs + # The outputs are in format: {"keyName": {"type": "String", "value": "actual-value"}} + def extract_value(key: str, fallback_key: str = "") -> str: + """Extract value from deployment outputs, trying fallback key if primary not found.""" + key_lower = key.lower() + if key_lower in outputs_lower and "value" in outputs_lower[key_lower]: + return outputs_lower[key_lower]["value"] + if fallback_key: + fb_lower = fallback_key.lower() + if fb_lower in outputs_lower and "value" in outputs_lower[fb_lower]: + return outputs_lower[fb_lower]["value"] + return "" + + values = { + "app_service": extract_value("appServiceName", "APP_SERVICE_NAME"), + "storage_account": extract_value("azureBlobAccountName", "AZURE_BLOB_ACCOUNT_NAME"), + "cosmos_account": extract_value("cosmosDbAccountName", "COSMOSDB_ACCOUNT_NAME"), + "search_service": extract_value("aiSearchServiceName", "AI_SEARCH_SERVICE_NAME"), + } + + # Filter out empty values + values = {k: v for k, v in values.items() if v} + + if values: + print_success(f"Retrieved {len(values)} values from deployment outputs") + for k, v in values.items(): + print(f" {k}: {v}") + else: + print_warning("No matching values found in deployment outputs") + + return values + + except subprocess.CalledProcessError as e: + print_warning(f"Failed to query Azure deployment: {e.stderr if e.stderr else str(e)}") + return None + except json.JSONDecodeError as e: + print_warning(f"Failed to parse deployment outputs: {e}") + return None + except Exception as e: + print_warning(f"Error getting deployment values: {e}") + return None + + def discover_resources(resource_group: str, app_name: str, storage_account: str, cosmos_account: str, search_service: str, api_key: str = "") -> ResourceConfig: """Build resource configuration from provided values (no Azure CLI required).""" print_step("Configuring Azure resources...") @@ -144,7 +250,10 @@ async def check_admin_api_health(config: ResourceConfig) -> bool: async with httpx.AsyncClient(timeout=30.0) as client: try: - response = await client.get(f"{config.app_url}/api/admin/health") + response = await client.get( + f"{config.app_url}/api/admin/health", + headers=get_api_headers(config) + ) if response.status_code == 200: print_success("Admin API is healthy") return True @@ -500,7 +609,7 @@ async def main(): args = parser.parse_args() - # Get values from args or environment variables (args take precedence) + # Priority 1: Command line arguments resource_group = args.resource_group or os.environ.get("RESOURCE_GROUP_NAME", "") app_name = args.app_name or os.environ.get("APP_SERVICE_NAME", "") storage_account = args.storage_account or os.environ.get("AZURE_BLOB_ACCOUNT_NAME", "") @@ -508,9 +617,39 @@ async def main(): search_service = args.search_service or os.environ.get("AI_SEARCH_SERVICE_NAME", "") api_key = args.api_key or os.environ.get("ADMIN_API_KEY", "") + # Priority 2: If resource group is provided but other values are missing, try deployment outputs + if resource_group and (not app_name or not storage_account or not cosmos_account or not search_service): + print() + deployment_values = get_values_from_az_deployment(resource_group) + if deployment_values: + # Use deployment values only for missing parameters + app_name = app_name or deployment_values.get("app_service", "") + storage_account = storage_account or deployment_values.get("storage_account", "") + cosmos_account = cosmos_account or deployment_values.get("cosmos_account", "") + search_service = search_service or deployment_values.get("search_service", "") + else: + print_warning("Could not retrieve values from deployment outputs") + # Validate required values are present - if not resource_group or not app_name or not storage_account or not cosmos_account or not search_service: - print_error("Missing required resource names. Provide via arguments or set environment variables: RESOURCE_GROUP_NAME (--resource-group), APP_SERVICE_NAME (--app-name), AZURE_BLOB_ACCOUNT_NAME (--storage-account), COSMOSDB_ACCOUNT_NAME (--cosmos-account), AI_SEARCH_SERVICE_NAME (--search-service)") + if not resource_group: + print_error("Resource group is required. Provide via --resource-group argument or RESOURCE_GROUP_NAME environment variable.") + sys.exit(1) + + if not app_name or not storage_account or not cosmos_account or not search_service: + print_error("Missing required resource names. Please provide via:") + print(" 1. Command line arguments (--app-name, --storage-account, --cosmos-account, --search-service)") + print(" 2. Environment variables (APP_SERVICE_NAME, AZURE_BLOB_ACCOUNT_NAME, COSMOSDB_ACCOUNT_NAME, AI_SEARCH_SERVICE_NAME)") + print(" 3. Azure deployment outputs (automatic if resource group has DeploymentName tag)") + print() + print("Missing values:") + if not app_name: + print(" - App Service name") + if not storage_account: + print(" - Storage Account name") + if not cosmos_account: + print(" - Cosmos DB Account name") + if not search_service: + print(" - AI Search Service name") sys.exit(1) print_header("Content Generation Solution Accelerator - Post Deployment") diff --git a/src/app/frontend/package-lock.json b/src/app/frontend/package-lock.json index f1f51db9b..59a539864 100644 --- a/src/app/frontend/package-lock.json +++ b/src/app/frontend/package-lock.json @@ -61,6 +61,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -863,9 +864,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -908,6 +909,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -2525,9 +2527,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -2655,9 +2657,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -2669,9 +2671,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -2683,9 +2685,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -2697,9 +2699,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -2711,9 +2713,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -2725,9 +2727,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -2739,9 +2741,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -2753,9 +2755,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -2767,9 +2769,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -2781,9 +2783,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -2795,9 +2797,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -2809,9 +2825,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -2823,9 +2853,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -2837,9 +2867,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -2851,9 +2881,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -2878,9 +2908,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -2891,10 +2921,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -2906,9 +2950,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -2920,9 +2964,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -2934,9 +2978,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -2948,9 +2992,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -3069,6 +3113,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -3084,6 +3129,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3094,6 +3140,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -3151,6 +3198,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -3337,6 +3385,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3355,9 +3404,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3484,6 +3533,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -3757,7 +3807,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-autoplay": { "version": "8.6.0", @@ -3849,6 +3900,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3964,9 +4016,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -4176,9 +4228,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -4261,9 +4313,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5316,13 +5368,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5613,6 +5665,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -5625,6 +5678,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5757,9 +5811,9 @@ } }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -5773,35 +5827,38 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -6056,6 +6113,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6147,6 +6205,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6346,6 +6405,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6439,6 +6499,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/src/backend/app.py b/src/backend/app.py index c79100501..550b1e1a3 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -15,6 +15,7 @@ from quart import Quart, request, jsonify, Response from quart_cors import cors +from opentelemetry import trace from settings import app_settings from models import CreativeBrief, Product @@ -24,6 +25,9 @@ from services.title_service import get_title_service from services.routing_service import get_routing_service, Intent, ConversationState from api.admin import admin_bp +from azure.monitor.opentelemetry import configure_azure_monitor +from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware +from event_utils import track_event_if_configured # In-memory task storage for generation tasks # In production, this should be replaced with Redis or similar @@ -31,22 +35,91 @@ _active_regenerations: Dict[str, Dict[str, Any]] = {} -# Configure logging +logging_settings = app_settings.logging +# Configure logging based on environment variables logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" + level=logging_settings.get_basic_log_level(), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + force=True ) -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) +azure_log_level = logging_settings.get_package_log_level() +for logger_name in logging_settings.logging_packages or []: + logging.getLogger(logger_name).setLevel(azure_log_level) +logging.info( + f"Logging configured - Basic: {logging_settings.basic_logging_level}, " + f"Azure packages: {logging_settings.package_logging_level}, " + f"Packages: {logging_settings.logging_packages}" +) + logger = logging.getLogger(__name__) # Create Quart app app = Quart(__name__) app = cors(app, allow_origin="*") +# Check if the Application Insights connection string is set in the environment variables +appinsights_connection_string = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING") +if appinsights_connection_string: + # Configure Application Insights if the connection string is found + configure_azure_monitor( + connection_string=appinsights_connection_string, + enable_live_metrics=False, + enable_performance_counters=False, + ) + # Suppress verbose Azure SDK INFO logs from App Insights + # WARNING/ERROR/CRITICAL from these loggers still come through + logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) + logging.getLogger("azure.monitor.opentelemetry.exporter").setLevel(logging.WARNING) + logging.getLogger("azure.identity").setLevel(logging.WARNING) + logging.getLogger("azure.cosmos").setLevel(logging.WARNING) + logging.getLogger("api.admin").setLevel(logging.WARNING) + logging.getLogger("httpx").setLevel(logging.WARNING) + # Apply ASGI middleware for request tracing (Quart is not auto-instrumented by configure_azure_monitor) + # Exclude health probes, post-deploy admin calls, and polling endpoints from telemetry + app.asgi_app = OpenTelemetryMiddleware( + app.asgi_app, + exclude_spans=["receive", "send"], + excluded_urls="health,api/admin,api/generate/status", + ) + logger.info("Application Insights configured with the provided connection string") +else: + # Log a warning if the connection string is not found + logger.warning("No Application Insights connection string found. Skipping configuration") + # Register blueprints app.register_blueprint(admin_bp) +@app.before_request +async def set_conversation_context(): + """Attach conversation_id and user_id to the current OTel span for App Insights.""" + conversation_id = "" + user_id = "" + + # 1. Extract from JSON body (POST requests) + if request.content_type and "json" in request.content_type: + data = await request.get_json(silent=True) + if data and isinstance(data, dict): + conversation_id = data.get("conversation_id", "") + user_id = data.get("user_id", "") + + # 2. Extract from URL path parameters (e.g. /api/conversations/) + if not conversation_id and request.view_args: + conversation_id = request.view_args.get("conversation_id", "") + + # 3. Extract from query parameters (e.g. ?conversation_id=xxx) + if not conversation_id: + conversation_id = request.args.get("conversation_id", "") + + if not user_id: + user_id = request.args.get("user_id", "") or request.headers.get("X-Ms-Client-Principal-Id", "anonymous") + + span = trace.get_current_span() + if span.is_recording(): + span.set_attribute("conversation_id", conversation_id) + span.set_attribute("user_id", user_id) + + # ==================== Authentication Helper ==================== def get_authenticated_user(): @@ -98,6 +171,8 @@ async def handle_chat(): selected_products = data.get("selected_products", []) brief_data = data.get("brief", {}) + track_event_if_configured("Chat_Request_Received", {"conversation_id": conversation_id, "user_id": user_id}) + # Get services routing_service = get_routing_service() orchestrator = get_orchestrator() @@ -111,7 +186,7 @@ async def handle_chat(): if conversation: state = routing_service.derive_state_from_conversation(conversation) except Exception as e: - logger.warning(f"Failed to get conversation state: {e}") + logger.exception(f"Failed to get conversation state: {e}") has_generated_content_flag = data.get("has_generated_content", False) if has_generated_content_flag: @@ -212,6 +287,7 @@ async def handle_chat(): except Exception as e: logger.exception(f"Error handling message: {e}") + track_event_if_configured("Error_Chat_Handler", {"conversation_id": conversation_id, "user_id": user_id, "error": str(e)}) return jsonify({ "action_type": "error", "message": f"An error occurred: {str(e)}", @@ -230,6 +306,8 @@ async def _handle_parse_brief( ) -> Response: """Handle parsing a new brief from user message.""" + track_event_if_configured("Brief_Parse_Request", {"conversation_id": conversation_id, "user_id": user_id}) + generated_title = None # Save user message @@ -255,12 +333,13 @@ async def _handle_parse_brief( generated_title=generated_title ) except Exception as e: - logger.warning(f"Failed to save message to CosmosDB: {e}") + logger.exception(f"Failed to save message to CosmosDB: {e}") # Parse the brief brief, questions, blocked = await orchestrator.parse_brief(message) if blocked: + track_event_if_configured("Error_RAI_Check_Failed", {"conversation_id": conversation_id, "user_id": user_id, "status": "Brief parse blocked by RAI"}) # Content was blocked by RAI - save refusal as assistant response try: cosmos_service = await get_cosmos_service() @@ -275,7 +354,7 @@ async def _handle_parse_brief( } ) except Exception as e: - logger.warning(f"Failed to save RAI response to CosmosDB: {e}") + logger.exception(f"Failed to save RAI response to CosmosDB: {e}") return jsonify({ "action_type": "rai_blocked", @@ -302,7 +381,7 @@ async def _handle_parse_brief( } ) except Exception as e: - logger.warning(f"Failed to save clarification to CosmosDB: {e}") + logger.exception(f"Failed to save clarification to CosmosDB: {e}") # Save partial brief to conversation so it can be confirmed later try: @@ -314,7 +393,7 @@ async def _handle_parse_brief( brief=brief ) except Exception as e: - logger.warning(f"Failed to save partial brief: {e}") + logger.exception(f"Failed to save partial brief: {e}") return jsonify({ "action_type": "clarification_needed", @@ -348,7 +427,7 @@ async def _handle_parse_brief( } ) except Exception as e: - logger.warning(f"Failed to save brief to CosmosDB: {e}") + logger.exception(f"Failed to save brief to CosmosDB: {e}") return jsonify({ "action_type": "brief_parsed", @@ -371,8 +450,11 @@ async def _handle_confirm_brief( try: brief = CreativeBrief(**brief_data) except Exception as e: + track_event_if_configured("Error_Brief_Invalid_Format", {"conversation_id": conversation_id, "user_id": user_id, "error": str(e)}) return jsonify({"error": f"Invalid brief format: {str(e)}"}), 400 + track_event_if_configured("Brief_Confirmed", {"conversation_id": conversation_id, "user_id": user_id}) + try: cosmos_service = await get_cosmos_service() @@ -396,7 +478,7 @@ async def _handle_confirm_brief( metadata={"status": "brief_confirmed", "brief_confirmed": True} ) except Exception as e: - logger.warning(f"Failed to save confirmed brief: {e}") + logger.exception(f"Failed to save confirmed brief: {e}") return jsonify({ "action_type": "brief_confirmed", @@ -417,6 +499,8 @@ async def _handle_refine_brief( ) -> Response: """Handle brief refinement based on user feedback.""" + track_event_if_configured("Brief_Refine_Request", {"conversation_id": conversation_id, "user_id": user_id}) + # Get existing brief if available existing_brief = conversation.get("brief") if conversation else None @@ -433,12 +517,13 @@ async def _handle_refine_brief( } ) except Exception as e: - logger.warning(f"Failed to save refinement message: {e}") + logger.exception(f"Failed to save refinement message: {e}") # Use orchestrator to refine the brief brief, questions, blocked = await orchestrator.parse_brief(message) if blocked: + track_event_if_configured("Error_RAI_Check_Failed", {"conversation_id": conversation_id, "user_id": user_id, "status": "Brief refinement blocked by RAI"}) return jsonify({ "action_type": "rai_blocked", "message": questions, @@ -462,7 +547,7 @@ async def _handle_refine_brief( } ) except Exception as e: - logger.warning(f"Failed to save clarification: {e}") + logger.exception(f"Failed to save clarification: {e}") # Merge partial brief with existing brief for confirmation option merged_brief = brief.model_dump() if brief else {} @@ -483,7 +568,7 @@ async def _handle_refine_brief( brief=CreativeBrief(**merged_brief) if merged_brief else None ) except Exception as e: - logger.warning(f"Failed to save merged brief: {e}") + logger.exception(f"Failed to save merged brief: {e}") return jsonify({ "action_type": "clarification_needed", @@ -526,7 +611,7 @@ async def _handle_refine_brief( } ) except Exception as e: - logger.warning(f"Failed to save refined brief: {e}") + logger.exception(f"Failed to save refined brief: {e}") return jsonify({ "action_type": "brief_parsed", @@ -547,6 +632,8 @@ async def _handle_search_products( ) -> Response: """Handle product search/selection via natural language.""" + track_event_if_configured("Product_Selection_Request", {"conversation_id": conversation_id, "user_id": user_id}) + # Save user message try: cosmos_service = await get_cosmos_service() @@ -560,7 +647,7 @@ async def _handle_search_products( } ) except Exception as e: - logger.warning(f"Failed to save search message: {e}") + logger.exception(f"Failed to save search message: {e}") # Get available products from catalog try: @@ -576,7 +663,7 @@ async def _handle_search_products( filename = original_url.split("/")[-1] if "/" in original_url else original_url p["image_url"] = f"/api/product-images/{filename}" except Exception as e: - logger.warning(f"Failed to get products from CosmosDB: {e}") + logger.exception(f"Failed to get products from CosmosDB: {e}") available_products = [] # Use orchestrator to process the selection request @@ -600,7 +687,7 @@ async def _handle_search_products( } ) except Exception as e: - logger.warning(f"Failed to save search response: {e}") + logger.exception(f"Failed to save search response: {e}") return jsonify({ "action_type": "products_found", @@ -626,6 +713,7 @@ async def _handle_generate_content( try: brief = CreativeBrief(**brief_data) except Exception as e: + track_event_if_configured("Error_Generation_Invalid_Brief", {"conversation_id": conversation_id, "user_id": user_id, "error": str(e)}) return jsonify({ "action_type": "error", "message": f"Invalid brief format: {str(e)}", @@ -645,6 +733,8 @@ async def _handle_generate_content( "error": None } + track_event_if_configured("Generation_Started", {"task_id": task_id, "conversation_id": conversation_id, "user_id": user_id, "generate_images": str(generate_images)}) + # Save user request try: cosmos_service = await get_cosmos_service() @@ -659,7 +749,7 @@ async def _handle_generate_content( } ) except Exception as e: - logger.warning(f"Failed to save generation request: {e}") + logger.exception(f"Failed to save generation request: {e}") # Start background task asyncio.create_task(_run_generation_task( @@ -694,6 +784,9 @@ async def _handle_modify_image( selected_products: list = None ) -> Response: """Handle image modification requests.""" + + track_event_if_configured("Regeneration_Request", {"conversation_id": conversation_id, "user_id": user_id}) + # Get products from frontend (frontend handles product detection) # This matches the original implementation where frontend detected product changes frontend_products = selected_products or [] @@ -708,7 +801,7 @@ async def _handle_modify_image( conversation = fresh_conversation logger.info(f"Fetched fresh conversation data for {conversation_id}") except Exception as e: - logger.warning(f"Failed to fetch fresh conversation, using stale data: {e}") + logger.exception(f"Failed to fetch fresh conversation, using stale data: {e}") # Save user message try: @@ -723,12 +816,13 @@ async def _handle_modify_image( } ) except Exception as e: - logger.warning(f"Failed to save image modification request: {e}") + logger.exception(f"Failed to save image modification request: {e}") # Get existing generated content generated_content = conversation.get("generated_content") if conversation else None if not generated_content: + track_event_if_configured("Error_Regeneration_No_Content", {"conversation_id": conversation_id, "user_id": user_id}) return jsonify({ "action_type": "error", "message": "No generated content found. Please generate content first.", @@ -756,6 +850,7 @@ async def _handle_modify_image( brief = None if not brief: + track_event_if_configured("Error_Regeneration_No_Brief", {"conversation_id": conversation_id, "user_id": user_id}) return jsonify({ "action_type": "error", "message": "No brief found. Please create and confirm a brief first.", @@ -836,6 +931,7 @@ async def _run_regeneration_task( # Check for RAI block if response.get("rai_blocked"): + track_event_if_configured("Error_RAI_Check_Failed", {"conversation_id": conversation_id, "user_id": user_id, "status": "Regeneration blocked by RAI"}) _generation_tasks[task_id]["status"] = "failed" _generation_tasks[task_id]["error"] = response.get("error", "Request blocked by content safety") return @@ -862,7 +958,7 @@ async def _run_regeneration_task( response["image_url"] = f"/api/images/{conversation_id}/{filename}" del response["image_base64"] except Exception as e: - logger.warning(f"Failed to save regenerated image to blob: {e}") + logger.exception(f"Failed to save regenerated image to blob: {e}") # Save assistant response existing_content = {} @@ -945,7 +1041,7 @@ async def _run_regeneration_task( ) logger.info(f"Updated brief visual_guidelines with modification: {modification_request}") except Exception as e: - logger.warning(f"Failed to save regeneration response to CosmosDB: {e}") + logger.exception(f"Failed to save regeneration response to CosmosDB: {e}") # Store result (use updated text_content if we replaced product name) _generation_tasks[task_id]["status"] = "completed" @@ -958,6 +1054,7 @@ async def _run_regeneration_task( "selected_products": products_data, "updated_brief": updated_brief_dict, # Include updated brief for frontend } + track_event_if_configured("Regeneration_Completed", {"task_id": task_id, "conversation_id": conversation_id, "user_id": user_id}) # Clear active regeneration marker (only if it's still our task) active_info = _active_regenerations.get(conversation_id, {}) @@ -969,6 +1066,7 @@ async def _run_regeneration_task( logger.exception(f"Error in regeneration task {task_id}: {e}") _generation_tasks[task_id]["status"] = "failed" _generation_tasks[task_id]["error"] = str(e) + track_event_if_configured("Error_Regeneration_Failed", {"task_id": task_id, "conversation_id": conversation_id, "user_id": user_id, "error": str(e)}) # Clear active regeneration marker on error too active_info = _active_regenerations.get(conversation_id, {}) if active_info.get("task_id") == task_id: @@ -981,6 +1079,8 @@ async def _handle_start_over( ) -> Response: """Handle start over request - clears the current session.""" + track_event_if_configured("Session_Reset", {"conversation_id": conversation_id, "user_id": user_id}) + # For start over, we create a new conversation new_conversation_id = str(uuid.uuid4()) @@ -1002,6 +1102,8 @@ async def _handle_general_chat( ) -> Response: """Handle general chat messages.""" + track_event_if_configured("General_Chat_Request", {"conversation_id": conversation_id, "user_id": user_id}) + # Save user message try: cosmos_service = await get_cosmos_service() @@ -1026,7 +1128,7 @@ async def _handle_general_chat( generated_title=generated_title ) except Exception as e: - logger.warning(f"Failed to save message: {e}") + logger.exception(f"Failed to save message: {e}") # For non-streaming response, collect orchestrator output response_content = "" @@ -1054,7 +1156,7 @@ async def _handle_general_chat( } ) except Exception as e: - logger.warning(f"Failed to save response: {e}") + logger.exception(f"Failed to save response: {e}") return jsonify({ "action_type": "chat_response", @@ -1108,7 +1210,7 @@ async def _run_generation_task(task_id: str, brief: CreativeBrief, products_data response["image_blob_url"] = blob_url # Include the original blob URL del response["image_base64"] except Exception as e: - logger.warning(f"Failed to save image to blob: {e}") + logger.exception(f"Failed to save image to blob: {e}") # Save to CosmosDB try: @@ -1140,11 +1242,12 @@ async def _run_generation_task(task_id: str, brief: CreativeBrief, products_data generated_content=generated_content_to_save ) except Exception as e: - logger.warning(f"Failed to save generated content to CosmosDB: {e}") + logger.exception(f"Failed to save generated content to CosmosDB: {e}") _generation_tasks[task_id]["status"] = "completed" _generation_tasks[task_id]["result"] = response _generation_tasks[task_id]["completed_at"] = datetime.now(timezone.utc).isoformat() + track_event_if_configured("Generation_Completed", {"task_id": task_id, "conversation_id": conversation_id, "user_id": user_id}) logger.info(f"Task {task_id} marked as completed") except Exception as e: @@ -1152,6 +1255,7 @@ async def _run_generation_task(task_id: str, brief: CreativeBrief, products_data _generation_tasks[task_id]["status"] = "failed" _generation_tasks[task_id]["error"] = str(e) _generation_tasks[task_id]["completed_at"] = datetime.now(timezone.utc).isoformat() + track_event_if_configured("Error_Generation_Failed", {"task_id": task_id, "conversation_id": conversation_id, "user_id": user_id, "error": str(e)}) @app.route("/api/generate/start", methods=["POST"]) @@ -1187,6 +1291,7 @@ async def start_generation(): try: brief = CreativeBrief(**brief_data) except Exception as e: + track_event_if_configured("Error_Generation_Invalid_Brief", {"conversation_id": conversation_id, "user_id": user_id, "error": str(e)}) return jsonify({"error": f"Invalid brief format: {str(e)}"}), 400 # Create task ID @@ -1215,7 +1320,7 @@ async def start_generation(): } ) except Exception as e: - logger.warning(f"Failed to save generation request to CosmosDB: {e}") + logger.exception(f"Failed to save generation request to CosmosDB: {e}") # Start background task asyncio.create_task(_run_generation_task( @@ -1229,6 +1334,8 @@ async def start_generation(): logger.info(f"Started generation task {task_id} for conversation {conversation_id}") + track_event_if_configured("Generation_Started", {"task_id": task_id, "conversation_id": conversation_id, "user_id": user_id, "generate_images": str(generate_images)}) + return jsonify({ "task_id": task_id, "status": "pending", @@ -1555,9 +1662,10 @@ async def delete_conversation(conversation_id: str): try: cosmos_service = await get_cosmos_service() await cosmos_service.delete_conversation(conversation_id, user_id) + track_event_if_configured("Conversation_Deleted", {"conversation_id": conversation_id, "user_id": user_id}) return jsonify({"success": True, "message": "Conversation deleted"}) except Exception as e: - logger.warning(f"Failed to delete conversation: {e}") + logger.exception(f"Failed to delete conversation: {e}") return jsonify({"error": "Failed to delete conversation"}), 500 @@ -1586,10 +1694,11 @@ async def update_conversation(conversation_id: str): cosmos_service = await get_cosmos_service() result = await cosmos_service.rename_conversation(conversation_id, user_id, new_title) if result: + track_event_if_configured("Conversation_Renamed", {"conversation_id": conversation_id, "user_id": user_id}) return jsonify({"success": True, "message": "Conversation renamed", "title": new_title}) return jsonify({"error": "Conversation not found"}), 404 except Exception as e: - logger.warning(f"Failed to rename conversation: {e}") + logger.exception(f"Failed to rename conversation: {e}") return jsonify({"error": "Failed to rename conversation"}), 500 @@ -1606,13 +1715,14 @@ async def delete_all_conversations(): try: cosmos_service = await get_cosmos_service() deleted_count = await cosmos_service.delete_all_conversations(user_id) + track_event_if_configured("Conversations_All_Deleted", {"user_id": user_id, "deleted_count": str(deleted_count)}) return jsonify({ "success": True, "message": f"Deleted {deleted_count} conversations", "deleted_count": deleted_count }) except Exception as e: - logger.warning(f"Failed to delete all conversations: {e}") + logger.exception(f"Failed to delete all conversations: {e}") return jsonify({"error": "Failed to delete conversations"}), 500 @@ -1665,13 +1775,13 @@ async def startup(): await get_cosmos_service() logger.info("CosmosDB service initialized") except Exception as e: - logger.warning(f"CosmosDB service initialization failed (may be firewall): {e}") + logger.exception(f"CosmosDB service initialization failed (may be firewall): {e}") try: await get_blob_service() logger.info("Blob storage service initialized") except Exception as e: - logger.warning(f"Blob storage service initialization failed: {e}") + logger.exception(f"Blob storage service initialization failed: {e}") logger.info("Application startup complete") diff --git a/src/backend/event_utils.py b/src/backend/event_utils.py new file mode 100644 index 000000000..6bd09fab0 --- /dev/null +++ b/src/backend/event_utils.py @@ -0,0 +1,11 @@ +import logging +import os +from azure.monitor.events.extension import track_event + + +def track_event_if_configured(event_name: str, event_data: dict): + connection_string = os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING") + if connection_string: + track_event(event_name, event_data) + else: + logging.warning(f"Skipping track_event for {event_name} as Application Insights is not configured") diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index e1471c19e..fda72578f 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -26,6 +26,11 @@ openai>=1.45.0 # HTTP Client (for Foundry direct API calls) httpx>=0.27.0 +# Monitoring / Telemetry +azure-monitor-opentelemetry>=1.6.0 +azure-monitor-events-extension>=0.1.0 +opentelemetry-instrumentation-asgi>=0.48b0 + # Data Validation pydantic>=2.8.0 pydantic-settings>=2.4.0 diff --git a/src/backend/settings.py b/src/backend/settings.py index c28a72e70..e5e4a81ae 100644 --- a/src/backend/settings.py +++ b/src/backend/settings.py @@ -6,10 +6,11 @@ and compliance validation. """ +import logging import os -from typing import List, Optional +from typing import List, Literal, Optional -from pydantic import BaseModel, Field, model_validator +from pydantic import BaseModel, Field, field_validator, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict from typing_extensions import Self @@ -25,6 +26,32 @@ def parse_comma_separated(value: str) -> List[str]: return [] +class _LoggingSettings(BaseSettings): + """Logging configuration settings.""" + model_config = SettingsConfigDict( + env_prefix="AZURE_", env_file=DOTENV_PATH, extra="ignore", env_ignore_empty=True + ) + + basic_logging_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO" + package_logging_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "WARNING" + logging_packages: Optional[List[str]] = [] + + @field_validator("logging_packages", mode="before") + @classmethod + def split_logging_packages(cls, packages) -> Optional[List[str]]: + if isinstance(packages, str) and len(packages.strip()) > 0: + return [pkg.strip() for pkg in packages.split(",") if pkg.strip()] + return None + + def get_basic_log_level(self) -> int: + """Convert string log level to logging constant.""" + return getattr(logging, self.basic_logging_level.upper()) + + def get_package_log_level(self) -> int: + """Convert string package log level to logging constant.""" + return getattr(logging, self.package_logging_level.upper()) + + class _UiSettings(BaseSettings): """UI configuration settings.""" model_config = SettingsConfigDict( @@ -436,6 +463,7 @@ class _AppSettings(BaseModel): azure_openai: _AzureOpenAISettings = _AzureOpenAISettings() ai_foundry: _AIFoundrySettings = _AIFoundrySettings() brand_guidelines: _BrandGuidelinesSettings = _BrandGuidelinesSettings() + logging: _LoggingSettings = _LoggingSettings() ui: Optional[_UiSettings] = _UiSettings() # Constructed properties diff --git a/src/tests/services/test_orchestrator.py b/src/tests/services/test_orchestrator.py index 4927c0556..1c79b00f2 100644 --- a/src/tests/services/test_orchestrator.py +++ b/src/tests/services/test_orchestrator.py @@ -3,7 +3,8 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -import orchestrator as orch_module + +import orchestrator from orchestrator import (_HARMFUL_PATTERNS_COMPILED, _SYSTEM_PROMPT_PATTERNS_COMPILED, PLANNING_INSTRUCTIONS, RAI_HARMFUL_CONTENT_RESPONSE, @@ -840,7 +841,7 @@ def test_get_orchestrator_singleton(): mock_builder.return_value = mock_builder_instance # Reset the singleton - orch_module._orchestrator = None + orchestrator._orchestrator = None instance1 = get_orchestrator() instance2 = get_orchestrator() @@ -1517,7 +1518,7 @@ async def test_get_chat_client_foundry_mode(): def test_foundry_not_available(): """Test when Foundry SDK is not available.""" # Check that FOUNDRY_AVAILABLE is defined - assert hasattr(orch_module, 'FOUNDRY_AVAILABLE') + assert hasattr(orchestrator, 'FOUNDRY_AVAILABLE') # Tests for workflow event handling (lines 736-799, 841-895) # Note: These are integration-level tests that verify the workflow event