diff --git a/documentation/DeploymentGuide.md b/documentation/DeploymentGuide.md index 4d2eb2b5e..e52f6a9f6 100644 --- a/documentation/DeploymentGuide.md +++ b/documentation/DeploymentGuide.md @@ -6,7 +6,7 @@ To deploy this solution accelerator, ensure you have access to an [Azure subscri Check the [Azure Products by Region](https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/?products=all®ions=all) page and select a **region** where the following services are available: -- [Azure OpenAI Service](https://learn.microsoft.com/en-us/azure/ai-services/openai/) +- [Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-foundry/) - [Azure Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/) - [Azure Container Registry](https://learn.microsoft.com/en-us/azure/container-registry/) - [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/) @@ -28,6 +28,31 @@ This will allow the scripts to run for the current session without permanently c ## Deployment Options & Steps +### Sandbox or WAF Aligned Deployment Options + +The [`infra`](../infra) folder of the Multi Agent Solution Accelerator contains the [`main.bicep`](../infra/main.bicep) Bicep script, which defines all Azure infrastructure components for this solution. + +By default, the `azd up` command uses the [`main.bicepparam`](../infra/main.bicepparam) file to deploy the solution. This file is pre-configured for a **sandbox environment** — ideal for development and proof-of-concept scenarios, with minimal security and cost controls for rapid iteration. + +For **production deployments**, the repository also provides [`main.waf-aligned.bicepparam`](../infra/main.waf-aligned.bicepparam), which applies a [Well-Architected Framework (WAF) aligned](https://learn.microsoft.com/en-us/azure/well-architected/) configuration. This option enables additional Azure best practices for reliability, security, cost optimization, operational excellence, and performance efficiency, such as: + +- Enhanced network security (e.g., Network protection with private endpoints) +- Stricter access controls and managed identities +- Logging, monitoring, and diagnostics enabled by default +- Resource tagging and cost management recommendations + +**How to choose your deployment configuration:** +- Use the default [`main.bicepparam`](../infra/main.bicepparam) for a sandbox/dev environment. +- For a WAF-aligned, production-ready deployment, copy the contents of [`main.waf-aligned.bicepparam`](../infra/main.waf-aligned.bicepparam) into `main.bicepparam` before running `azd up`. + +> [!TIP] +> Always review and adjust parameter values (such as region, capacity, and security settings) to match your organization’s requirements before deploying. For production, ensure you have sufficient quota and follow the principle of least privilege for all identities and role assignments. + +> [!IMPORTANT] +> The WAF-aligned configuration is under active development. More Azure Well-Architected recommendations will be added in future updates. + +### Deployment Steps + Pick from the options below to see step-by-step instructions for GitHub Codespaces, VS Code Dev Containers, Local Environments, and Bicep deployments. | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/microsoft/Multi-Agent-Custom-Automation-Engine-Solution-Accelerator) | [![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/Multi-Agent-Custom-Automation-Engine-Solution-Accelerator) | diff --git a/infra/bicepconfig.json b/infra/bicepconfig.json new file mode 100644 index 000000000..7d7839f72 --- /dev/null +++ b/infra/bicepconfig.json @@ -0,0 +1,9 @@ +{ + "experimentalFeaturesEnabled": { + "extensibility": true + }, + "extensions": { + "graphV1": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.2.0-preview" // , + // "graphBeta": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/beta:0.2.0-preview" + } + } \ No newline at end of file diff --git a/infra/main.bicep b/infra/main.bicep index 9630ff100..9d76c0d81 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -1,503 +1,1929 @@ -targetScope = 'resourceGroup' - -@allowed([ - 'australiaeast' - 'brazilsouth' - 'canadacentral' - 'canadaeast' - 'eastus' - 'eastus2' - 'francecentral' - 'germanywestcentral' - 'japaneast' - 'koreacentral' - 'northcentralus' - 'norwayeast' - 'polandcentral' - 'southafricanorth' - 'southcentralus' - 'southindia' - 'swedencentral' - 'switzerlandnorth' - 'uaenorth' - 'uksouth' - 'westeurope' - 'westus' - 'westus3' -]) -@description('Location for all Ai services resources. This location can be different from the resource group location.') +metadata name = 'Multi-Agent Custom Automation Engine' +metadata description = 'This module contains the resources required to deploy the Multi-Agent Custom Automation Engine solution accelerator for both Sandbox environments and WAF aligned environments.' + +@description('Optional. The prefix to add in the default names given to all deployed Azure resources.') +@maxLength(19) +param solutionPrefix string = 'macae${uniqueString(deployer().objectId, deployer().tenantId, subscription().subscriptionId, resourceGroup().id)}' + +@description('Required. Location for all Resources except AI Foundry.') +param solutionLocation string = resourceGroup().location + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +// Restricting deployment to only supported Azure OpenAI regions validated with GPT-4o model +@allowed(['australiaeast', 'eastus2', 'francecentral', 'japaneast', 'norwayeast', 'swedencentral', 'uksouth', 'westus']) +@description('Azure OpenAI Location') param azureOpenAILocation string -@minLength(3) -@maxLength(20) -@description('A unique prefix for all resources in this deployment. This should be 3-20 characters long:') -param environmentName string +// @description('Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') +// param AZURE_LOCATION string='' +// param solutionLocation string = empty(AZURE_LOCATION) ? resourceGroup().location -@description('Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default.') -param AZURE_LOCATION string='' -var solutionLocation = empty(AZURE_LOCATION) ? resourceGroup().location : AZURE_LOCATION +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags object = { + app: solutionPrefix + location: solutionLocation +} -var uniqueId = toLower(uniqueString(subscription().id, environmentName, solutionLocation)) -var solutionPrefix = 'ma${padLeft(take(uniqueId, 12), 12, '0')}' +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Log Analytics Workspace resource.') +param logAnalyticsWorkspaceConfiguration logAnalyticsWorkspaceConfigurationType = { + enabled: true + name: 'log-${solutionPrefix}' + location: solutionLocation + sku: 'PerGB2018' + tags: tags + dataRetentionInDays: 365 +} -// Load the abbrevations file required to name the azure resources. -var abbrs = loadJsonContent('./abbreviations.json') +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Application Insights resource.') +param applicationInsightsConfiguration applicationInsightsConfigurationType = { + enabled: true + name: 'appi-${solutionPrefix}' + location: solutionLocation + tags: tags + retentionInDays: 365 +} -@description('Tags to apply to all deployed resources') -param tags object = {} +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Managed Identity resource.') +param userAssignedManagedIdentityConfiguration userAssignedManagedIdentityType = { + enabled: true + name: 'id-${solutionPrefix}' + location: solutionLocation + tags: tags +} -@description('The size of the resources to deploy, defaults to a mini size') -param resourceSize { - gpt4oCapacity: int - containerAppSize: { - cpu: string - memory: string - minReplicas: int - maxReplicas: int - } -} = { - gpt4oCapacity: 1 - containerAppSize: { - cpu: '2.0' - memory: '4.0Gi' - minReplicas: 1 - maxReplicas: 1 - } +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the backend subnet.') +param networkSecurityGroupBackendConfiguration networkSecurityGroupConfigurationType = { + enabled: true + name: 'nsg-backend-${solutionPrefix}' + location: solutionLocation + tags: tags + securityRules: null //Default value set on module configuration } -param capacity int = 140 -var modelVersion = '2024-08-06' -var aiServicesName = '${abbrs.ai.aiServices}${solutionPrefix}' -var deploymentType = 'GlobalStandard' -var gptModelVersion = 'gpt-4o' -var appVersion = 'latest' -var resgistryName = 'biabcontainerreg' -var dockerRegistryUrl = 'https://${resgistryName}.azurecr.io' +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the containers subnet.') +param networkSecurityGroupContainersConfiguration networkSecurityGroupConfigurationType = { + enabled: true + name: 'nsg-containers-${solutionPrefix}' + location: solutionLocation + tags: tags + securityRules: null //Default value set on module configuration +} -@description('URL for frontend docker image') -var backendDockerImageURL = '${resgistryName}.azurecr.io/macaebackend:${appVersion}' -var frontendDockerImageURL = '${resgistryName}.azurecr.io/macaefrontend:${appVersion}' +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the Bastion subnet.') +param networkSecurityGroupBastionConfiguration networkSecurityGroupConfigurationType = { + enabled: true + name: 'nsg-bastion-${solutionPrefix}' + location: solutionLocation + tags: tags + securityRules: null //Default value set on module configuration +} -//var uniqueNameFormat = '${solutionPrefix}-{0}-${uniqueString(resourceGroup().id, solutionPrefix)}' -var aoaiApiVersion = '2025-01-01-preview' +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the administration subnet.') +param networkSecurityGroupAdministrationConfiguration networkSecurityGroupConfigurationType = { + enabled: true + name: 'nsg-administration-${solutionPrefix}' + location: solutionLocation + tags: tags + securityRules: null //Default value set on module configuration +} -resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = { - name: '${abbrs.managementGovernance.logAnalyticsWorkspace}${solutionPrefix}' +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine virtual network resource.') +param virtualNetworkConfiguration virtualNetworkConfigurationType = { + enabled: true + name: 'vnet-${solutionPrefix}' location: solutionLocation tags: tags - properties: { - retentionInDays: 30 - sku: { - name: 'PerGB2018' - } - } + addressPrefixes: null //Default value set on module configuration + subnets: null //Default value set on module configuration } -resource appInsights 'Microsoft.Insights/components@2020-02-02-preview' = { - name: '${abbrs.managementGovernance.applicationInsights}${solutionPrefix}' +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine bastion resource.') +param bastionConfiguration bastionConfigurationType = { + enabled: true + name: 'bas-${solutionPrefix}' location: solutionLocation - kind: 'web' - properties: { - Application_Type: 'web' - WorkspaceResourceId: logAnalytics.id - } + tags: tags + sku: 'Standard' + virtualNetworkResourceId: null //Default value set on module configuration + publicIpResourceName: 'pip-bas${solutionPrefix}' } -var aiModelDeployments = [ - { - name: gptModelVersion - model: gptModelVersion - version: modelVersion - sku: { - name: deploymentType - capacity: capacity - } - raiPolicyName: 'Microsoft.Default' - } -] +@description('Optional. Configuration for the Windows virtual machine.') +param virtualMachineConfiguration virtualMachineConfigurationType = { + enabled: true + name: 'vm${solutionPrefix}' + location: solutionLocation + tags: tags + adminUsername: 'adminuser' + adminPassword: guid(solutionPrefix, subscription().subscriptionId) + vmSize: 'Standard_D2s_v3' + subnetResourceId: null //Default value set on module configuration +} -resource aiServices 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' = { - name: aiServicesName +@description('Optional. The configuration to apply for the AI Foundry AI Services resource.') +param aiFoundryAiServicesConfiguration aiServicesConfigurationType = { + enabled: true + name: 'aisa-${solutionPrefix}' location: azureOpenAILocation - sku: { - name: 'S0' - } - kind: 'AIServices' - properties: { - customSubDomainName: aiServicesName - apiProperties: { - //statisticsEnabled: false - } - disableLocalAuth: true - publicNetworkAccess: 'Enabled' - } + sku: 'S0' + deployments: null //Default value set on module configuration + subnetResourceId: null //Default value set on module configuration + modelCapacity: 140 +} + +@description('Optional. The configuration to apply for the AI Foundry Storage Account resource.') +param aiFoundryStorageAccountConfiguration storageAccountType = { + enabled: true + name: replace('sthub${solutionPrefix}', '-', '') + location: azureOpenAILocation + tags: tags + sku: 'Standard_ZRS' + subnetResourceId: null //Default value set on module configuration +} + +@description('Optional. The configuration to apply for the AI Foundry AI Hub resource.') +param aiFoundryAiHubConfiguration aiHubType = { + enabled: true + name: 'aih-${solutionPrefix}' + location: azureOpenAILocation + sku: 'Basic' + tags: tags + subnetResourceId: null //Default value set on module configuration +} + +@description('Optional. The configuration to apply for the AI Foundry AI Project resource.') +param aiFoundryAiProjectConfiguration aiProjectConfigurationType = { + enabled: true + name: 'aihb-${solutionPrefix}' + location: azureOpenAILocation + sku: 'Basic' + tags: tags +} + +@description('Optional. The configuration to apply for the Cosmos DB Account resource.') +param cosmosDbAccountConfiguration cosmosDbAccountConfigurationType = { + enabled: true + name: 'cosmos-${solutionPrefix}' + location: solutionLocation + tags: tags + subnetResourceId: null //Default value set on module configuration + sqlDatabases: null //Default value set on module configuration } -resource aiServicesDeployments 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [ - for aiModeldeployment in aiModelDeployments: { - parent: aiServices //aiServices_m - name: aiModeldeployment.name - properties: { - model: { - format: 'OpenAI' - name: aiModeldeployment.model - version: aiModeldeployment.version +@description('Optional. The configuration to apply for the Container App Environment resource.') +param containerAppEnvironmentConfiguration containerAppEnvironmentConfigurationType = { + enabled: true + name: 'cae-${solutionPrefix}' + location: solutionLocation + tags: tags + subnetResourceId: null //Default value set on module configuration +} + +@description('Optional. The configuration to apply for the Container App resource.') +param containerAppConfiguration containerAppConfigurationType = { + enabled: true + name: 'ca-${solutionPrefix}' + location: solutionLocation + tags: tags + environmentResourceId: null //Default value set on module configuration + concurrentRequests: '100' + containerCpu: '2.0' + containerMemory: '4.0Gi' + containerImageRegistryDomain: 'biabcontainerreg.azurecr.io' + containerImageName: 'macaebackend' + containerImageTag: 'latest' + containerName: 'backend' + ingressTargetPort: 8000 + maxReplicas: 1 + minReplicas: 1 +} + +@description('Optional. The configuration to apply for the Web Server Farm resource.') +param webServerFarmConfiguration webServerFarmConfigurationType = { + enabled: true + name: 'asp-${solutionPrefix}' + location: solutionLocation + skuName: 'P1v3' + skuCapacity: 3 + tags: tags +} + +@description('Optional. The configuration to apply for the Web Server Farm resource.') +param webSiteConfiguration webSiteConfigurationType = { + enabled: true + name: 'app-${solutionPrefix}' + location: solutionLocation + containerImageRegistryDomain: 'biabcontainerreg.azurecr.io' + containerImageName: 'macaefrontend' + containerImageTag: 'latest' + containerName: 'backend' + tags: tags + environmentResourceId: null //Default value set on module configuration +} + +// +// Add your parameters here +// + +// ============== // +// Resources // +// ============== // + +/* #disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.[[REPLACE WITH TELEMETRY IDENTIFIER]].${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } } - raiPolicyName: aiModeldeployment.raiPolicyName - } - sku: { - name: aiModeldeployment.sku.name - capacity: aiModeldeployment.sku.capacity } } -] +} */ -module kvault 'deploy_keyvault.bicep' = { - name: 'deploy_keyvault' +// ========== Log Analytics Workspace ========== // +// WAF best practices for Log Analytics: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-log-analytics +// Log Analytics configuration defaults +var logAnalyticsWorkspaceEnabled = logAnalyticsWorkspaceConfiguration.?enabled ?? true +var logAnalyticsWorkspaceResourceName = logAnalyticsWorkspaceConfiguration.?name ?? 'log-${solutionPrefix}' +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.11.2' = if (logAnalyticsWorkspaceEnabled) { + name: take('avm.res.operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) params: { - solutionLocation: solutionLocation - managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - keyvaultName: '${abbrs.security.keyVault}${solutionPrefix}' + name: logAnalyticsWorkspaceResourceName + tags: logAnalyticsWorkspaceConfiguration.?tags ?? tags + location: logAnalyticsWorkspaceConfiguration.?location ?? solutionLocation + enableTelemetry: enableTelemetry + skuName: logAnalyticsWorkspaceConfiguration.?sku ?? 'PerGB2018' + dataRetention: logAnalyticsWorkspaceConfiguration.?dataRetentionInDays ?? 365 + diagnosticSettings: [{ useThisWorkspace: true }] } - scope: resourceGroup(resourceGroup().name) } -// First, add this section to store the AI Services key in Key Vault - +// ========== Application Insights ========== // +// WAF best practices for Application Insights: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/application-insights +// Application Insights configuration defaults +var applicationInsightsEnabled = applicationInsightsConfiguration.?enabled ?? true +var applicationInsightsResourceName = applicationInsightsConfiguration.?name ?? 'appi-${solutionPrefix}' +module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (applicationInsightsEnabled) { + name: take('avm.res.insights.component.${applicationInsightsResourceName}', 64) + params: { + name: applicationInsightsResourceName + workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId + location: applicationInsightsConfiguration.?location ?? solutionLocation + enableTelemetry: enableTelemetry + tags: applicationInsightsConfiguration.?tags ?? tags + retentionInDays: applicationInsightsConfiguration.?retentionInDays ?? 365 + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + kind: 'web' + disableIpMasking: false + flowType: 'Bluefield' + } +} -// Then modify the aifoundry module to reference the secret securely -module aifoundry 'deploy_ai_foundry.bicep' = { - name: 'deploy_ai_foundry' +// ========== User assigned identity Web Site ========== // +// WAF best practices for identity and access management: https://learn.microsoft.com/en-us/azure/well-architected/security/identity-access +var userAssignedManagedIdentityEnabled = userAssignedManagedIdentityConfiguration.?enabled ?? true +var userAssignedManagedIdentityResourceName = userAssignedManagedIdentityConfiguration.?name ?? 'id-${solutionPrefix}' +module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = if (userAssignedManagedIdentityEnabled) { + name: take('avm.res.managed-identity.user-assigned-identity.${userAssignedManagedIdentityResourceName}', 64) params: { - solutionName: solutionPrefix - solutionLocation: azureOpenAILocation - keyVaultName: kvault.outputs.keyvaultName - gptModelName: gptModelVersion - gptModelVersion: gptModelVersion - managedIdentityObjectId: managedIdentityModule.outputs.managedIdentityOutput.objectId - aiServicesEndpoint: aiServices.properties.endpoint - aiServicesKey: aiServices.listKeys().key1 - aiServicesId: aiServices.id + name: userAssignedManagedIdentityResourceName + tags: userAssignedManagedIdentityConfiguration.?tags ?? tags + location: userAssignedManagedIdentityConfiguration.?location ?? solutionLocation + enableTelemetry: enableTelemetry } - scope: resourceGroup(resourceGroup().name) } -resource aoaiUserRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview' existing = { - name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' //'Cognitive Services OpenAI User' +// ========== Network Security Groups ========== // +// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network +// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking +var networkSecurityGroupBackendEnabled = networkSecurityGroupBackendConfiguration.?enabled ?? true +var networkSecurityGroupBackendResourceName = networkSecurityGroupBackendConfiguration.?name ?? 'nsg-backend-${solutionPrefix}' +module networkSecurityGroupBackend 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupBackendEnabled) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupBackendResourceName}', 64) + params: { + name: networkSecurityGroupBackendResourceName + location: networkSecurityGroupBackendConfiguration.?location ?? solutionLocation + tags: networkSecurityGroupBackendConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupBackendConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] + } } -resource acaAoaiRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(containerApp.id, aiServices.id, aoaiUserRoleDefinition.id) - scope: aiServices - properties: { - principalId: containerApp.identity.principalId - roleDefinitionId: aoaiUserRoleDefinition.id - principalType: 'ServicePrincipal' +var networkSecurityGroupContainersEnabled = networkSecurityGroupContainersConfiguration.?enabled ?? true +var networkSecurityGroupContainersResourceName = networkSecurityGroupContainersConfiguration.?name ?? 'nsg-containers-${solutionPrefix}' +module networkSecurityGroupContainers 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupContainersEnabled) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupContainersResourceName}', 64) + params: { + name: networkSecurityGroupContainersResourceName + location: networkSecurityGroupContainersConfiguration.?location ?? solutionLocation + tags: networkSecurityGroupContainersConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupContainersConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] } } -resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = { - name: '${abbrs.databases.cosmosDBDatabase}${solutionPrefix}' - location: solutionLocation - tags: tags - kind: 'GlobalDocumentDB' - properties: { - databaseAccountOfferType: 'Standard' - enableFreeTier: false - locations: [ +var networkSecurityGroupBastionEnabled = networkSecurityGroupBastionConfiguration.?enabled ?? true +var networkSecurityGroupBastionResourceName = networkSecurityGroupBastionConfiguration.?name ?? 'nsg-bastion-${solutionPrefix}' +module networkSecurityGroupBastion 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupBastionEnabled) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupBastionResourceName}', 64) + params: { + name: networkSecurityGroupBastionResourceName + location: networkSecurityGroupBastionConfiguration.?location ?? solutionLocation + tags: networkSecurityGroupBastionConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupBastionConfiguration.?securityRules ?? [ { - failoverPriority: 0 - locationName: solutionLocation + name: 'AllowHttpsInBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'Internet' + destinationPortRange: '443' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'AllowGatewayManagerInBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'GatewayManager' + destinationPortRange: '443' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 110 + direction: 'Inbound' + } + } + { + name: 'AllowLoadBalancerInBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: 'AzureLoadBalancer' + destinationPortRange: '443' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 120 + direction: 'Inbound' + } + } + { + name: 'AllowBastionHostCommunicationInBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '8080' + '5701' + ] + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 130 + direction: 'Inbound' + } + } + { + name: 'DenyAllInBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationPortRange: '*' + destinationAddressPrefix: '*' + access: 'Deny' + priority: 1000 + direction: 'Inbound' + } + } + { + name: 'AllowSshRdpOutBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationPortRanges: [ + '22' + '3389' + ] + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'AllowAzureCloudCommunicationOutBound' + properties: { + protocol: 'Tcp' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationPortRange: '443' + destinationAddressPrefix: 'AzureCloud' + access: 'Allow' + priority: 110 + direction: 'Outbound' + } + } + { + name: 'AllowBastionHostCommunicationOutBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '8080' + '5701' + ] + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 120 + direction: 'Outbound' + } + } + { + name: 'AllowGetSessionInformationOutBound' + properties: { + protocol: '*' + sourcePortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: 'Internet' + destinationPortRanges: [ + '80' + '443' + ] + access: 'Allow' + priority: 130 + direction: 'Outbound' + } + } + { + name: 'DenyAllOutBound' + properties: { + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Deny' + priority: 1000 + direction: 'Outbound' + } } ] - capabilities: [{ name: 'EnableServerless' }] - disableLocalAuth: true } +} - resource contributorRoleDefinition 'sqlRoleDefinitions' existing = { - name: '00000000-0000-0000-0000-000000000002' +var networkSecurityGroupAdministrationEnabled = networkSecurityGroupAdministrationConfiguration.?enabled ?? true +var networkSecurityGroupAdministrationResourceName = networkSecurityGroupAdministrationConfiguration.?name ?? 'nsg-administration-${solutionPrefix}' +module networkSecurityGroupAdministration 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupAdministrationEnabled) { + name: take('avm.res.network.network-security-group.${networkSecurityGroupAdministrationResourceName}', 64) + params: { + name: networkSecurityGroupAdministrationResourceName + location: networkSecurityGroupAdministrationConfiguration.?location ?? solutionLocation + tags: networkSecurityGroupAdministrationConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupAdministrationConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] } +} - resource macaeDb 'sqlDatabases' = { - name: 'macae' - properties: { - resource: { - id: 'macae' - createMode: 'Default' +// ========== Virtual Network ========== // +// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network +// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking +var virtualNetworkEnabled = virtualNetworkConfiguration.?enabled ?? true +var virtualNetworkResourceName = virtualNetworkConfiguration.?name ?? 'vnet-${solutionPrefix}' +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.6.1' = if (virtualNetworkEnabled) { + name: take('avm.res.network.virtual-network.${virtualNetworkResourceName}', 64) + params: { + name: virtualNetworkResourceName + location: virtualNetworkConfiguration.?location ?? solutionLocation + tags: virtualNetworkConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + addressPrefixes: virtualNetworkConfiguration.?addressPrefixes ?? ['10.0.0.0/8'] + subnets: virtualNetworkConfiguration.?subnets ?? [ + { + name: 'backend' + addressPrefix: '10.0.0.0/27' + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + networkSecurityGroupResourceId: networkSecurityGroupBackend.outputs.resourceId } + { + name: 'administration' + addressPrefix: '10.0.0.32/27' + networkSecurityGroupResourceId: networkSecurityGroupAdministration.outputs.resourceId + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + //natGatewayResourceId: natGateway.outputs.resourceId + } + { + // For Azure Bastion resources deployed on or after November 2, 2021, the minimum AzureBastionSubnet size is /26 or larger (/25, /24, etc.). + // https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet + name: 'AzureBastionSubnet' //This exact name is required for Azure Bastion + addressPrefix: '10.0.0.64/26' + networkSecurityGroupResourceId: networkSecurityGroupBastion.outputs.resourceId + } + { + // If you use your own vnw, you need to provide a subnet that is dedicated exclusively to the Container App environment you deploy. This subnet isn't available to other services + // https://learn.microsoft.com/en-us/azure/container-apps/networking?tabs=workload-profiles-env%2Cazure-cli#custom-vnw-configuration + name: 'containers' + addressPrefix: '10.0.2.0/23' //subnet of size /23 is required for container app + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + delegation: 'Microsoft.App/environments' + networkSecurityGroupResourceId: networkSecurityGroupContainers.outputs.resourceId + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + ] + } +} +var bastionEnabled = bastionConfiguration.?enabled ?? true +var bastionResourceName = bastionConfiguration.?name ?? 'bas-${solutionPrefix}' + +// ========== Bastion host ========== // +// WAF best practices for virtual networks: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-network +// WAF recommendations for networking and connectivity: https://learn.microsoft.com/en-us/azure/well-architected/security/networking +module bastionHost 'br/public:avm/res/network/bastion-host:0.6.1' = if (virtualNetworkEnabled && bastionEnabled) { + name: take('avm.res.network.bastion-host.${bastionResourceName}', 64) + params: { + name: bastionResourceName + location: bastionConfiguration.?location ?? solutionLocation + skuName: bastionConfiguration.?sku ?? 'Standard' + enableTelemetry: enableTelemetry + tags: bastionConfiguration.?tags ?? tags + virtualNetworkResourceId: bastionConfiguration.?virtualNetworkResourceId ?? virtualNetwork.?outputs.?resourceId + publicIPAddressObject: { + name: bastionConfiguration.?publicIpResourceName ?? 'pip-bas${solutionPrefix}' } + disableCopyPaste: false + enableFileCopy: false + enableIpConnect: true + //enableKerberos: bastionConfiguration.?enableKerberos + enableShareableLink: true + //scaleUnits: bastionConfiguration.?scaleUnits + } +} - resource memoryContainer 'containers' = { - name: 'memory' - properties: { - resource: { - id: 'memory' - partitionKey: { - kind: 'Hash' - version: 2 - paths: [ - '/session_id' - ] +// ========== Virtual machine ========== // +// WAF best practices for virtual machines: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/virtual-machines +var virtualMachineEnabled = virtualMachineConfiguration.?enabled ?? true +var virtualMachineResourceName = virtualMachineConfiguration.?name ?? 'vm${solutionPrefix}' +module virtualMachine 'br/public:avm/res/compute/virtual-machine:0.13.0' = if (virtualNetworkEnabled && virtualMachineEnabled) { + name: take('avm.res.compute.virtual-machine.${virtualMachineResourceName}', 64) + params: { + name: virtualMachineResourceName + computerName: take(virtualMachineResourceName, 15) + location: virtualMachineConfiguration.?location ?? solutionLocation + tags: virtualMachineConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + vmSize: virtualMachineConfiguration.?vmSize ?? 'Standard_D2s_v3' + adminUsername: virtualMachineConfiguration.?adminUsername ?? 'adminuser' + adminPassword: virtualMachineConfiguration.?adminPassword ?? guid(solutionPrefix, subscription().subscriptionId) + nicConfigurations: [ + { + name: 'nic-${virtualMachineResourceName}' + //networkSecurityGroupResourceId: virtualMachineConfiguration.?nicConfigurationConfiguration.networkSecurityGroupResourceId + //nicSuffix: 'nic-${virtualMachineResourceName}' + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + ipConfigurations: [ + { + name: '${virtualMachineResourceName}-nic01-ipconfig01' + subnetResourceId: virtualMachineConfiguration.?subnetResourceId ?? virtualNetwork.outputs.subnetResourceIds[1] + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] } - } + ] + } + ] + imageReference: { + publisher: 'microsoft-dsvm' + offer: 'dsvm-win-2022' + sku: 'winserver-2022' + version: 'latest' + } + osDisk: { + name: 'osdisk-${virtualMachineResourceName}' + createOption: 'FromImage' + managedDisk: { + storageAccountType: 'Premium_ZRS' } + diskSizeGB: 128 + caching: 'ReadWrite' } + //patchMode: virtualMachineConfiguration.?patchMode + osType: 'Windows' + encryptionAtHost: false //The property 'securityProfile.encryptionAtHost' is not valid because the 'Microsoft.Compute/EncryptionAtHost' feature is not enabled for this subscription. + zone: 0 + extensionAadJoinConfig: { + enabled: true + typeHandlerVersion: '1.0' + } + // extensionMonitoringAgentConfig: { + // enabled: true + // } + // maintenanceConfigurationResourceId: virtualMachineConfiguration.?maintenanceConfigurationResourceId } } -// Define existing ACR resource -resource pullIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { - name: '${abbrs.security.managedIdentity}${solutionPrefix}-containerapp-pull' - location: solutionLocation +// ========== AI Foundry: AI Services ========== // +// WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai +var openAiSubResource = 'account' +var openAiPrivateDnsZones = { + 'privatelink.cognitiveservices.azure.com': openAiSubResource + 'privatelink.openai.azure.com': openAiSubResource + 'privatelink.services.ai.azure.com': openAiSubResource } -resource containerAppEnv 'Microsoft.App/managedEnvironments@2024-03-01' = { - name: '${abbrs.containers.containerAppsEnvironment}${solutionPrefix}' - location: solutionLocation - tags: tags - properties: { - daprAIConnectionString: appInsights.properties.ConnectionString - appLogsConfiguration: { - destination: 'log-analytics' - logAnalyticsConfiguration: { - customerId: logAnalytics.properties.customerId - sharedKey: logAnalytics.listKeys().primarySharedKey - } +module privateDnsZonesAiServices 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ + for zone in objectKeys(openAiPrivateDnsZones): if (virtualNetworkEnabled && aiFoundryAIservicesEnabled) { + name: take( + 'avm.res.network.private-dns-zone.ai-services.${uniqueString(aiFoundryAiServicesResourceName,zone)}.${solutionPrefix}', + 64 + ) + params: { + name: zone + tags: tags + enableTelemetry: enableTelemetry + virtualNetworkLinks: [ + { + name: 'vnetlink-${split(zone, '.')[1]}' + virtualNetworkResourceId: virtualNetwork.outputs.resourceId + } + ] } } - resource aspireDashboard 'dotNetComponents@2024-02-02-preview' = { - name: 'aspire-dashboard' - properties: { - componentType: 'AspireDashboard' - } +] + +// NOTE: Required version 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' not available in AVM +var aiFoundryAiServicesResourceName = aiFoundryAiServicesConfiguration.?name ?? 'aisa-${solutionPrefix}' +var aiFoundryAIservicesEnabled = aiFoundryAiServicesConfiguration.?enabled ?? true +var aiFoundryAiServicesModelDeployment = { + format: 'OpenAI' + name: 'gpt-4o' + version: '2024-08-06' + sku: { + name: 'GlobalStandard' + //Curently the capacity is set to 140 for opinanal performance. + capacity: aiFoundryAiServicesConfiguration.?modelCapcity ?? 140 } + raiPolicyName: 'Microsoft.Default' } -resource acaCosomsRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15' = { - name: guid(containerApp.id, cosmos::contributorRoleDefinition.id) - parent: cosmos - properties: { - principalId: containerApp.identity.principalId - roleDefinitionId: cosmos::contributorRoleDefinition.id - scope: cosmos.id +module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.10.2' = if (aiFoundryAIservicesEnabled) { + name: take('avm.res.cognitive-services.account.${aiFoundryAiServicesResourceName}', 64) + params: { + name: aiFoundryAiServicesResourceName + tags: aiFoundryAiServicesConfiguration.?tags ?? tags + location: aiFoundryAiServicesConfiguration.?location ?? azureOpenAILocation + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + sku: aiFoundryAiServicesConfiguration.?sku ?? 'S0' + kind: 'AIServices' + disableLocalAuth: false //Should be set to true for WAF aligned configuration + customSubDomainName: aiFoundryAiServicesResourceName + apiProperties: { + //staticsEnabled: false + } + //publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + //publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + publicNetworkAccess: 'Enabled' //TODO: connection via private endpoint is not working from containers network. Change this when fixed + privateEndpoints: virtualNetworkEnabled + ? ([ + { + name: 'pep-${aiFoundryAiServicesResourceName}' + customNetworkInterfaceName: 'nic-${aiFoundryAiServicesResourceName}' + subnetResourceId: aiFoundryAiServicesConfiguration.?subnetResourceId ?? virtualNetwork.outputs.subnetResourceIds[0] + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: map(objectKeys(openAiPrivateDnsZones), zone => { + name: replace(zone, '.', '-') + privateDnsZoneResourceId: resourceId('Microsoft.Network/privateDnsZones', zone) + }) + } + } + ]) + : [] + roleAssignments: [ + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'Cognitive Services OpenAI User' + // } + { + principalId: containerApp.outputs.?systemAssignedMIPrincipalId! + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Cognitive Services OpenAI User' + } + ] + deployments: aiFoundryAiServicesConfiguration.?deployments ?? [ + { + name: aiFoundryAiServicesModelDeployment.name + model: { + format: aiFoundryAiServicesModelDeployment.format + name: aiFoundryAiServicesModelDeployment.name + version: aiFoundryAiServicesModelDeployment.version + } + raiPolicyName: aiFoundryAiServicesModelDeployment.raiPolicyName + sku: { + name: aiFoundryAiServicesModelDeployment.sku.name + capacity: aiFoundryAiServicesModelDeployment.sku.capacity + } + } + ] } } -@description('') -resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { - name: '${abbrs.containers.containerApp}${solutionPrefix}-backend' - location: solutionLocation - tags: tags - identity: { - type: 'SystemAssigned, UserAssigned' - userAssignedIdentities: { - '${pullIdentity.id}': {} +// AI Foundry: storage account +// WAF best practices for Azure Blob Storage: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-blob-storage +var storageAccountPrivateDnsZones = { + 'privatelink.blob.${environment().suffixes.storage}': 'blob' + 'privatelink.file.${environment().suffixes.storage}': 'file' +} + +module privateDnsZonesAiFoundryStorageAccount 'br/public:avm/res/network/private-dns-zone:0.3.1' = [ + for zone in objectKeys(storageAccountPrivateDnsZones): if (virtualNetworkEnabled && aiFoundryStorageAccountEnabled) { + name: take( + 'avm.res.network.private-dns-zone.storage-account.${uniqueString(aiFoundryStorageAccountResourceName,zone)}.${solutionPrefix}', + 64 + ) + params: { + name: zone + tags: tags + enableTelemetry: enableTelemetry + virtualNetworkLinks: [ + { + name: 'vnetlink-${split(zone, '.')[1]}' + virtualNetworkResourceId: virtualNetwork.outputs.resourceId + } + ] } } - properties: { - managedEnvironmentId: containerAppEnv.id - configuration: { - ingress: { - targetPort: 8000 - external: true - corsPolicy: { - allowedOrigins: [ - 'https://${abbrs.compute.webApp}${solutionPrefix}-frontend.azurewebsites.net' - 'http://${abbrs.compute.webApp}${solutionPrefix}-frontend.azurewebsites.net' - ] +] +var aiFoundryStorageAccountEnabled = aiFoundryStorageAccountConfiguration.?enabled ?? true +var aiFoundryStorageAccountResourceName = aiFoundryStorageAccountConfiguration.?name ?? replace( + 'sthub${solutionPrefix}', + '-', + '' +) + +module aiFoundryStorageAccount 'br/public:avm/res/storage/storage-account:0.18.2' = if (aiFoundryStorageAccountEnabled) { + name: take('avm.res.storage.storage-account.${aiFoundryStorageAccountResourceName}', 64) + dependsOn: [ + privateDnsZonesAiFoundryStorageAccount + ] + params: { + name: aiFoundryStorageAccountResourceName + location: aiFoundryStorageAccountConfiguration.?location ?? azureOpenAILocation + tags: aiFoundryStorageAccountConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + skuName: aiFoundryStorageAccountConfiguration.?sku ?? 'Standard_ZRS' + allowSharedKeyAccess: false + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + } + blobServices: { + deleteRetentionPolicyEnabled: false + containerDeleteRetentionPolicyDays: 7 + containerDeleteRetentionPolicyEnabled: false + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + } + publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + allowBlobPublicAccess: virtualNetworkEnabled ? false : true + privateEndpoints: virtualNetworkEnabled + ? map(items(storageAccountPrivateDnsZones), zone => { + name: 'pep-${zone.value}-${aiFoundryStorageAccountResourceName}' + customNetworkInterfaceName: 'nic-${zone.value}-${aiFoundryStorageAccountResourceName}' + service: zone.value + subnetResourceId: aiFoundryStorageAccountConfiguration.?subnetResourceId ?? virtualNetwork.outputs.subnetResourceIds[0] ?? '' + privateDnsZoneResourceIds: [resourceId('Microsoft.Network/privateDnsZones', zone.key)] + }) + : null + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + } + ] + } +} + +// AI Foundry: AI Hub +// WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai +var mlTargetSubResource = 'amlworkspace' +var mlPrivateDnsZones = { + 'privatelink.api.azureml.ms': mlTargetSubResource + 'privatelink.notebooks.azure.net': mlTargetSubResource +} +module privateDnsZonesAiFoundryWorkspaceHub 'br/public:avm/res/network/private-dns-zone:0.3.1' = [ + for zone in objectKeys(mlPrivateDnsZones): if (virtualNetworkEnabled && aiFoundryAiHubEnabled) { + name: take('avm.res.network.private-dns-zone.ai-hub.${uniqueString(aiFoundryAiHubName,zone)}.${solutionPrefix}', 64) + params: { + name: zone + enableTelemetry: enableTelemetry + tags: tags + virtualNetworkLinks: [ + { + name: 'vnetlink-${split(zone, '.')[1]}' + virtualNetworkResourceId: virtualNetwork.outputs.resourceId } + ] + } + } +] +var aiFoundryAiHubEnabled = aiFoundryAiHubConfiguration.?enabled ?? true +var aiFoundryAiHubName = aiFoundryAiHubConfiguration.?name ?? 'aih-${solutionPrefix}' +module aiFoundryAiHub 'modules/ai-hub.bicep' = if (aiFoundryAiHubEnabled) { + name: take('module.ai-hub.${aiFoundryAiHubName}', 64) + dependsOn: [ + privateDnsZonesAiFoundryWorkspaceHub + ] + params: { + name: aiFoundryAiHubName + location: aiFoundryAiHubConfiguration.?location ?? azureOpenAILocation + tags: aiFoundryAiHubConfiguration.?tags ?? tags + sku: aiFoundryAiHubConfiguration.?sku ?? 'Basic' + aiFoundryAiServicesName: aiFoundryAiServices.outputs.name + applicationInsightsResourceId: applicationInsights.outputs.resourceId + enableTelemetry: enableTelemetry + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.resourceId + storageAccountResourceId: aiFoundryStorageAccount.outputs.resourceId + virtualNetworkEnabled: virtualNetworkEnabled + privateEndpoints: virtualNetworkEnabled + ? [ + { + name: 'pep-${aiFoundryAiHubName}' + customNetworkInterfaceName: 'nic-${aiFoundryAiHubName}' + service: mlTargetSubResource + subnetResourceId: virtualNetworkEnabled + ? aiFoundryAiHubConfiguration.?subnetResourceId ?? virtualNetwork.?outputs.?subnetResourceIds[0] + : null + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: map(objectKeys(mlPrivateDnsZones), zone => { + name: replace(zone, '.', '-') + privateDnsZoneResourceId: resourceId('Microsoft.Network/privateDnsZones', zone) + }) + } + } + ] + : [] + } +} + +// AI Foundry: AI Project +// WAF best practices for Open AI: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-openai +var aiFoundryAiProjectEnabled = aiFoundryAiProjectConfiguration.?enabled ?? true +var aiFoundryAiProjectName = aiFoundryAiProjectConfiguration.?name ?? 'aihb-${solutionPrefix}' + +module aiFoundryAiProject 'br/public:avm/res/machine-learning-services/workspace:0.12.0' = if (aiFoundryAiProjectEnabled) { + name: take('avm.res.machine-learning-services.workspace.${aiFoundryAiProjectName}', 64) + params: { + name: aiFoundryAiProjectName + location: aiFoundryAiProjectConfiguration.?location ?? azureOpenAILocation + tags: aiFoundryAiProjectConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + sku: aiFoundryAiProjectConfiguration.?sku ?? 'Basic' + kind: 'Project' + hubResourceId: aiFoundryAiHub.outputs.resourceId + roleAssignments: [ + { + principalId: containerApp.outputs.?systemAssignedMIPrincipalId! + // Assigning the role with the role name instead of the role ID freezes the deployment at this point + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' //'Azure AI Developer' } - activeRevisionsMode: 'Single' + ] + } +} + +// ========== Cosmos DB ========== // +// WAF best practices for Cosmos DB: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/cosmos-db +module privateDnsZonesCosmosDb 'br/public:avm/res/network/private-dns-zone:0.7.0' = if (virtualNetworkEnabled) { + name: take('avm.res.network.private-dns-zone.cosmos-db.${solutionPrefix}', 64) + params: { + name: 'privatelink.documents.azure.com' + enableTelemetry: enableTelemetry + virtualNetworkLinks: [ + { + name: 'vnetlink-cosmosdb' + virtualNetworkResourceId: virtualNetwork.outputs.resourceId + } + ] + tags: tags + } +} + +var cosmosDbAccountEnabled = cosmosDbAccountConfiguration.?enabled ?? true +var cosmosDbResourceName = cosmosDbAccountConfiguration.?name ?? 'cosmos-${solutionPrefix}' +var cosmosDbDatabaseName = 'macae' +var cosmosDbDatabaseMemoryContainerName = 'memory' +module cosmosDb 'br/public:avm/res/document-db/database-account:0.12.0' = if (cosmosDbAccountEnabled) { + name: take('avm.res.document-db.database-account.${cosmosDbResourceName}', 64) + params: { + // Required parameters + name: cosmosDbAccountConfiguration.?name ?? 'cosmos-${solutionPrefix}' + location: cosmosDbAccountConfiguration.?location ?? solutionLocation + tags: cosmosDbAccountConfiguration.?tags ?? tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + databaseAccountOfferType: 'Standard' + enableFreeTier: false + networkRestrictions: { + networkAclBypass: 'None' + publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' } - template: { - scale: { - minReplicas: resourceSize.containerAppSize.minReplicas - maxReplicas: resourceSize.containerAppSize.maxReplicas - rules: [ + privateEndpoints: virtualNetworkEnabled + ? [ { - name: 'http-scaler' - http: { - metadata: { - concurrentRequests: '100' - } + name: 'pep-${cosmosDbResourceName}' + customNetworkInterfaceName: 'nic-${cosmosDbResourceName}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: privateDnsZonesCosmosDb.outputs.resourceId }] } + service: 'Sql' + subnetResourceId: cosmosDbAccountConfiguration.?subnetResourceId ?? virtualNetwork.outputs.subnetResourceIds[0] + } + ] + : [] + sqlDatabases: concat(cosmosDbAccountConfiguration.?sqlDatabases ?? [], [ + { + name: cosmosDbDatabaseName + containers: [ + { + name: cosmosDbDatabaseMemoryContainerName + paths: [ + '/session_id' + ] + kind: 'Hash' + version: 2 } ] } - containers: [ + ]) + locations: [ + { + locationName: cosmosDbAccountConfiguration.?location ?? solutionLocation + failoverPriority: 0 + } + ] + capabilitiesToAdd: [ + 'EnableServerless' + ] + sqlRoleAssignmentsPrincipalIds: [ + //userAssignedIdentity.outputs.principalId + containerApp.outputs.?systemAssignedMIPrincipalId + ] + sqlRoleDefinitions: [ + { + // Replace this with built-in role definition Cosmos DB Built-in Data Contributor: https://docs.azure.cn/en-us/cosmos-db/nosql/security/reference-data-plane-roles#cosmos-db-built-in-data-contributor + roleType: 'CustomRole' + roleName: 'Cosmos DB SQL Data Contributor' + name: 'cosmos-db-sql-data-contributor' + dataAction: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + ] + } + ] + } +} + +// ========== Backend Container App Environment ========== // +// WAF best practices for container apps: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-container-apps +var containerAppEnvironmentEnabled = containerAppEnvironmentConfiguration.?enabled ?? true +var containerAppEnvironmentResourceName = containerAppEnvironmentConfiguration.?name ?? 'cae-${solutionPrefix}' +module containerAppEnvironment 'modules/container-app-environment.bicep' = if (containerAppEnvironmentEnabled) { + name: take('module.container-app-environment.${containerAppEnvironmentResourceName}', 64) + params: { + name: containerAppEnvironmentResourceName + tags: containerAppEnvironmentConfiguration.?tags ?? tags + location: containerAppEnvironmentConfiguration.?location ?? solutionLocation + logAnalyticsResourceName: logAnalyticsWorkspace.outputs.name + publicNetworkAccess: 'Enabled' + zoneRedundant: virtualNetworkEnabled ? true : false + applicationInsightsConnectionString: applicationInsights.outputs.connectionString + enableTelemetry: enableTelemetry + subnetResourceId: virtualNetworkEnabled + ? containerAppEnvironmentConfiguration.?subnetResourceId ?? virtualNetwork.?outputs.?subnetResourceIds[3] ?? '' + : '' + //aspireDashboardEnabled: !virtualNetworkEnabled + // vnetConfiguration: virtualNetworkEnabled + // ? { + // internal: false + // infrastructureSubnetId: containerAppEnvironmentConfiguration.?subnetResourceId ?? virtualNetwork.?outputs.?subnetResourceIds[3] ?? '' + // } + // : {} + } +} + +// ========== Backend Container App Service ========== // +// WAF best practices for container apps: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/azure-container-apps +var containerAppEnabled = containerAppConfiguration.?enabled ?? true +var containerAppResourceName = containerAppConfiguration.?name ?? 'ca-${solutionPrefix}' +module containerApp 'br/public:avm/res/app/container-app:0.14.2' = if (containerAppEnabled) { + name: take('avm.res.app.container-app.${containerAppResourceName}', 64) + params: { + name: containerAppResourceName + tags: containerAppConfiguration.?tags ?? tags + location: containerAppConfiguration.?location ?? solutionLocation + enableTelemetry: enableTelemetry + environmentResourceId: containerAppConfiguration.?environmentResourceId ?? containerAppEnvironment.outputs.resourceId + managedIdentities: { + systemAssigned: true //Replace with user assigned identity + userAssignedResourceIds: [userAssignedIdentity.outputs.resourceId] + } + ingressTargetPort: containerAppConfiguration.?ingressTargetPort ?? 8000 + ingressExternal: true + activeRevisionsMode: 'Single' + corsPolicy: { + allowedOrigins: [ + 'https://${webSiteName}.azurewebsites.net' + 'http://${webSiteName}.azurewebsites.net' + ] + } + scaleSettings: { + //TODO: Make maxReplicas and minReplicas parameterized + maxReplicas: containerAppConfiguration.?maxReplicas ?? 1 + minReplicas: containerAppConfiguration.?minReplicas ?? 1 + rules: [ { - name: 'backend' - image: backendDockerImageURL - resources: { - cpu: json(resourceSize.containerAppSize.cpu) - memory: resourceSize.containerAppSize.memory - } - env: [ - { - name: 'COSMOSDB_ENDPOINT' - value: cosmos.properties.documentEndpoint + name: 'http-scaler' + http: { + metadata: { + concurrentRequests: containerAppConfiguration.?concurrentRequests ?? '100' } - { - name: 'COSMOSDB_DATABASE' - value: cosmos::macaeDb.name - } - { - name: 'COSMOSDB_CONTAINER' - value: cosmos::macaeDb::memoryContainer.name - } - { - name: 'AZURE_OPENAI_ENDPOINT' - value: replace(aiServices.properties.endpoint, 'cognitiveservices.azure.com', 'openai.azure.com') - } - { - name: 'AZURE_OPENAI_MODEL_NAME' - value: gptModelVersion - } - { - name: 'AZURE_OPENAI_DEPLOYMENT_NAME' - value: gptModelVersion - } - { - name: 'AZURE_OPENAI_API_VERSION' - value: aoaiApiVersion - } - { - name: 'APPLICATIONINSIGHTS_INSTRUMENTATION_KEY' - value: appInsights.properties.InstrumentationKey - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: appInsights.properties.ConnectionString - } - { - name: 'AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' - value: aifoundry.outputs.projectConnectionString - } - { - name: 'AZURE_AI_SUBSCRIPTION_ID' - value: subscription().subscriptionId - } - { - name: 'AZURE_AI_RESOURCE_GROUP' - value: resourceGroup().name - } - { - name: 'AZURE_AI_PROJECT_NAME' - value: aifoundry.outputs.aiProjectName - } - { - name: 'FRONTEND_SITE_NAME' - value: 'https://${abbrs.compute.webApp}${solutionPrefix}-frontend.azurewebsites.net' - } - ] + } } ] } + containers: [ + { + name: containerAppConfiguration.?containerName ?? 'backend' + image: '${containerAppConfiguration.?containerImageRegistryDomain ?? 'biabcontainerreg.azurecr.io'}/${containerAppConfiguration.?containerImageName ?? 'macaebackend'}:${containerAppConfiguration.?containerImageTag ?? 'latest'}' + resources: { + //TODO: Make cpu and memory parameterized + cpu: containerAppConfiguration.?containerCpu ?? '2.0' + memory: containerAppConfiguration.?containerMemory ?? '4.0Gi' + } + env: [ + { + name: 'COSMOSDB_ENDPOINT' + value: 'https://${cosmosDbResourceName}.documents.azure.com:443/' + } + { + name: 'COSMOSDB_DATABASE' + value: cosmosDbDatabaseName + } + { + name: 'COSMOSDB_CONTAINER' + value: cosmosDbDatabaseMemoryContainerName + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: 'https://${aiFoundryAiServicesResourceName}.openai.azure.com/' + } + { + name: 'AZURE_OPENAI_MODEL_NAME' + value: aiFoundryAiServicesModelDeployment.name + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: aiFoundryAiServicesModelDeployment.name + } + { + name: 'AZURE_OPENAI_API_VERSION' + value: '2025-01-01-preview' //TODO: set parameter/variable + } + { + name: 'APPLICATIONINSIGHTS_INSTRUMENTATION_KEY' + value: applicationInsights.outputs.instrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.outputs.connectionString + } + { + name: 'AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' + value: '${toLower(replace(azureOpenAILocation,' ',''))}.api.azureml.ms;${subscription().subscriptionId};${resourceGroup().name};${aiFoundryAiProjectName}' + //Location should be the AI Foundry AI Project location + } + { + name: 'AZURE_AI_SUBSCRIPTION_ID' + value: subscription().subscriptionId + } + { + name: 'AZURE_AI_RESOURCE_GROUP' + value: resourceGroup().name + } + { + name: 'AZURE_AI_PROJECT_NAME' + value: aiFoundryAiProjectName + } + { + name: 'FRONTEND_SITE_NAME' + value: 'https://${webSiteName}.azurewebsites.net' + } + ] + } + ] } } -resource frontendAppServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = { - name: '${abbrs.compute.appServicePlan}${solutionPrefix}-frontend' - location: solutionLocation - tags: tags - sku: { - name: 'B2' - capacity: 1 - tier: 'Basic' - } - properties: { + +var webServerFarmEnabled = webServerFarmConfiguration.?enabled ?? true +var webServerFarmResourceName = webServerFarmConfiguration.?name ?? 'asp-${solutionPrefix}' + +// ========== Frontend server farm ========== // +// WAF best practices for web app service: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps +module webServerFarm 'br/public:avm/res/web/serverfarm:0.4.1' = if (webServerFarmEnabled) { + name: take('avm.res.web.serverfarm.${webServerFarmResourceName}', 64) + params: { + name: webServerFarmResourceName + tags: tags + location: webServerFarmConfiguration.?location ?? solutionLocation + skuName: webServerFarmConfiguration.?skuName ?? 'P1v3' + skuCapacity: webServerFarmConfiguration.?skuCapacity ?? 3 reserved: true + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + kind: 'linux' + zoneRedundant: false //TODO: make it zone redundant for waf aligned } - kind: 'linux' // Add this line to support Linux containers } -resource frontendAppService 'Microsoft.Web/sites@2021-02-01' = { - name: '${abbrs.compute.webApp}${solutionPrefix}-frontend' - location: solutionLocation - tags: tags - kind: 'app,linux,container' - properties: { - serverFarmId: frontendAppServicePlan.id - reserved: true +// ========== Frontend web site ========== // +// WAF best practices for web app service: https://learn.microsoft.com/en-us/azure/well-architected/service-guides/app-service-web-apps +var webSiteEnabled = webSiteConfiguration.?enabled ?? true + +var webSiteName = 'app-${solutionPrefix}' +module webSite 'br/public:avm/res/web/site:0.15.1' = if (webSiteEnabled) { + name: take('avm.res.web.site.${webSiteName}', 64) + params: { + name: webSiteName + tags: webSiteConfiguration.?tags ?? tags + location: webSiteConfiguration.?location ?? solutionLocation + kind: 'app,linux,container' + enableTelemetry: enableTelemetry + serverFarmResourceId: webSiteConfiguration.?environmentResourceId ?? webServerFarm.?outputs.resourceId + appInsightResourceId: applicationInsights.outputs.resourceId + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + publicNetworkAccess: 'Enabled' //TODO: use Azure Front Door WAF or Application Gateway WAF instead siteConfig: { - linuxFxVersion: 'DOCKER|${frontendDockerImageURL}' - appSettings: [ - { - name: 'DOCKER_REGISTRY_SERVER_URL' - value: dockerRegistryUrl - } - { - name: 'WEBSITES_PORT' - value: '3000' - } - { - name: 'WEBSITES_CONTAINER_START_TIME_LIMIT' - value: '1800' - } - { - name: 'BACKEND_API_URL' - value: 'https://${containerApp.properties.configuration.ingress.fqdn}' - } - { - name: 'AUTH_ENABLED' - value: 'false' - } - ] + linuxFxVersion: 'DOCKER|${webSiteConfiguration.?containerImageRegistryDomain ?? 'biabcontainerreg.azurecr.io'}/${webSiteConfiguration.?containerImageName ?? 'macaefrontend'}:${webSiteConfiguration.?containerImageTag ?? 'latest'}' } - } - dependsOn: [containerApp] - identity: { - type: 'SystemAssigned, UserAssigned' - userAssignedIdentities: { - '${pullIdentity.id}': {} + appSettingsKeyValuePairs: { + SCM_DO_BUILD_DURING_DEPLOYMENT: 'true' + DOCKER_REGISTRY_SERVER_URL: 'https://${webSiteConfiguration.?containerImageRegistryDomain ?? 'biabcontainerreg.azurecr.io'}' + WEBSITES_PORT: '3000' + WEBSITES_CONTAINER_START_TIME_LIMIT: '1800' // 30 minutes, adjust as needed + BACKEND_API_URL: 'https://${containerApp.outputs.fqdn}' + AUTH_ENABLED: 'false' } } } -resource aiHubProject 'Microsoft.MachineLearningServices/workspaces@2024-01-01-preview' existing = { - name: '${abbrs.ai.aiHubProject}${solutionPrefix}' // aiProjectName must be calculated - available at main start. +// ============ // +// Outputs // +// ============ // + +// Add your outputs here + +@description('The default url of the website to connect to the Multi-Agent Custom Automation Engine solution.') +output webSiteDefaultHostname string = webSite.outputs.defaultHostname + +// @description('The name of the resource.') +// output name string = .name + +// @description('The location the resource was deployed into.') +// output location string = .location + +// ================ // +// Definitions // +// ================ // +// +// Add your User-defined-types here, if any +// + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Log Analytics Workspace resource configuration.') +type logAnalyticsWorkspaceConfigurationType = { + @description('Optional. If the Log Analytics Workspace resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Log Analytics Workspace resource.') + @maxLength(63) + name: string? + + @description('Optional. Location for the Log Analytics Workspace resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to for the Log Analytics Workspace resource.') + tags: object? + + @description('Optional. The SKU for the Log Analytics Workspace resource.') + sku: ('CapacityReservation' | 'Free' | 'LACluster' | 'PerGB2018' | 'PerNode' | 'Premium' | 'Standalone' | 'Standard')? + + @description('Optional. The number of days to retain the data in the Log Analytics Workspace. If empty, it will be set to 365 days.') + @maxValue(730) + dataRetentionInDays: int? } -resource aiDeveloper 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { - name: '64702f94-c441-49e6-a78b-ef80e0188fee' +@export() +@description('The type for the Multi-Agent Custom Automation Engine Application Insights resource configuration.') +type applicationInsightsConfigurationType = { + @description('Optional. If the Application Insights resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Application Insights resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Application Insights resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Application Insights resource.') + tags: object? + + @description('Optional. The retention of Application Insights data in days. If empty, Standard will be used.') + retentionInDays: (120 | 180 | 270 | 30 | 365 | 550 | 60 | 730 | 90)? } -resource aiDeveloperAccessProj 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(containerApp.name, aiHubProject.id, aiDeveloper.id) - scope: aiHubProject - properties: { - roleDefinitionId: aiDeveloper.id - principalId: containerApp.identity.principalId - } +@export() +@description('The type for the Multi-Agent Custom Automation Engine Application User Assigned Managed Identity resource configuration.') +type userAssignedManagedIdentityType = { + @description('Optional. If the User Assigned Managed Identity resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the User Assigned Managed Identity resource.') + @maxLength(128) + name: string? + + @description('Optional. Location for the User Assigned Managed Identity resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the User Assigned Managed Identity resource.') + tags: object? } -var cosmosAssignCli = 'az cosmosdb sql role assignment create --resource-group "${resourceGroup().name}" --account-name "${cosmos.name}" --role-definition-id "${cosmos::contributorRoleDefinition.id}" --scope "${cosmos.id}" --principal-id "${containerApp.identity.principalId}"' +@export() +import { securityRuleType } from 'br/public:avm/res/network/network-security-group:0.5.1' +@description('The type for the Multi-Agent Custom Automation Engine Network Security Group resource configuration.') +type networkSecurityGroupConfigurationType = { + @description('Optional. If the Network Security Group resource should be deployed or not.') + enabled: bool? -module managedIdentityModule 'deploy_managed_identity.bicep' = { - name: 'deploy_managed_identity' - params: { - //solutionLocation: location - managedIdentityId: pullIdentity.id - managedIdentityPropPrin: pullIdentity.properties.principalId - managedIdentityLocation: pullIdentity.location - miName: '${abbrs.security.managedIdentity}${solutionPrefix}' - } - scope: resourceGroup(resourceGroup().name) + @description('Optional. The name of the Network Security Group resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Network Security Group resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Network Security Group resource.') + tags: object? + + @description('Optional. The security rules to set for the Network Security Group resource.') + securityRules: securityRuleType[]? } -module deploymentScriptCLI 'br/public:avm/res/resources/deployment-script:0.5.1' = { - name: 'deploymentScriptCLI' - params: { - // Required parameters - kind: 'AzureCLI' - name: 'rdsmin001' - // Non-required parameters - azCliVersion: '2.69.0' - location: solutionLocation - managedIdentities: { - userAssignedResourceIds: [ - managedIdentityModule.outputs.managedIdentityId - ] - } - scriptContent: cosmosAssignCli - } +@export() +@description('The type for the Multi-Agent Custom Automation virtual network resource configuration.') +type virtualNetworkConfigurationType = { + @description('Optional. If the Virtual Network resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Virtual Network resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Virtual Network resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Virtual Network resource.') + tags: object? + + @description('Optional. An array of 1 or more IP Addresses prefixes for the Virtual Network resource.') + addressPrefixes: string[]? + + @description('Optional. An array of 1 or more subnets for the Virtual Network resource.') + subnets: subnetType[]? +} + +import { roleAssignmentType } from 'br/public:avm/utl/types/avm-common-types:0.5.1' +type subnetType = { + @description('Optional. The Name of the subnet resource.') + name: string + + @description('Conditional. The address prefix for the subnet. Required if `addressPrefixes` is empty.') + addressPrefix: string? + + @description('Conditional. List of address prefixes for the subnet. Required if `addressPrefix` is empty.') + addressPrefixes: string[]? + + @description('Optional. Application gateway IP configurations of virtual network resource.') + applicationGatewayIPConfigurations: object[]? + + @description('Optional. The delegation to enable on the subnet.') + delegation: string? + + @description('Optional. The resource ID of the NAT Gateway to use for the subnet.') + natGatewayResourceId: string? + + @description('Optional. The resource ID of the network security group to assign to the subnet.') + networkSecurityGroupResourceId: string? + + @description('Optional. enable or disable apply network policies on private endpoint in the subnet.') + privateEndpointNetworkPolicies: ('Disabled' | 'Enabled' | 'NetworkSecurityGroupEnabled' | 'RouteTableEnabled')? + + @description('Optional. enable or disable apply network policies on private link service in the subnet.') + privateLinkServiceNetworkPolicies: ('Disabled' | 'Enabled')? + + @description('Optional. Array of role assignments to create.') + roleAssignments: roleAssignmentType[]? + + @description('Optional. The resource ID of the route table to assign to the subnet.') + routeTableResourceId: string? + + @description('Optional. An array of service endpoint policies.') + serviceEndpointPolicies: object[]? + + @description('Optional. The service endpoints to enable on the subnet.') + serviceEndpoints: string[]? + + @description('Optional. Set this property to false to disable default outbound connectivity for all VMs in the subnet. This property can only be set at the time of subnet creation and cannot be updated for an existing subnet.') + defaultOutboundAccess: bool? + + @description('Optional. Set this property to Tenant to allow sharing subnet with other subscriptions in your AAD tenant. This property can only be set if defaultOutboundAccess is set to false, both properties can only be set if subnet is empty.') + sharingScope: ('DelegatedServices' | 'Tenant')? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Bastion resource configuration.') +type bastionConfigurationType = { + @description('Optional. If the Bastion resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Bastion resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Bastion resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Bastion resource.') + tags: object? + + @description('Optional. The SKU for the Bastion resource.') + sku: ('Basic' | 'Developer' | 'Premium' | 'Standard')? + + @description('Optional. The Virtual Network resource id where the Bastion resource should be deployed.') + virtualNetworkResourceId: string? + + @description('Optional. The name of the Public Ip resource created to connect to Bastion.') + publicIpResourceName: string? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine virtual machine resource configuration.') +type virtualMachineConfigurationType = { + @description('Optional. If the Virtual Machine resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Virtual Machine resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Virtual Machine resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Virtual Machine resource.') + tags: object? + + @description('Optional. Specifies the size for the Virtual Machine resource.') + vmSize: ( + | 'Basic_A0' + | 'Basic_A1' + | 'Basic_A2' + | 'Basic_A3' + | 'Basic_A4' + | 'Standard_A0' + | 'Standard_A1' + | 'Standard_A2' + | 'Standard_A3' + | 'Standard_A4' + | 'Standard_A5' + | 'Standard_A6' + | 'Standard_A7' + | 'Standard_A8' + | 'Standard_A9' + | 'Standard_A10' + | 'Standard_A11' + | 'Standard_A1_v2' + | 'Standard_A2_v2' + | 'Standard_A4_v2' + | 'Standard_A8_v2' + | 'Standard_A2m_v2' + | 'Standard_A4m_v2' + | 'Standard_A8m_v2' + | 'Standard_B1s' + | 'Standard_B1ms' + | 'Standard_B2s' + | 'Standard_B2ms' + | 'Standard_B4ms' + | 'Standard_B8ms' + | 'Standard_D1' + | 'Standard_D2' + | 'Standard_D3' + | 'Standard_D4' + | 'Standard_D11' + | 'Standard_D12' + | 'Standard_D13' + | 'Standard_D14' + | 'Standard_D1_v2' + | 'Standard_D2_v2' + | 'Standard_D3_v2' + | 'Standard_D4_v2' + | 'Standard_D5_v2' + | 'Standard_D2_v3' + | 'Standard_D4_v3' + | 'Standard_D8_v3' + | 'Standard_D16_v3' + | 'Standard_D32_v3' + | 'Standard_D64_v3' + | 'Standard_D2s_v3' + | 'Standard_D4s_v3' + | 'Standard_D8s_v3' + | 'Standard_D16s_v3' + | 'Standard_D32s_v3' + | 'Standard_D64s_v3' + | 'Standard_D11_v2' + | 'Standard_D12_v2' + | 'Standard_D13_v2' + | 'Standard_D14_v2' + | 'Standard_D15_v2' + | 'Standard_DS1' + | 'Standard_DS2' + | 'Standard_DS3' + | 'Standard_DS4' + | 'Standard_DS11' + | 'Standard_DS12' + | 'Standard_DS13' + | 'Standard_DS14' + | 'Standard_DS1_v2' + | 'Standard_DS2_v2' + | 'Standard_DS3_v2' + | 'Standard_DS4_v2' + | 'Standard_DS5_v2' + | 'Standard_DS11_v2' + | 'Standard_DS12_v2' + | 'Standard_DS13_v2' + | 'Standard_DS14_v2' + | 'Standard_DS15_v2' + | 'Standard_DS13-4_v2' + | 'Standard_DS13-2_v2' + | 'Standard_DS14-8_v2' + | 'Standard_DS14-4_v2' + | 'Standard_E2_v3' + | 'Standard_E4_v3' + | 'Standard_E8_v3' + | 'Standard_E16_v3' + | 'Standard_E32_v3' + | 'Standard_E64_v3' + | 'Standard_E2s_v3' + | 'Standard_E4s_v3' + | 'Standard_E8s_v3' + | 'Standard_E16s_v3' + | 'Standard_E32s_v3' + | 'Standard_E64s_v3' + | 'Standard_E32-16_v3' + | 'Standard_E32-8s_v3' + | 'Standard_E64-32s_v3' + | 'Standard_E64-16s_v3' + | 'Standard_F1' + | 'Standard_F2' + | 'Standard_F4' + | 'Standard_F8' + | 'Standard_F16' + | 'Standard_F1s' + | 'Standard_F2s' + | 'Standard_F4s' + | 'Standard_F8s' + | 'Standard_F16s' + | 'Standard_F2s_v2' + | 'Standard_F4s_v2' + | 'Standard_F8s_v2' + | 'Standard_F16s_v2' + | 'Standard_F32s_v2' + | 'Standard_F64s_v2' + | 'Standard_F72s_v2' + | 'Standard_G1' + | 'Standard_G2' + | 'Standard_G3' + | 'Standard_G4' + | 'Standard_G5' + | 'Standard_GS1' + | 'Standard_GS2' + | 'Standard_GS3' + | 'Standard_GS4' + | 'Standard_GS5' + | 'Standard_GS4-8' + | 'Standard_GS4-4' + | 'Standard_GS5-16' + | 'Standard_GS5-8' + | 'Standard_H8' + | 'Standard_H16' + | 'Standard_H8m' + | 'Standard_H16m' + | 'Standard_H16r' + | 'Standard_H16mr' + | 'Standard_L4s' + | 'Standard_L8s' + | 'Standard_L16s' + | 'Standard_L32s' + | 'Standard_M64s' + | 'Standard_M64ms' + | 'Standard_M128s' + | 'Standard_M128ms' + | 'Standard_M64-32ms' + | 'Standard_M64-16ms' + | 'Standard_M128-64ms' + | 'Standard_M128-32ms' + | 'Standard_NC6' + | 'Standard_NC12' + | 'Standard_NC24' + | 'Standard_NC24r' + | 'Standard_NC6s_v2' + | 'Standard_NC12s_v2' + | 'Standard_NC24s_v2' + | 'Standard_NC24rs_v2' + | 'Standard_NC6s_v3' + | 'Standard_NC12s_v3' + | 'Standard_NC24s_v3' + | 'Standard_NC24rs_v3' + | 'Standard_ND6s' + | 'Standard_ND12s' + | 'Standard_ND24s' + | 'Standard_ND24rs' + | 'Standard_NV6' + | 'Standard_NV12' + | 'Standard_NV24')? + + @description('Optional. The username for the administrator account on the virtual machine. Required if a virtual machine is created as part of the module.') + adminUsername: string? + + @description('Optional. The password for the administrator account on the virtual machine. Required if a virtual machine is created as part of the module.') + @secure() + adminPassword: string? + + @description('Optional. The resource ID of the subnet where the Virtual Machine resource should be deployed.') + subnetResourceId: string? +} + +@export() +import { deploymentType } from 'br/public:avm/res/cognitive-services/account:0.10.2' +@description('The type for the Multi-Agent Custom Automation Engine AI Services resource configuration.') +type aiServicesConfigurationType = { + @description('Optional. If the AI Services resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the AI Services resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the AI Services resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the AI Services resource.') + tags: object? + + @description('Optional. The SKU of the AI Services resource. Use \'Get-AzCognitiveServicesAccountSku\' to determine a valid combinations of \'kind\' and \'SKU\' for your Azure region.') + sku: ( + | 'C2' + | 'C3' + | 'C4' + | 'F0' + | 'F1' + | 'S' + | 'S0' + | 'S1' + | 'S10' + | 'S2' + | 'S3' + | 'S4' + | 'S5' + | 'S6' + | 'S7' + | 'S8' + | 'S9')? + + @description('Optional. The resource Id of the subnet where the AI Services private endpoint should be created.') + subnetResourceId: string? + + @description('Optional. The model deployments to set for the AI Services resource.') + deployments: deploymentType[]? + + @description('Optional. The capacity to set for AI Services GTP model.') + modelCapacity: int? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Storage Account resource configuration.') +type storageAccountType = { + @description('Optional. If the Storage Account resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Storage Account resource.') + @maxLength(60) + name: string? + + @description('Optional. Location for the Storage Account resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Storage Account resource.') + tags: object? + + @description('Optional. The SKU for the Storage Account resource.') + sku: ('Standard_LRS' | 'Standard_GRS' | 'Standard_RAGRS' | 'Standard_ZRS' | 'Premium_LRS' | 'Premium_ZRS')? + + @description('Optional. The resource Id of the subnet where the Storage Account private endpoint should be created.') + subnetResourceId: string? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine AI Hub resource configuration.') +type aiHubType = { + @description('Optional. If the AI Hub resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the AI Hub resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the AI Hub resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the AI Hub resource.') + tags: object? + + @description('Optional. The SKU of the AI Hub resource.') + sku: ('Basic' | 'Free' | 'Standard' | 'Premium')? + + @description('Optional. The resource Id of the subnet where the AI Hub private endpoint should be created.') + subnetResourceId: string? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine AI Foundry AI Project resource configuration.') +type aiProjectConfigurationType = { + @description('Optional. If the AI Project resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the AI Project resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the AI Project resource deployment.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The SKU of the AI Project resource.') + sku: ('Basic' | 'Free' | 'Standard' | 'Premium')? + + @description('Optional. The tags to set for the AI Project resource.') + tags: object? +} + +import { sqlDatabaseType } from 'br/public:avm/res/document-db/database-account:0.13.0' +@export() +@description('The type for the Multi-Agent Custom Automation Engine Cosmos DB Account resource configuration.') +type cosmosDbAccountConfigurationType = { + @description('Optional. If the Cosmos DB Account resource should be deployed or not.') + enabled: bool? + @description('Optional. The name of the Cosmos DB Account resource.') + @maxLength(60) + name: string? + + @description('Optional. Location for the Cosmos DB Account resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Cosmos DB Account resource.') + tags: object? + + @description('Optional. The resource Id of the subnet where the Cosmos DB Account private endpoint should be created.') + subnetResourceId: string? + + @description('Optional. The SQL databases configuration for the Cosmos DB Account resource.') + sqlDatabases: sqlDatabaseType[]? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Container App Environment resource configuration.') +type containerAppEnvironmentConfigurationType = { + @description('Optional. If the Container App Environment resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Container App Environment resource.') + @maxLength(60) + name: string? + + @description('Optional. Location for the Container App Environment resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Container App Environment resource.') + tags: object? + + @description('Optional. The resource Id of the subnet where the Container App Environment private endpoint should be created.') + subnetResourceId: string? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Container App resource configuration.') +type containerAppConfigurationType = { + @description('Optional. If the Container App resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Container App resource.') + @maxLength(60) + name: string? + + @description('Optional. Location for the Container App resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Container App resource.') + tags: object? + + @description('Optional. The resource Id of the Container App Environment where the Container App should be created.') + environmentResourceId: string? + + @description('Optional. The maximum number of replicas of the Container App.') + maxReplicas: int? + + @description('Optional. The minimum number of replicas of the Container App.') + minReplicas: int? + + @description('Optional. The ingress target port of the Container App.') + ingressTargetPort: int? + + @description('Optional. The concurrent requests allowed for the Container App.') + concurrentRequests: string? + + @description('Optional. The name given to the Container App.') + containerName: string? + + @description('Optional. The container registry domain of the container image to be used by the Container App. Default to `biabcontainerreg.azurecr.io`') + containerImageRegistryDomain: string? + + @description('Optional. The name of the container image to be used by the Container App.') + containerImageName: string? + + @description('Optional. The tag of the container image to be used by the Container App.') + containerImageTag: string? + + @description('Optional. The CPU reserved for the Container App. Defaults to 2.0') + containerCpu: string? + + @description('Optional. The Memory reserved for the Container App. Defaults to 4.0Gi') + containerMemory: string? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Entra ID Application resource configuration.') +type entraIdApplicationConfigurationType = { + @description('Optional. If the Entra ID Application for website authentication should be deployed or not.') + enabled: bool? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Web Server Farm resource configuration.') +type webServerFarmConfigurationType = { + @description('Optional. If the Web Server Farm resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Web Server Farm resource.') + @maxLength(60) + name: string? + + @description('Optional. Location for the Web Server Farm resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Web Server Farm resource.') + tags: object? + + @description('Optional. The name of th SKU that will determine the tier, size and family for the Web Server Farm resource. This defaults to P1v3 to leverage availability zones.') + skuName: string? + + @description('Optional. Number of workers associated with the App Service Plan. This defaults to 3, to leverage availability zones.') + skuCapacity: int? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Web Site resource configuration.') +type webSiteConfigurationType = { + @description('Optional. If the Web Site resource should be deployed or not.') + enabled: bool? + + @description('Optional. The name of the Web Site resource.') + @maxLength(60) + name: string? + + @description('Optional. Location for the Web Site resource deployment.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Web Site resource.') + tags: object? + + @description('Optional. The resource Id of the Web Site Environment where the Web Site should be created.') + environmentResourceId: string? + + @description('Optional. The name given to the Container App.') + containerName: string? + + @description('Optional. The container registry domain of the container image to be used by the Web Site. Default to `biabcontainerreg.azurecr.io`') + containerImageRegistryDomain: string? + + @description('Optional. The name of the container image to be used by the Web Site.') + containerImageName: string? + + @description('Optional. The tag of the container image to be used by the Web Site.') + containerImageTag: string? } diff --git a/infra/main.bicepparam b/infra/main.bicepparam index a95a9f06b..ade5bbf3d 100644 --- a/infra/main.bicepparam +++ b/infra/main.bicepparam @@ -1,5 +1,21 @@ using './main.bicep' -param environmentName = readEnvironmentVariable('AZURE_ENV_NAME', 'macaetemplate') -param AZURE_LOCATION = readEnvironmentVariable('AZURE_LOCATION', '') -param azureOpenAILocation = readEnvironmentVariable('AZURE_OPENAI_LOCATION', 'eastus2') +param solutionPrefix = null //Type a string value to customize the prefix for your resource names +param solutionLocation = readEnvironmentVariable('AZURE_LOCATION', 'swedencentral') +param azureOpenAILocation = readEnvironmentVariable('AZURE_ENV_OPENAI_LOCATION', 'swedencentral') +param logAnalyticsWorkspaceConfiguration = { + dataRetentionInDays: 30 +} +param applicationInsightsConfiguration = { + retentionInDays: 30 +} +param virtualNetworkConfiguration = { + enabled: false +} +param aiFoundryStorageAccountConfiguration = { + sku: 'Standard_LRS' +} +param webServerFarmConfiguration = { + skuCapacity: 1 + skuName: 'B2' +} diff --git a/infra/main.json b/infra/main.json deleted file mode 100644 index 010b65f6b..000000000 --- a/infra/main.json +++ /dev/null @@ -1,2197 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.35.1.17967", - "templateHash": "6559422175999367984" - } - }, - "parameters": { - "azureOpenAILocation": { - "type": "string", - "allowedValues": [ - "australiaeast", - "brazilsouth", - "canadacentral", - "canadaeast", - "eastus", - "eastus2", - "francecentral", - "germanywestcentral", - "japaneast", - "koreacentral", - "northcentralus", - "norwayeast", - "polandcentral", - "southafricanorth", - "southcentralus", - "southindia", - "swedencentral", - "switzerlandnorth", - "uaenorth", - "uksouth", - "westeurope", - "westus", - "westus3" - ], - "metadata": { - "description": "Location for all Ai services resources. This location can be different from the resource group location." - } - }, - "environmentName": { - "type": "string", - "minLength": 3, - "maxLength": 20, - "metadata": { - "description": "A unique prefix for all resources in this deployment. This should be 3-20 characters long:" - } - }, - "AZURE_LOCATION": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Set this if you want to deploy to a different region than the resource group. Otherwise, it will use the resource group location by default." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Tags to apply to all deployed resources" - } - }, - "resourceSize": { - "type": "object", - "properties": { - "gpt4oCapacity": { - "type": "int" - }, - "containerAppSize": { - "type": "object", - "properties": { - "cpu": { - "type": "string" - }, - "memory": { - "type": "string" - }, - "minReplicas": { - "type": "int" - }, - "maxReplicas": { - "type": "int" - } - } - } - }, - "defaultValue": { - "gpt4oCapacity": 1, - "containerAppSize": { - "cpu": "2.0", - "memory": "4.0Gi", - "minReplicas": 1, - "maxReplicas": 1 - } - }, - "metadata": { - "description": "The size of the resources to deploy, defaults to a mini size" - } - }, - "capacity": { - "type": "int", - "defaultValue": 140 - } - }, - "variables": { - "$fxv#0": { - "ai": { - "aiSearch": "srch-", - "aiServices": "aisa-", - "aiVideoIndexer": "avi-", - "machineLearningWorkspace": "mlw-", - "openAIService": "oai-", - "botService": "bot-", - "computerVision": "cv-", - "contentModerator": "cm-", - "contentSafety": "cs-", - "customVisionPrediction": "cstv-", - "customVisionTraining": "cstvt-", - "documentIntelligence": "di-", - "faceApi": "face-", - "healthInsights": "hi-", - "immersiveReader": "ir-", - "languageService": "lang-", - "speechService": "spch-", - "translator": "trsl-", - "aiHub": "aih-", - "aiHubProject": "aihp-" - }, - "analytics": { - "analysisServicesServer": "as", - "databricksWorkspace": "dbw-", - "dataExplorerCluster": "dec", - "dataExplorerClusterDatabase": "dedb", - "dataFactory": "adf-", - "digitalTwin": "dt-", - "streamAnalytics": "asa-", - "synapseAnalyticsPrivateLinkHub": "synplh-", - "synapseAnalyticsSQLDedicatedPool": "syndp", - "synapseAnalyticsSparkPool": "synsp", - "synapseAnalyticsWorkspaces": "synw", - "dataLakeStoreAccount": "dls", - "dataLakeAnalyticsAccount": "dla", - "eventHubsNamespace": "evhns-", - "eventHub": "evh-", - "eventGridDomain": "evgd-", - "eventGridSubscriptions": "evgs-", - "eventGridTopic": "evgt-", - "eventGridSystemTopic": "egst-", - "hdInsightHadoopCluster": "hadoop-", - "hdInsightHBaseCluster": "hbase-", - "hdInsightKafkaCluster": "kafka-", - "hdInsightSparkCluster": "spark-", - "hdInsightStormCluster": "storm-", - "hdInsightMLServicesCluster": "mls-", - "iotHub": "iot-", - "provisioningServices": "provs-", - "provisioningServicesCertificate": "pcert-", - "powerBIEmbedded": "pbi-", - "timeSeriesInsightsEnvironment": "tsi-" - }, - "compute": { - "appServiceEnvironment": "ase-", - "appServicePlan": "asp-", - "loadTesting": "lt-", - "availabilitySet": "avail-", - "arcEnabledServer": "arcs-", - "arcEnabledKubernetesCluster": "arck", - "batchAccounts": "ba-", - "cloudService": "cld-", - "communicationServices": "acs-", - "diskEncryptionSet": "des", - "functionApp": "func-", - "gallery": "gal", - "hostingEnvironment": "host-", - "imageTemplate": "it-", - "managedDiskOS": "osdisk", - "managedDiskData": "disk", - "notificationHubs": "ntf-", - "notificationHubsNamespace": "ntfns-", - "proximityPlacementGroup": "ppg-", - "restorePointCollection": "rpc-", - "snapshot": "snap-", - "staticWebApp": "stapp-", - "virtualMachine": "vm", - "virtualMachineScaleSet": "vmss-", - "virtualMachineMaintenanceConfiguration": "mc-", - "virtualMachineStorageAccount": "stvm", - "webApp": "app-" - }, - "containers": { - "aksCluster": "aks-", - "aksSystemNodePool": "npsystem-", - "aksUserNodePool": "np-", - "containerApp": "ca-", - "containerAppsEnvironment": "cae-", - "containerRegistry": "cr", - "containerInstance": "ci", - "serviceFabricCluster": "sf-", - "serviceFabricManagedCluster": "sfmc-" - }, - "databases": { - "cosmosDBDatabase": "cosmos-", - "cosmosDBApacheCassandra": "coscas-", - "cosmosDBMongoDB": "cosmon-", - "cosmosDBNoSQL": "cosno-", - "cosmosDBTable": "costab-", - "cosmosDBGremlin": "cosgrm-", - "cosmosDBPostgreSQL": "cospos-", - "cacheForRedis": "redis-", - "sqlDatabaseServer": "sql-", - "sqlDatabase": "sqldb-", - "sqlElasticJobAgent": "sqlja-", - "sqlElasticPool": "sqlep-", - "mariaDBServer": "maria-", - "mariaDBDatabase": "mariadb-", - "mySQLDatabase": "mysql-", - "postgreSQLDatabase": "psql-", - "sqlServerStretchDatabase": "sqlstrdb-", - "sqlManagedInstance": "sqlmi-" - }, - "developerTools": { - "appConfigurationStore": "appcs-", - "mapsAccount": "map-", - "signalR": "sigr", - "webPubSub": "wps-" - }, - "devOps": { - "managedGrafana": "amg-" - }, - "integration": { - "apiManagementService": "apim-", - "integrationAccount": "ia-", - "logicApp": "logic-", - "serviceBusNamespace": "sbns-", - "serviceBusQueue": "sbq-", - "serviceBusTopic": "sbt-", - "serviceBusTopicSubscription": "sbts-" - }, - "managementGovernance": { - "automationAccount": "aa-", - "applicationInsights": "appi-", - "monitorActionGroup": "ag-", - "monitorDataCollectionRules": "dcr-", - "monitorAlertProcessingRule": "apr-", - "blueprint": "bp-", - "blueprintAssignment": "bpa-", - "dataCollectionEndpoint": "dce-", - "logAnalyticsWorkspace": "log-", - "logAnalyticsQueryPacks": "pack-", - "managementGroup": "mg-", - "purviewInstance": "pview-", - "resourceGroup": "rg-", - "templateSpecsName": "ts-" - }, - "migration": { - "migrateProject": "migr-", - "databaseMigrationService": "dms-", - "recoveryServicesVault": "rsv-" - }, - "networking": { - "applicationGateway": "agw-", - "applicationSecurityGroup": "asg-", - "cdnProfile": "cdnp-", - "cdnEndpoint": "cdne-", - "connections": "con-", - "dnsForwardingRuleset": "dnsfrs-", - "dnsPrivateResolver": "dnspr-", - "dnsPrivateResolverInboundEndpoint": "in-", - "dnsPrivateResolverOutboundEndpoint": "out-", - "firewall": "afw-", - "firewallPolicy": "afwp-", - "expressRouteCircuit": "erc-", - "expressRouteGateway": "ergw-", - "frontDoorProfile": "afd-", - "frontDoorEndpoint": "fde-", - "frontDoorFirewallPolicy": "fdfp-", - "ipGroups": "ipg-", - "loadBalancerInternal": "lbi-", - "loadBalancerExternal": "lbe-", - "loadBalancerRule": "rule-", - "localNetworkGateway": "lgw-", - "natGateway": "ng-", - "networkInterface": "nic-", - "networkSecurityGroup": "nsg-", - "networkSecurityGroupSecurityRules": "nsgsr-", - "networkWatcher": "nw-", - "privateLink": "pl-", - "privateEndpoint": "pep-", - "publicIPAddress": "pip-", - "publicIPAddressPrefix": "ippre-", - "routeFilter": "rf-", - "routeServer": "rtserv-", - "routeTable": "rt-", - "serviceEndpointPolicy": "se-", - "trafficManagerProfile": "traf-", - "userDefinedRoute": "udr-", - "virtualNetwork": "vnet-", - "virtualNetworkGateway": "vgw-", - "virtualNetworkManager": "vnm-", - "virtualNetworkPeering": "peer-", - "virtualNetworkSubnet": "snet-", - "virtualWAN": "vwan-", - "virtualWANHub": "vhub-" - }, - "security": { - "bastion": "bas-", - "keyVault": "kv-", - "keyVaultManagedHSM": "kvmhsm-", - "managedIdentity": "id-", - "sshKey": "sshkey-", - "vpnGateway": "vpng-", - "vpnConnection": "vcn-", - "vpnSite": "vst-", - "webApplicationFirewallPolicy": "waf", - "webApplicationFirewallPolicyRuleGroup": "wafrg" - }, - "storage": { - "storSimple": "ssimp", - "backupVault": "bvault-", - "backupVaultPolicy": "bkpol-", - "fileShare": "share-", - "storageAccount": "st", - "storageSyncService": "sss-" - }, - "virtualDesktop": { - "labServicesPlan": "lp-", - "virtualDesktopHostPool": "vdpool-", - "virtualDesktopApplicationGroup": "vdag-", - "virtualDesktopWorkspace": "vdws-", - "virtualDesktopScalingPlan": "vdscaling-" - } - }, - "solutionLocation": "[if(empty(parameters('AZURE_LOCATION')), resourceGroup().location, parameters('AZURE_LOCATION'))]", - "uniqueId": "[toLower(uniqueString(subscription().id, parameters('environmentName'), variables('solutionLocation')))]", - "solutionPrefix": "[format('ma{0}', padLeft(take(variables('uniqueId'), 12), 12, '0'))]", - "abbrs": "[variables('$fxv#0')]", - "modelVersion": "2024-08-06", - "aiServicesName": "[format('{0}{1}', variables('abbrs').ai.aiServices, variables('solutionPrefix'))]", - "deploymentType": "GlobalStandard", - "gptModelVersion": "gpt-4o", - "appVersion": "latest", - "resgistryName": "biabcontainerreg", - "dockerRegistryUrl": "[format('https://{0}.azurecr.io', variables('resgistryName'))]", - "backendDockerImageURL": "[format('{0}.azurecr.io/macaebackend:{1}', variables('resgistryName'), variables('appVersion'))]", - "frontendDockerImageURL": "[format('{0}.azurecr.io/macaefrontend:{1}', variables('resgistryName'), variables('appVersion'))]", - "aoaiApiVersion": "2025-01-01-preview", - "aiModelDeployments": [ - { - "name": "[variables('gptModelVersion')]", - "model": "[variables('gptModelVersion')]", - "version": "[variables('modelVersion')]", - "sku": { - "name": "[variables('deploymentType')]", - "capacity": "[parameters('capacity')]" - }, - "raiPolicyName": "Microsoft.Default" - } - ] - }, - "resources": { - "cosmos::macaeDb::memoryContainer": { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers", - "apiVersion": "2024-05-15", - "name": "[format('{0}/{1}/{2}', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), 'macae', 'memory')]", - "properties": { - "resource": { - "id": "memory", - "partitionKey": { - "kind": "Hash", - "version": 2, - "paths": [ - "/session_id" - ] - } - } - }, - "dependsOn": [ - "cosmos::macaeDb" - ] - }, - "cosmos::contributorRoleDefinition": { - "existing": true, - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions", - "apiVersion": "2024-05-15", - "name": "[format('{0}/{1}', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), '00000000-0000-0000-0000-000000000002')]", - "dependsOn": [ - "cosmos" - ] - }, - "cosmos::macaeDb": { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlDatabases", - "apiVersion": "2024-05-15", - "name": "[format('{0}/{1}', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), 'macae')]", - "properties": { - "resource": { - "id": "macae", - "createMode": "Default" - } - }, - "dependsOn": [ - "cosmos" - ] - }, - "containerAppEnv::aspireDashboard": { - "type": "Microsoft.App/managedEnvironments/dotNetComponents", - "apiVersion": "2024-02-02-preview", - "name": "[format('{0}/{1}', format('{0}{1}', variables('abbrs').containers.containerAppsEnvironment, variables('solutionPrefix')), 'aspire-dashboard')]", - "properties": { - "componentType": "AspireDashboard" - }, - "dependsOn": [ - "containerAppEnv" - ] - }, - "logAnalytics": { - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2023-09-01", - "name": "[format('{0}{1}', variables('abbrs').managementGovernance.logAnalyticsWorkspace, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "tags": "[parameters('tags')]", - "properties": { - "retentionInDays": 30, - "sku": { - "name": "PerGB2018" - } - } - }, - "appInsights": { - "type": "Microsoft.Insights/components", - "apiVersion": "2020-02-02-preview", - "name": "[format('{0}{1}', variables('abbrs').managementGovernance.applicationInsights, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "kind": "web", - "properties": { - "Application_Type": "web", - "WorkspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', format('{0}{1}', variables('abbrs').managementGovernance.logAnalyticsWorkspace, variables('solutionPrefix')))]" - }, - "dependsOn": [ - "logAnalytics" - ] - }, - "aiServices": { - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2024-04-01-preview", - "name": "[variables('aiServicesName')]", - "location": "[parameters('azureOpenAILocation')]", - "sku": { - "name": "S0" - }, - "kind": "AIServices", - "properties": { - "customSubDomainName": "[variables('aiServicesName')]", - "apiProperties": {}, - "disableLocalAuth": true, - "publicNetworkAccess": "Enabled" - } - }, - "aiServicesDeployments": { - "copy": { - "name": "aiServicesDeployments", - "count": "[length(variables('aiModelDeployments'))]" - }, - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2023-05-01", - "name": "[format('{0}/{1}', variables('aiServicesName'), variables('aiModelDeployments')[copyIndex()].name)]", - "properties": { - "model": { - "format": "OpenAI", - "name": "[variables('aiModelDeployments')[copyIndex()].model]", - "version": "[variables('aiModelDeployments')[copyIndex()].version]" - }, - "raiPolicyName": "[variables('aiModelDeployments')[copyIndex()].raiPolicyName]" - }, - "sku": { - "name": "[variables('aiModelDeployments')[copyIndex()].sku.name]", - "capacity": "[variables('aiModelDeployments')[copyIndex()].sku.capacity]" - }, - "dependsOn": [ - "aiServices" - ] - }, - "aoaiUserRoleDefinition": { - "existing": true, - "type": "Microsoft.Authorization/roleDefinitions", - "apiVersion": "2022-05-01-preview", - "name": "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd" - }, - "acaAoaiRoleAssignment": { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('aiServicesName'))]", - "name": "[guid(resourceId('Microsoft.App/containerApps', format('{0}{1}-backend', variables('abbrs').containers.containerApp, variables('solutionPrefix'))), resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName')), resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'))]", - "properties": { - "principalId": "[reference('containerApp', '2024-03-01', 'full').identity.principalId]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "aiServices", - "containerApp" - ] - }, - "cosmos": { - "type": "Microsoft.DocumentDB/databaseAccounts", - "apiVersion": "2024-05-15", - "name": "[format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "tags": "[parameters('tags')]", - "kind": "GlobalDocumentDB", - "properties": { - "databaseAccountOfferType": "Standard", - "enableFreeTier": false, - "locations": [ - { - "failoverPriority": 0, - "locationName": "[variables('solutionLocation')]" - } - ], - "capabilities": [ - { - "name": "EnableServerless" - } - ], - "disableLocalAuth": true - } - }, - "pullIdentity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-07-31-preview", - "name": "[format('{0}{1}-containerapp-pull', variables('abbrs').security.managedIdentity, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]" - }, - "containerAppEnv": { - "type": "Microsoft.App/managedEnvironments", - "apiVersion": "2024-03-01", - "name": "[format('{0}{1}', variables('abbrs').containers.containerAppsEnvironment, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "tags": "[parameters('tags')]", - "properties": { - "daprAIConnectionString": "[reference('appInsights').ConnectionString]", - "appLogsConfiguration": { - "destination": "log-analytics", - "logAnalyticsConfiguration": { - "customerId": "[reference('logAnalytics').customerId]", - "sharedKey": "[listKeys('logAnalytics', '2023-09-01').primarySharedKey]" - } - } - }, - "dependsOn": [ - "appInsights", - "logAnalytics" - ] - }, - "acaCosomsRoleAssignment": { - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", - "apiVersion": "2024-05-15", - "name": "[format('{0}/{1}', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), guid(resourceId('Microsoft.App/containerApps', format('{0}{1}-backend', variables('abbrs').containers.containerApp, variables('solutionPrefix'))), resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), '00000000-0000-0000-0000-000000000002')))]", - "properties": { - "principalId": "[reference('containerApp', '2024-03-01', 'full').identity.principalId]", - "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), '00000000-0000-0000-0000-000000000002')]", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')))]" - }, - "dependsOn": [ - "containerApp", - "cosmos" - ] - }, - "containerApp": { - "type": "Microsoft.App/containerApps", - "apiVersion": "2024-03-01", - "name": "[format('{0}{1}-backend', variables('abbrs').containers.containerApp, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "tags": "[parameters('tags')]", - "identity": { - "type": "SystemAssigned, UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}{1}-containerapp-pull', variables('abbrs').security.managedIdentity, variables('solutionPrefix'))))]": {} - } - }, - "properties": { - "managedEnvironmentId": "[resourceId('Microsoft.App/managedEnvironments', format('{0}{1}', variables('abbrs').containers.containerAppsEnvironment, variables('solutionPrefix')))]", - "configuration": { - "ingress": { - "targetPort": 8000, - "external": true, - "corsPolicy": { - "allowedOrigins": [ - "[format('https://{0}{1}-frontend.azurewebsites.net', variables('abbrs').compute.webApp, variables('solutionPrefix'))]", - "[format('http://{0}{1}-frontend.azurewebsites.net', variables('abbrs').compute.webApp, variables('solutionPrefix'))]" - ] - } - }, - "activeRevisionsMode": "Single" - }, - "template": { - "scale": { - "minReplicas": "[parameters('resourceSize').containerAppSize.minReplicas]", - "maxReplicas": "[parameters('resourceSize').containerAppSize.maxReplicas]", - "rules": [ - { - "name": "http-scaler", - "http": { - "metadata": { - "concurrentRequests": "100" - } - } - } - ] - }, - "containers": [ - { - "name": "backend", - "image": "[variables('backendDockerImageURL')]", - "resources": { - "cpu": "[json(parameters('resourceSize').containerAppSize.cpu)]", - "memory": "[parameters('resourceSize').containerAppSize.memory]" - }, - "env": [ - { - "name": "COSMOSDB_ENDPOINT", - "value": "[reference('cosmos').documentEndpoint]" - }, - { - "name": "COSMOSDB_DATABASE", - "value": "macae" - }, - { - "name": "COSMOSDB_CONTAINER", - "value": "memory" - }, - { - "name": "AZURE_OPENAI_ENDPOINT", - "value": "[replace(reference('aiServices').endpoint, 'cognitiveservices.azure.com', 'openai.azure.com')]" - }, - { - "name": "AZURE_OPENAI_MODEL_NAME", - "value": "[variables('gptModelVersion')]" - }, - { - "name": "AZURE_OPENAI_DEPLOYMENT_NAME", - "value": "[variables('gptModelVersion')]" - }, - { - "name": "AZURE_OPENAI_API_VERSION", - "value": "[variables('aoaiApiVersion')]" - }, - { - "name": "APPLICATIONINSIGHTS_INSTRUMENTATION_KEY", - "value": "[reference('appInsights').InstrumentationKey]" - }, - { - "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", - "value": "[reference('appInsights').ConnectionString]" - }, - { - "name": "AZURE_AI_AGENT_PROJECT_CONNECTION_STRING", - "value": "[reference('aifoundry').outputs.projectConnectionString.value]" - }, - { - "name": "AZURE_AI_SUBSCRIPTION_ID", - "value": "[subscription().subscriptionId]" - }, - { - "name": "AZURE_AI_RESOURCE_GROUP", - "value": "[resourceGroup().name]" - }, - { - "name": "AZURE_AI_PROJECT_NAME", - "value": "[reference('aifoundry').outputs.aiProjectName.value]" - }, - { - "name": "FRONTEND_SITE_NAME", - "value": "[format('https://{0}{1}-frontend.azurewebsites.net', variables('abbrs').compute.webApp, variables('solutionPrefix'))]" - } - ] - } - ] - } - }, - "dependsOn": [ - "aifoundry", - "aiServices", - "appInsights", - "containerAppEnv", - "cosmos", - "cosmos::macaeDb", - "cosmos::macaeDb::memoryContainer", - "pullIdentity" - ], - "metadata": { - "description": "" - } - }, - "frontendAppServicePlan": { - "type": "Microsoft.Web/serverfarms", - "apiVersion": "2021-02-01", - "name": "[format('{0}{1}-frontend', variables('abbrs').compute.appServicePlan, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "tags": "[parameters('tags')]", - "sku": { - "name": "B2", - "capacity": 1, - "tier": "Basic" - }, - "properties": { - "reserved": true - }, - "kind": "linux" - }, - "frontendAppService": { - "type": "Microsoft.Web/sites", - "apiVersion": "2021-02-01", - "name": "[format('{0}{1}-frontend', variables('abbrs').compute.webApp, variables('solutionPrefix'))]", - "location": "[variables('solutionLocation')]", - "tags": "[parameters('tags')]", - "kind": "app,linux,container", - "properties": { - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', format('{0}{1}-frontend', variables('abbrs').compute.appServicePlan, variables('solutionPrefix')))]", - "reserved": true, - "siteConfig": { - "linuxFxVersion": "[format('DOCKER|{0}', variables('frontendDockerImageURL'))]", - "appSettings": [ - { - "name": "DOCKER_REGISTRY_SERVER_URL", - "value": "[variables('dockerRegistryUrl')]" - }, - { - "name": "WEBSITES_PORT", - "value": "3000" - }, - { - "name": "WEBSITES_CONTAINER_START_TIME_LIMIT", - "value": "1800" - }, - { - "name": "BACKEND_API_URL", - "value": "[format('https://{0}', reference('containerApp').configuration.ingress.fqdn)]" - }, - { - "name": "AUTH_ENABLED", - "value": "false" - } - ] - } - }, - "identity": { - "type": "SystemAssigned, UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}{1}-containerapp-pull', variables('abbrs').security.managedIdentity, variables('solutionPrefix'))))]": {} - } - }, - "dependsOn": [ - "containerApp", - "frontendAppServicePlan", - "pullIdentity" - ] - }, - "aiHubProject": { - "existing": true, - "type": "Microsoft.MachineLearningServices/workspaces", - "apiVersion": "2024-01-01-preview", - "name": "[format('{0}{1}', variables('abbrs').ai.aiHubProject, variables('solutionPrefix'))]" - }, - "aiDeveloper": { - "existing": true, - "type": "Microsoft.Authorization/roleDefinitions", - "apiVersion": "2022-04-01", - "name": "64702f94-c441-49e6-a78b-ef80e0188fee" - }, - "aiDeveloperAccessProj": { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.MachineLearningServices/workspaces/{0}', format('{0}{1}', variables('abbrs').ai.aiHubProject, variables('solutionPrefix')))]", - "name": "[guid(format('{0}{1}-backend', variables('abbrs').containers.containerApp, variables('solutionPrefix')), resourceId('Microsoft.MachineLearningServices/workspaces', format('{0}{1}', variables('abbrs').ai.aiHubProject, variables('solutionPrefix'))), resourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]", - "principalId": "[reference('containerApp', '2024-03-01', 'full').identity.principalId]" - }, - "dependsOn": [ - "containerApp" - ] - }, - "kvault": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploy_keyvault", - "resourceGroup": "[resourceGroup().name]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "solutionLocation": { - "value": "[variables('solutionLocation')]" - }, - "managedIdentityObjectId": { - "value": "[reference('managedIdentityModule').outputs.managedIdentityOutput.value.objectId]" - }, - "keyvaultName": { - "value": "[format('{0}{1}', variables('abbrs').security.keyVault, variables('solutionPrefix'))]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.35.1.17967", - "templateHash": "7119862929918770475" - } - }, - "parameters": { - "solutionLocation": { - "type": "string" - }, - "managedIdentityObjectId": { - "type": "string" - }, - "keyvaultName": { - "type": "string", - "metadata": { - "description": "KeyVault Name" - } - } - }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2022-07-01", - "name": "[parameters('keyvaultName')]", - "location": "[parameters('solutionLocation')]", - "properties": { - "createMode": "default", - "accessPolicies": [ - { - "objectId": "[parameters('managedIdentityObjectId')]", - "permissions": { - "certificates": [ - "all" - ], - "keys": [ - "all" - ], - "secrets": [ - "all" - ], - "storage": [ - "all" - ] - }, - "tenantId": "[subscription().tenantId]" - } - ], - "enabledForDeployment": true, - "enabledForDiskEncryption": true, - "enabledForTemplateDeployment": true, - "enableRbacAuthorization": true, - "publicNetworkAccess": "enabled", - "sku": { - "family": "A", - "name": "standard" - }, - "softDeleteRetentionInDays": 7, - "tenantId": "[subscription().tenantId]" - } - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceGroup().id, parameters('managedIdentityObjectId'), resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483'))]", - "properties": { - "principalId": "[parameters('managedIdentityObjectId')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '00482a5a-887f-4fb3-b363-3b7fe8e74483')]", - "principalType": "ServicePrincipal" - } - } - ], - "outputs": { - "keyvaultName": { - "type": "string", - "value": "[parameters('keyvaultName')]" - }, - "keyvaultId": { - "type": "string", - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyvaultName'))]" - } - } - } - }, - "dependsOn": [ - "managedIdentityModule" - ] - }, - "aifoundry": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploy_ai_foundry", - "resourceGroup": "[resourceGroup().name]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "solutionName": { - "value": "[variables('solutionPrefix')]" - }, - "solutionLocation": { - "value": "[parameters('azureOpenAILocation')]" - }, - "keyVaultName": { - "value": "[reference('kvault').outputs.keyvaultName.value]" - }, - "gptModelName": { - "value": "[variables('gptModelVersion')]" - }, - "gptModelVersion": { - "value": "[variables('gptModelVersion')]" - }, - "managedIdentityObjectId": { - "value": "[reference('managedIdentityModule').outputs.managedIdentityOutput.value.objectId]" - }, - "aiServicesEndpoint": { - "value": "[reference('aiServices').endpoint]" - }, - "aiServicesKey": { - "value": "[listKeys('aiServices', '2024-04-01-preview').key1]" - }, - "aiServicesId": { - "value": "[resourceId('Microsoft.CognitiveServices/accounts', variables('aiServicesName'))]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.35.1.17967", - "templateHash": "13047093698365252995" - } - }, - "parameters": { - "solutionName": { - "type": "string" - }, - "solutionLocation": { - "type": "string" - }, - "keyVaultName": { - "type": "string" - }, - "gptModelName": { - "type": "string" - }, - "gptModelVersion": { - "type": "string" - }, - "managedIdentityObjectId": { - "type": "string" - }, - "aiServicesEndpoint": { - "type": "string" - }, - "aiServicesKey": { - "type": "string" - }, - "aiServicesId": { - "type": "string" - } - }, - "variables": { - "$fxv#0": { - "ai": { - "aiSearch": "srch-", - "aiServices": "aisa-", - "aiVideoIndexer": "avi-", - "machineLearningWorkspace": "mlw-", - "openAIService": "oai-", - "botService": "bot-", - "computerVision": "cv-", - "contentModerator": "cm-", - "contentSafety": "cs-", - "customVisionPrediction": "cstv-", - "customVisionTraining": "cstvt-", - "documentIntelligence": "di-", - "faceApi": "face-", - "healthInsights": "hi-", - "immersiveReader": "ir-", - "languageService": "lang-", - "speechService": "spch-", - "translator": "trsl-", - "aiHub": "aih-", - "aiHubProject": "aihp-" - }, - "analytics": { - "analysisServicesServer": "as", - "databricksWorkspace": "dbw-", - "dataExplorerCluster": "dec", - "dataExplorerClusterDatabase": "dedb", - "dataFactory": "adf-", - "digitalTwin": "dt-", - "streamAnalytics": "asa-", - "synapseAnalyticsPrivateLinkHub": "synplh-", - "synapseAnalyticsSQLDedicatedPool": "syndp", - "synapseAnalyticsSparkPool": "synsp", - "synapseAnalyticsWorkspaces": "synw", - "dataLakeStoreAccount": "dls", - "dataLakeAnalyticsAccount": "dla", - "eventHubsNamespace": "evhns-", - "eventHub": "evh-", - "eventGridDomain": "evgd-", - "eventGridSubscriptions": "evgs-", - "eventGridTopic": "evgt-", - "eventGridSystemTopic": "egst-", - "hdInsightHadoopCluster": "hadoop-", - "hdInsightHBaseCluster": "hbase-", - "hdInsightKafkaCluster": "kafka-", - "hdInsightSparkCluster": "spark-", - "hdInsightStormCluster": "storm-", - "hdInsightMLServicesCluster": "mls-", - "iotHub": "iot-", - "provisioningServices": "provs-", - "provisioningServicesCertificate": "pcert-", - "powerBIEmbedded": "pbi-", - "timeSeriesInsightsEnvironment": "tsi-" - }, - "compute": { - "appServiceEnvironment": "ase-", - "appServicePlan": "asp-", - "loadTesting": "lt-", - "availabilitySet": "avail-", - "arcEnabledServer": "arcs-", - "arcEnabledKubernetesCluster": "arck", - "batchAccounts": "ba-", - "cloudService": "cld-", - "communicationServices": "acs-", - "diskEncryptionSet": "des", - "functionApp": "func-", - "gallery": "gal", - "hostingEnvironment": "host-", - "imageTemplate": "it-", - "managedDiskOS": "osdisk", - "managedDiskData": "disk", - "notificationHubs": "ntf-", - "notificationHubsNamespace": "ntfns-", - "proximityPlacementGroup": "ppg-", - "restorePointCollection": "rpc-", - "snapshot": "snap-", - "staticWebApp": "stapp-", - "virtualMachine": "vm", - "virtualMachineScaleSet": "vmss-", - "virtualMachineMaintenanceConfiguration": "mc-", - "virtualMachineStorageAccount": "stvm", - "webApp": "app-" - }, - "containers": { - "aksCluster": "aks-", - "aksSystemNodePool": "npsystem-", - "aksUserNodePool": "np-", - "containerApp": "ca-", - "containerAppsEnvironment": "cae-", - "containerRegistry": "cr", - "containerInstance": "ci", - "serviceFabricCluster": "sf-", - "serviceFabricManagedCluster": "sfmc-" - }, - "databases": { - "cosmosDBDatabase": "cosmos-", - "cosmosDBApacheCassandra": "coscas-", - "cosmosDBMongoDB": "cosmon-", - "cosmosDBNoSQL": "cosno-", - "cosmosDBTable": "costab-", - "cosmosDBGremlin": "cosgrm-", - "cosmosDBPostgreSQL": "cospos-", - "cacheForRedis": "redis-", - "sqlDatabaseServer": "sql-", - "sqlDatabase": "sqldb-", - "sqlElasticJobAgent": "sqlja-", - "sqlElasticPool": "sqlep-", - "mariaDBServer": "maria-", - "mariaDBDatabase": "mariadb-", - "mySQLDatabase": "mysql-", - "postgreSQLDatabase": "psql-", - "sqlServerStretchDatabase": "sqlstrdb-", - "sqlManagedInstance": "sqlmi-" - }, - "developerTools": { - "appConfigurationStore": "appcs-", - "mapsAccount": "map-", - "signalR": "sigr", - "webPubSub": "wps-" - }, - "devOps": { - "managedGrafana": "amg-" - }, - "integration": { - "apiManagementService": "apim-", - "integrationAccount": "ia-", - "logicApp": "logic-", - "serviceBusNamespace": "sbns-", - "serviceBusQueue": "sbq-", - "serviceBusTopic": "sbt-", - "serviceBusTopicSubscription": "sbts-" - }, - "managementGovernance": { - "automationAccount": "aa-", - "applicationInsights": "appi-", - "monitorActionGroup": "ag-", - "monitorDataCollectionRules": "dcr-", - "monitorAlertProcessingRule": "apr-", - "blueprint": "bp-", - "blueprintAssignment": "bpa-", - "dataCollectionEndpoint": "dce-", - "logAnalyticsWorkspace": "log-", - "logAnalyticsQueryPacks": "pack-", - "managementGroup": "mg-", - "purviewInstance": "pview-", - "resourceGroup": "rg-", - "templateSpecsName": "ts-" - }, - "migration": { - "migrateProject": "migr-", - "databaseMigrationService": "dms-", - "recoveryServicesVault": "rsv-" - }, - "networking": { - "applicationGateway": "agw-", - "applicationSecurityGroup": "asg-", - "cdnProfile": "cdnp-", - "cdnEndpoint": "cdne-", - "connections": "con-", - "dnsForwardingRuleset": "dnsfrs-", - "dnsPrivateResolver": "dnspr-", - "dnsPrivateResolverInboundEndpoint": "in-", - "dnsPrivateResolverOutboundEndpoint": "out-", - "firewall": "afw-", - "firewallPolicy": "afwp-", - "expressRouteCircuit": "erc-", - "expressRouteGateway": "ergw-", - "frontDoorProfile": "afd-", - "frontDoorEndpoint": "fde-", - "frontDoorFirewallPolicy": "fdfp-", - "ipGroups": "ipg-", - "loadBalancerInternal": "lbi-", - "loadBalancerExternal": "lbe-", - "loadBalancerRule": "rule-", - "localNetworkGateway": "lgw-", - "natGateway": "ng-", - "networkInterface": "nic-", - "networkSecurityGroup": "nsg-", - "networkSecurityGroupSecurityRules": "nsgsr-", - "networkWatcher": "nw-", - "privateLink": "pl-", - "privateEndpoint": "pep-", - "publicIPAddress": "pip-", - "publicIPAddressPrefix": "ippre-", - "routeFilter": "rf-", - "routeServer": "rtserv-", - "routeTable": "rt-", - "serviceEndpointPolicy": "se-", - "trafficManagerProfile": "traf-", - "userDefinedRoute": "udr-", - "virtualNetwork": "vnet-", - "virtualNetworkGateway": "vgw-", - "virtualNetworkManager": "vnm-", - "virtualNetworkPeering": "peer-", - "virtualNetworkSubnet": "snet-", - "virtualWAN": "vwan-", - "virtualWANHub": "vhub-" - }, - "security": { - "bastion": "bas-", - "keyVault": "kv-", - "keyVaultManagedHSM": "kvmhsm-", - "managedIdentity": "id-", - "sshKey": "sshkey-", - "vpnGateway": "vpng-", - "vpnConnection": "vcn-", - "vpnSite": "vst-", - "webApplicationFirewallPolicy": "waf", - "webApplicationFirewallPolicyRuleGroup": "wafrg" - }, - "storage": { - "storSimple": "ssimp", - "backupVault": "bvault-", - "backupVaultPolicy": "bkpol-", - "fileShare": "share-", - "storageAccount": "st", - "storageSyncService": "sss-" - }, - "virtualDesktop": { - "labServicesPlan": "lp-", - "virtualDesktopHostPool": "vdpool-", - "virtualDesktopApplicationGroup": "vdag-", - "virtualDesktopWorkspace": "vdws-", - "virtualDesktopScalingPlan": "vdscaling-" - } - }, - "abbrs": "[variables('$fxv#0')]", - "storageName": "[format('{0}{1}hub', variables('abbrs').storage.storageAccount, parameters('solutionName'))]", - "storageSkuName": "Standard_LRS", - "aiServicesName": "[format('{0}{1}', variables('abbrs').ai.aiServices, parameters('solutionName'))]", - "workspaceName": "[format('{0}{1}hub', variables('abbrs').managementGovernance.logAnalyticsWorkspace, parameters('solutionName'))]", - "location": "[parameters('solutionLocation')]", - "aiHubName": "[format('{0}{1}', variables('abbrs').ai.aiHub, parameters('solutionName'))]", - "aiHubFriendlyName": "[variables('aiHubName')]", - "aiHubDescription": "AI Hub for MACAE template", - "aiProjectName": "[format('{0}{1}', variables('abbrs').ai.aiHubProject, parameters('solutionName'))]", - "aiProjectFriendlyName": "[variables('aiProjectName')]", - "aiSearchName": "[format('{0}{1}', variables('abbrs').ai.aiSearch, parameters('solutionName'))]", - "storageNameCleaned": "[replace(variables('storageName'), '-', '')]" - }, - "resources": [ - { - "type": "Microsoft.MachineLearningServices/workspaces/connections", - "apiVersion": "2024-07-01-preview", - "name": "[format('{0}/{1}', variables('aiHubName'), format('{0}-connection-AzureOpenAI', variables('aiHubName')))]", - "properties": { - "category": "AIServices", - "target": "[parameters('aiServicesEndpoint')]", - "authType": "AAD", - "isSharedToAll": true, - "metadata": { - "ApiType": "Azure", - "ResourceId": "[parameters('aiServicesId')]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiHubName'))]" - ] - }, - { - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2023-09-01", - "name": "[variables('workspaceName')]", - "location": "[variables('location')]", - "tags": {}, - "properties": { - "retentionInDays": 30, - "sku": { - "name": "PerGB2018" - } - } - }, - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('storageNameCleaned')]", - "location": "[variables('location')]", - "sku": { - "name": "[variables('storageSkuName')]" - }, - "kind": "StorageV2", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "accessTier": "Hot", - "allowBlobPublicAccess": false, - "allowCrossTenantReplication": false, - "allowSharedKeyAccess": false, - "encryption": { - "keySource": "Microsoft.Storage", - "requireInfrastructureEncryption": false, - "services": { - "blob": { - "enabled": true, - "keyType": "Account" - }, - "file": { - "enabled": true, - "keyType": "Account" - }, - "queue": { - "enabled": true, - "keyType": "Service" - }, - "table": { - "enabled": true, - "keyType": "Service" - } - } - }, - "isHnsEnabled": false, - "isNfsV3Enabled": false, - "keyPolicy": { - "keyExpirationPeriodInDays": 7 - }, - "largeFileSharesState": "Disabled", - "minimumTlsVersion": "TLS1_2", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Allow" - }, - "supportsHttpsTrafficOnly": true - } - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', variables('storageNameCleaned'))]", - "name": "[guid(resourceGroup().id, parameters('managedIdentityObjectId'), subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'))]", - "properties": { - "principalId": "[parameters('managedIdentityObjectId')]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]" - ] - }, - { - "type": "Microsoft.MachineLearningServices/workspaces", - "apiVersion": "2023-08-01-preview", - "name": "[variables('aiHubName')]", - "location": "[variables('location')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "friendlyName": "[variables('aiHubFriendlyName')]", - "description": "[variables('aiHubDescription')]", - "keyVault": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", - "storageAccount": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]" - }, - "kind": "hub", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]" - ] - }, - { - "type": "Microsoft.MachineLearningServices/workspaces", - "apiVersion": "2024-01-01-preview", - "name": "[variables('aiProjectName')]", - "location": "[variables('location')]", - "kind": "Project", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "friendlyName": "[variables('aiProjectFriendlyName')]", - "hubResourceId": "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiHubName'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiHubName'))]" - ] - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName')), resourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '64702f94-c441-49e6-a78b-ef80e0188fee')]", - "principalId": "[reference(resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName')), '2024-01-01-preview', 'full').identity.principalId]" - }, - "dependsOn": [ - "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'TENANT-ID')]", - "properties": { - "value": "[subscription().tenantId]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-INFERENCE-ENDPOINT')]", - "properties": { - "value": "" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-INFERENCE-KEY')]", - "properties": { - "value": "" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-KEY')]", - "properties": { - "value": "[parameters('aiServicesKey')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPEN-AI-DEPLOYMENT-MODEL')]", - "properties": { - "value": "[parameters('gptModelName')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-PREVIEW-API-VERSION')]", - "properties": { - "value": "[parameters('gptModelVersion')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-ENDPOINT')]", - "properties": { - "value": "[parameters('aiServicesEndpoint')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-AI-PROJECT-CONN-STRING')]", - "properties": { - "value": "[format('{0};{1};{2};{3}', split(reference(resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName')), '2024-01-01-preview').discoveryUrl, '/')[2], subscription().subscriptionId, resourceGroup().name, variables('aiProjectName'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-OPENAI-CU-VERSION')]", - "properties": { - "value": "?api-version=2024-12-01-preview" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SEARCH-INDEX')]", - "properties": { - "value": "transcripts_index" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-ENDPOINT')]", - "properties": { - "value": "[parameters('aiServicesEndpoint')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-KEY')]", - "properties": { - "value": "[parameters('aiServicesKey')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'COG-SERVICES-NAME')]", - "properties": { - "value": "[variables('aiServicesName')]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-SUBSCRIPTION-ID')]", - "properties": { - "value": "[subscription().subscriptionId]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-RESOURCE-GROUP')]", - "properties": { - "value": "[resourceGroup().name]" - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-11-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), 'AZURE-LOCATION')]", - "properties": { - "value": "[parameters('solutionLocation')]" - } - } - ], - "outputs": { - "keyvaultName": { - "type": "string", - "value": "[parameters('keyVaultName')]" - }, - "keyvaultId": { - "type": "string", - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" - }, - "aiServicesName": { - "type": "string", - "value": "[variables('aiServicesName')]" - }, - "aiSearchName": { - "type": "string", - "value": "[variables('aiSearchName')]" - }, - "aiProjectName": { - "type": "string", - "value": "[variables('aiProjectName')]" - }, - "storageAccountName": { - "type": "string", - "value": "[variables('storageNameCleaned')]" - }, - "logAnalyticsId": { - "type": "string", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]" - }, - "storageAccountId": { - "type": "string", - "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageNameCleaned'))]" - }, - "projectConnectionString": { - "type": "string", - "value": "[format('{0};{1};{2};{3}', split(reference(resourceId('Microsoft.MachineLearningServices/workspaces', variables('aiProjectName')), '2024-01-01-preview').discoveryUrl, '/')[2], subscription().subscriptionId, resourceGroup().name, variables('aiProjectName'))]" - } - } - } - }, - "dependsOn": [ - "aiServices", - "kvault", - "managedIdentityModule" - ] - }, - "managedIdentityModule": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploy_managed_identity", - "resourceGroup": "[resourceGroup().name]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "managedIdentityId": { - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}{1}-containerapp-pull', variables('abbrs').security.managedIdentity, variables('solutionPrefix')))]" - }, - "managedIdentityPropPrin": { - "value": "[reference('pullIdentity').principalId]" - }, - "managedIdentityLocation": { - "value": "[reference('pullIdentity', '2023-07-31-preview', 'full').location]" - }, - "miName": { - "value": "[format('{0}{1}', variables('abbrs').security.managedIdentity, variables('solutionPrefix'))]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.35.1.17967", - "templateHash": "14769217536017297821" - } - }, - "parameters": { - "managedIdentityId": { - "type": "string", - "metadata": { - "description": "Solution Location" - } - }, - "managedIdentityPropPrin": { - "type": "string" - }, - "managedIdentityLocation": { - "type": "string" - }, - "miName": { - "type": "string", - "metadata": { - "description": "Managed Identity Name" - } - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(resourceGroup().id, parameters('managedIdentityId'), resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'))]", - "properties": { - "principalId": "[parameters('managedIdentityPropPrin')]", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", - "principalType": "ServicePrincipal" - } - } - ], - "outputs": { - "managedIdentityOutput": { - "type": "object", - "value": { - "id": "[parameters('managedIdentityId')]", - "objectId": "[parameters('managedIdentityPropPrin')]", - "resourceId": "[parameters('managedIdentityId')]", - "location": "[parameters('managedIdentityLocation')]", - "name": "[parameters('miName')]" - } - }, - "managedIdentityId": { - "type": "string", - "value": "[parameters('managedIdentityId')]" - } - } - } - }, - "dependsOn": [ - "pullIdentity" - ] - }, - "deploymentScriptCLI": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deploymentScriptCLI", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "kind": { - "value": "AzureCLI" - }, - "name": { - "value": "rdsmin001" - }, - "azCliVersion": { - "value": "2.69.0" - }, - "location": { - "value": "[variables('solutionLocation')]" - }, - "managedIdentities": { - "value": { - "userAssignedResourceIds": [ - "[reference('managedIdentityModule').outputs.managedIdentityId.value]" - ] - } - }, - "scriptContent": { - "value": "[format('az cosmosdb sql role assignment create --resource-group \"{0}\" --account-name \"{1}\" --role-definition-id \"{2}\" --scope \"{3}\" --principal-id \"{4}\"', resourceGroup().name, format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix')), '00000000-0000-0000-0000-000000000002'), resourceId('Microsoft.DocumentDB/databaseAccounts', format('{0}{1}', variables('abbrs').databases.cosmosDBDatabase, variables('solutionPrefix'))), reference('containerApp', '2024-03-01', 'full').identity.principalId)]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.32.4.45862", - "templateHash": "8965217851411422458" - }, - "name": "Deployment Scripts", - "description": "This module deploys Deployment Scripts.", - "owner": "Azure/module-maintainers" - }, - "definitions": { - "environmentVariableType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "metadata": { - "description": "Required. The name of the environment variable." - } - }, - "secureValue": { - "type": "securestring", - "nullable": true, - "metadata": { - "description": "Conditional. The value of the secure environment variable. Required if `value` is null." - } - }, - "value": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Conditional. The value of the environment variable. Required if `secureValue` is null." - } - } - } - }, - "lockType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Specify the name of lock." - } - }, - "kind": { - "type": "string", - "allowedValues": [ - "CanNotDelete", - "None", - "ReadOnly" - ], - "nullable": true, - "metadata": { - "description": "Optional. Specify the type of lock." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a lock.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "managedIdentityOnlyUserAssignedType": { - "type": "object", - "properties": { - "userAssignedResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a managed identity configuration. To be used if only user-assigned identities are supported by the resource provider.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - }, - "roleAssignmentType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." - } - }, - "roleDefinitionIdOrName": { - "type": "string", - "metadata": { - "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." - } - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." - } - }, - "principalType": { - "type": "string", - "allowedValues": [ - "Device", - "ForeignGroup", - "Group", - "ServicePrincipal", - "User" - ], - "nullable": true, - "metadata": { - "description": "Optional. The principal type of the assigned principal ID." - } - }, - "description": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The description of the role assignment." - } - }, - "condition": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." - } - }, - "conditionVersion": { - "type": "string", - "allowedValues": [ - "2.0" - ], - "nullable": true, - "metadata": { - "description": "Optional. Version of the condition." - } - }, - "delegatedManagedIdentityResourceId": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. The Resource Id of the delegated managed identity resource." - } - } - }, - "metadata": { - "description": "An AVM-aligned type for a role assignment.", - "__bicep_imported_from!": { - "sourceTemplate": "br:mcr.microsoft.com/bicep/avm/utl/types/avm-common-types:0.2.1" - } - } - } - }, - "parameters": { - "name": { - "type": "string", - "maxLength": 90, - "metadata": { - "description": "Required. Name of the Deployment Script." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. Location for all resources." - } - }, - "kind": { - "type": "string", - "allowedValues": [ - "AzureCLI", - "AzurePowerShell" - ], - "metadata": { - "description": "Required. Specifies the Kind of the Deployment Script." - } - }, - "managedIdentities": { - "$ref": "#/definitions/managedIdentityOnlyUserAssignedType", - "nullable": true, - "metadata": { - "description": "Optional. The managed identity definition for this resource." - } - }, - "tags": { - "type": "object", - "nullable": true, - "metadata": { - "description": "Optional. Resource tags." - } - }, - "azPowerShellVersion": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Azure PowerShell module version to be used. See a list of supported Azure PowerShell versions: https://mcr.microsoft.com/v2/azuredeploymentscripts-powershell/tags/list." - } - }, - "azCliVersion": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Azure CLI module version to be used. See a list of supported Azure CLI versions: https://mcr.microsoft.com/v2/azure-cli/tags/list." - } - }, - "scriptContent": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Script body. Max length: 32000 characters. To run an external script, use primaryScriptURI instead." - } - }, - "primaryScriptUri": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Uri for the external script. This is the entry point for the external script. To run an internal script, use the scriptContent parameter instead." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/environmentVariableType" - }, - "nullable": true, - "metadata": { - "description": "Optional. The environment variables to pass over to the script." - } - }, - "supportingScriptUris": { - "type": "array", - "nullable": true, - "metadata": { - "description": "Optional. List of supporting files for the external script (defined in primaryScriptUri). Does not work with internal scripts (code defined in scriptContent)." - } - }, - "subnetResourceIds": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "metadata": { - "description": "Optional. List of subnet IDs to use for the container group. This is required if you want to run the deployment script in a private network. When using a private network, the `Storage File Data Privileged Contributor` role needs to be assigned to the user-assigned managed identity and the deployment principal needs to have permissions to list the storage account keys. Also, Shared-Keys must not be disabled on the used storage account [ref](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deployment-script-vnet)." - } - }, - "arguments": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Command-line arguments to pass to the script. Arguments are separated by spaces." - } - }, - "retentionInterval": { - "type": "string", - "defaultValue": "P1D", - "metadata": { - "description": "Optional. Interval for which the service retains the script resource after it reaches a terminal state. Resource will be deleted when this duration expires. Duration is based on ISO 8601 pattern (for example P7D means one week)." - } - }, - "baseTime": { - "type": "string", - "defaultValue": "[utcNow('yyyy-MM-dd-HH-mm-ss')]", - "metadata": { - "description": "Generated. Do not provide a value! This date value is used to make sure the script run every time the template is deployed." - } - }, - "runOnce": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. When set to false, script will run every time the template is deployed. When set to true, the script will only run once." - } - }, - "cleanupPreference": { - "type": "string", - "defaultValue": "Always", - "allowedValues": [ - "Always", - "OnSuccess", - "OnExpiration" - ], - "metadata": { - "description": "Optional. The clean up preference when the script execution gets in a terminal state. Specify the preference on when to delete the deployment script resources. The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, canceled)." - } - }, - "containerGroupName": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Container group name, if not specified then the name will get auto-generated. Not specifying a 'containerGroupName' indicates the system to generate a unique name which might end up flagging an Azure Policy as non-compliant. Use 'containerGroupName' when you have an Azure Policy that expects a specific naming convention or when you want to fully control the name. 'containerGroupName' property must be between 1 and 63 characters long, must contain only lowercase letters, numbers, and dashes and it cannot start or end with a dash and consecutive dashes are not allowed." - } - }, - "storageAccountResourceId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. The resource ID of the storage account to use for this deployment script. If none is provided, the deployment script uses a temporary, managed storage account." - } - }, - "timeout": { - "type": "string", - "nullable": true, - "metadata": { - "description": "Optional. Maximum allowed script execution time specified in ISO 8601 format. Default value is PT1H - 1 hour; 'PT30M' - 30 minutes; 'P5D' - 5 days; 'P1Y' 1 year." - } - }, - "lock": { - "$ref": "#/definitions/lockType", - "nullable": true, - "metadata": { - "description": "Optional. The lock settings of the service." - } - }, - "roleAssignments": { - "type": "array", - "items": { - "$ref": "#/definitions/roleAssignmentType" - }, - "nullable": true, - "metadata": { - "description": "Optional. Array of role assignments to create." - } - }, - "enableTelemetry": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable/Disable usage telemetry for module." - } - } - }, - "variables": { - "copy": [ - { - "name": "formattedRoleAssignments", - "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", - "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" - }, - { - "name": "subnetIds", - "count": "[length(coalesce(parameters('subnetResourceIds'), createArray()))]", - "input": { - "id": "[coalesce(parameters('subnetResourceIds'), createArray())[copyIndex('subnetIds')]]" - } - } - ], - "builtInRoleNames": { - "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", - "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", - "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", - "Role Based Access Control Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", - "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" - }, - "containerSettings": { - "containerGroupName": "[parameters('containerGroupName')]", - "subnetIds": "[if(not(empty(coalesce(variables('subnetIds'), createArray()))), variables('subnetIds'), null())]" - }, - "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", - "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', null()), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" - }, - "resources": { - "storageAccount": { - "condition": "[not(empty(parameters('storageAccountResourceId')))]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2023-05-01", - "subscriptionId": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2]]", - "resourceGroup": "[split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]]", - "name": "[last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))]" - }, - "avmTelemetry": { - "condition": "[parameters('enableTelemetry')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2024-03-01", - "name": "[format('46d3xbcp.res.resources-deploymentscript.{0}.{1}', replace('0.5.1', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "resources": [], - "outputs": { - "telemetry": { - "type": "String", - "value": "For more information, see https://aka.ms/avm/TelemetryInfo" - } - } - } - } - }, - "deploymentScript": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", - "identity": "[variables('identity')]", - "kind": "[parameters('kind')]", - "properties": { - "azPowerShellVersion": "[if(equals(parameters('kind'), 'AzurePowerShell'), parameters('azPowerShellVersion'), null())]", - "azCliVersion": "[if(equals(parameters('kind'), 'AzureCLI'), parameters('azCliVersion'), null())]", - "containerSettings": "[if(not(empty(variables('containerSettings'))), variables('containerSettings'), null())]", - "storageAccountSettings": "[if(not(empty(parameters('storageAccountResourceId'))), if(not(empty(parameters('storageAccountResourceId'))), createObject('storageAccountKey', if(empty(parameters('subnetResourceIds')), listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '//'), '/')[2], split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), '////'), '/')[4]), 'Microsoft.Storage/storageAccounts', last(split(if(not(empty(parameters('storageAccountResourceId'))), parameters('storageAccountResourceId'), 'dummyAccount'), '/'))), '2023-01-01').keys[0].value, null()), 'storageAccountName', last(split(parameters('storageAccountResourceId'), '/'))), null()), null())]", - "arguments": "[parameters('arguments')]", - "environmentVariables": "[parameters('environmentVariables')]", - "scriptContent": "[if(not(empty(parameters('scriptContent'))), parameters('scriptContent'), null())]", - "primaryScriptUri": "[if(not(empty(parameters('primaryScriptUri'))), parameters('primaryScriptUri'), null())]", - "supportingScriptUris": "[if(not(empty(parameters('supportingScriptUris'))), parameters('supportingScriptUris'), null())]", - "cleanupPreference": "[parameters('cleanupPreference')]", - "forceUpdateTag": "[if(parameters('runOnce'), resourceGroup().name, parameters('baseTime'))]", - "retentionInterval": "[parameters('retentionInterval')]", - "timeout": "[parameters('timeout')]" - } - }, - "deploymentScript_lock": { - "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", - "type": "Microsoft.Authorization/locks", - "apiVersion": "2020-05-01", - "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", - "properties": { - "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", - "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" - }, - "dependsOn": [ - "deploymentScript" - ] - }, - "deploymentScript_roleAssignments": { - "copy": { - "name": "deploymentScript_roleAssignments", - "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Resources/deploymentScripts/{0}', parameters('name'))]", - "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.Resources/deploymentScripts', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", - "properties": { - "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", - "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", - "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", - "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", - "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", - "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", - "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" - }, - "dependsOn": [ - "deploymentScript" - ] - }, - "deploymentScriptLogs": { - "existing": true, - "type": "Microsoft.Resources/deploymentScripts/logs", - "apiVersion": "2023-08-01", - "name": "[format('{0}/{1}', parameters('name'), 'default')]", - "dependsOn": [ - "deploymentScript" - ] - } - }, - "outputs": { - "resourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the deployment script." - }, - "value": "[resourceId('Microsoft.Resources/deploymentScripts', parameters('name'))]" - }, - "resourceGroupName": { - "type": "string", - "metadata": { - "description": "The resource group the deployment script was deployed into." - }, - "value": "[resourceGroup().name]" - }, - "name": { - "type": "string", - "metadata": { - "description": "The name of the deployment script." - }, - "value": "[parameters('name')]" - }, - "location": { - "type": "string", - "metadata": { - "description": "The location the resource was deployed into." - }, - "value": "[reference('deploymentScript', '2023-08-01', 'full').location]" - }, - "outputs": { - "type": "object", - "metadata": { - "description": "The output of the deployment script." - }, - "value": "[coalesce(tryGet(reference('deploymentScript'), 'outputs'), createObject())]" - }, - "deploymentScriptLogs": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "The logs of the deployment script." - }, - "value": "[split(reference('deploymentScriptLogs').log, '\n')]" - } - } - } - }, - "dependsOn": [ - "containerApp", - "cosmos", - "managedIdentityModule" - ] - } - } -} \ No newline at end of file diff --git a/infra/main.parameters.json b/infra/main.parameters.json deleted file mode 100644 index c7fc26a4a..000000000 --- a/infra/main.parameters.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "environmentName": { - "value": "${AZURE_ENV_NAME}" - }, - "location": { - "value": "${AZURE_LOCATION}" - }, - "backendExists": { - "value": "${SERVICE_BACKEND_RESOURCE_EXISTS=false}" - }, - "backendDefinition": { - "value": { - "settings": [ - { - "name": "", - "value": "${VAR}", - "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.", - "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment." - }, - { - "name": "", - "value": "${VAR_S}", - "secret": true, - "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.", - "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment." - } - ] - } - }, - "frontendExists": { - "value": "${SERVICE_FRONTEND_RESOURCE_EXISTS=false}" - }, - "frontendDefinition": { - "value": { - "settings": [ - { - "name": "", - "value": "${VAR}", - "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.", - "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR} to use the value of 'VAR' from the current environment." - }, - { - "name": "", - "value": "${VAR_S}", - "secret": true, - "_comment_name": "The name of the environment variable when running in Azure. If empty, ignored.", - "_comment_value": "The value to provide. This can be a fixed literal, or an expression like ${VAR_S} to use the value of 'VAR_S' from the current environment." - } - ] - } - }, - "principalId": { - "value": "${AZURE_PRINCIPAL_ID}" - } - } -} diff --git a/infra/main.waf-aligned.bicepparam b/infra/main.waf-aligned.bicepparam new file mode 100644 index 000000000..af6f1122c --- /dev/null +++ b/infra/main.waf-aligned.bicepparam @@ -0,0 +1,9 @@ +using './main.bicep' + +param solutionPrefix = null //Type a string value to customize the prefix for your resource names +param solutionLocation = readEnvironmentVariable('AZURE_LOCATION', 'swedencentral') +param azureOpenAILocation = readEnvironmentVariable('AZURE_ENV_OPENAI_LOCATION', 'swedencentral') +param virtualMachineConfiguration = { + adminUsername: 'adminuser' + adminPassword: 'P@ssw0rd1234' +} diff --git a/infra/modules/ai-hub.bicep b/infra/modules/ai-hub.bicep new file mode 100644 index 000000000..c92acff92 --- /dev/null +++ b/infra/modules/ai-hub.bicep @@ -0,0 +1,62 @@ +param name string +param tags object +param location string +param sku string +param storageAccountResourceId string +param logAnalyticsWorkspaceResourceId string +param applicationInsightsResourceId string +param aiFoundryAiServicesName string +param enableTelemetry bool +param virtualNetworkEnabled bool +import { privateEndpointSingleServiceType } from 'br/public:avm/utl/types/avm-common-types:0.4.0' +param privateEndpoints privateEndpointSingleServiceType[] + +resource aiServices 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { + name: aiFoundryAiServicesName +} + +module aiFoundryAiHub 'br/public:avm/res/machine-learning-services/workspace:0.10.1' = { + name: take('avm.res.machine-learning-services.workspace.${name}', 64) + params: { + name: name + tags: tags + location: location + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] + kind: 'Hub' + sku: sku + description: 'AI Hub for Multi Agent Custom Automation Engine Solution Accelerator template' + //associatedKeyVaultResourceId: keyVaultResourceId + associatedStorageAccountResourceId: storageAccountResourceId + associatedApplicationInsightsResourceId: applicationInsightsResourceId + connections: [ + { + name: 'connection-AzureOpenAI' + category: 'AIServices' + target: aiServices.properties.endpoint + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: aiServices.id + } + connectionProperties: { + authType: 'ApiKey' + credentials: { + key: aiServices.listKeys().key1 + } + } + } + ] + //publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + publicNetworkAccess: 'Enabled' //TODO: connection via private endpoint is not working from containers network. Change this when fixed + managedNetworkSettings: virtualNetworkEnabled + ? { + isolationMode: 'AllowInternetOutbound' + outboundRules: null //TODO: Refine this + } + : null + privateEndpoints: privateEndpoints + } +} + +output resourceId string = aiFoundryAiHub.outputs.resourceId diff --git a/infra/modules/container-app-environment.bicep b/infra/modules/container-app-environment.bicep new file mode 100644 index 000000000..41298690f --- /dev/null +++ b/infra/modules/container-app-environment.bicep @@ -0,0 +1,88 @@ +param name string +param location string +param logAnalyticsResourceName string +param tags object +param publicNetworkAccess string +//param vnetConfiguration object +param zoneRedundant bool +//param aspireDashboardEnabled bool +param enableTelemetry bool +param subnetResourceId string +param applicationInsightsConnectionString string + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = { + name: logAnalyticsResourceName +} + +// resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2024-08-02-preview' = { +// name: name +// location: location +// tags: tags +// properties: { +// //daprAIConnectionString: appInsights.properties.ConnectionString +// //daprAIConnectionString: applicationInsights.outputs.connectionString +// appLogsConfiguration: { +// destination: 'log-analytics' +// logAnalyticsConfiguration: { +// customerId: logAnalyticsWorkspace.properties.customerId +// #disable-next-line use-secure-value-for-secure-inputs +// sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey +// } +// } +// workloadProfiles: [ +// //THIS IS REQUIRED TO ADD PRIVATE ENDPOINTS +// { +// name: 'Consumption' +// workloadProfileType: 'Consumption' +// } +// ] +// publicNetworkAccess: publicNetworkAccess +// vnetConfiguration: vnetConfiguration +// zoneRedundant: zoneRedundant +// } +// } + +module containerAppEnvironment 'br/public:avm/res/app/managed-environment:0.11.1' = { + name: take('avm.res.app.managed-environment.${name}', 64) + params: { + name: name + location: location + tags: tags + enableTelemetry: enableTelemetry + //daprAIConnectionString: applicationInsights.outputs.connectionString //Troubleshoot: ContainerAppsConfiguration.DaprAIConnectionString is invalid. DaprAIConnectionString can not be set when AppInsightsConfiguration has been set, please set DaprAIConnectionString to null. (Code:InvalidRequestParameterWithDetails + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalyticsWorkspace.properties.customerId + #disable-next-line use-secure-value-for-secure-inputs + sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey + } + } + workloadProfiles: [ + //THIS IS REQUIRED TO ADD PRIVATE ENDPOINTS + { + name: 'Consumption' + workloadProfileType: 'Consumption' + } + ] + publicNetworkAccess: publicNetworkAccess + appInsightsConnectionString: applicationInsightsConnectionString + zoneRedundant: zoneRedundant + infrastructureSubnetResourceId: subnetResourceId + internal: false + } +} + +//TODO: FIX when deployed to vnet. This needs access to Azure to work +// resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@2024-10-02-preview' = if (aspireDashboardEnabled) { +// parent: containerAppEnvironment +// name: 'aspire-dashboard' +// properties: { +// componentType: 'AspireDashboard' +// } +// } + +//output resourceId string = containerAppEnvironment.id +output resourceId string = containerAppEnvironment.outputs.resourceId +//output location string = containerAppEnvironment.location +output location string = containerAppEnvironment.outputs.location diff --git a/infra/deploy_ai_foundry.bicep b/infra/old/deploy_ai_foundry.bicep similarity index 100% rename from infra/deploy_ai_foundry.bicep rename to infra/old/deploy_ai_foundry.bicep diff --git a/infra/deploy_keyvault.bicep b/infra/old/deploy_keyvault.bicep similarity index 100% rename from infra/deploy_keyvault.bicep rename to infra/old/deploy_keyvault.bicep diff --git a/infra/deploy_managed_identity.bicep b/infra/old/deploy_managed_identity.bicep similarity index 100% rename from infra/deploy_managed_identity.bicep rename to infra/old/deploy_managed_identity.bicep diff --git a/infra/macae-continer-oc.json b/infra/old/macae-continer-oc.json similarity index 100% rename from infra/macae-continer-oc.json rename to infra/old/macae-continer-oc.json diff --git a/infra/macae-continer.json b/infra/old/macae-continer.json similarity index 100% rename from infra/macae-continer.json rename to infra/old/macae-continer.json diff --git a/infra/macae-dev.bicep b/infra/old/macae-dev.bicep similarity index 100% rename from infra/macae-dev.bicep rename to infra/old/macae-dev.bicep diff --git a/infra/macae-large.bicepparam b/infra/old/macae-large.bicepparam similarity index 100% rename from infra/macae-large.bicepparam rename to infra/old/macae-large.bicepparam diff --git a/infra/macae-mini.bicepparam b/infra/old/macae-mini.bicepparam similarity index 100% rename from infra/macae-mini.bicepparam rename to infra/old/macae-mini.bicepparam diff --git a/infra/macae.bicep b/infra/old/macae.bicep similarity index 100% rename from infra/macae.bicep rename to infra/old/macae.bicep diff --git a/infra/old/main.bicep b/infra/old/main.bicep new file mode 100644 index 000000000..661973ff8 --- /dev/null +++ b/infra/old/main.bicep @@ -0,0 +1,1296 @@ +extension graphV1 +//extension graphBeta + +metadata name = '' +metadata description = '' + +@description('Required. The prefix to add in the default names given to all deployed Azure resources.') +@maxLength(19) +param solutionPrefix string + +@description('Optional. Location for all Resources.') +param solutionLocation string = resourceGroup().location + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableNetworkSecurity bool + +@description('Optional. The tags to apply to all deployed Azure resources.') +param tags object = { + app: solutionPrefix + location: solutionLocation +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Log Analytics Workspace resource.') +param logAnalyticsWorkspaceConfiguration logAnalyticsWorkspaceConfigurationType = { + enabled: true + name: '${solutionPrefix}laws' + location: solutionLocation + sku: 'PerGB2018' + tags: tags + dataRetentionInDays: 30 +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Application Insights resource.') +param applicationInsightsConfiguration applicationInsightsConfigurationType = { + enabled: true + name: '${solutionPrefix}appi' + location: solutionLocation + tags: tags + retentionInDays: 30 +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Managed Identity resource.') +param userAssignedManagedIdentityConfiguration userAssignedManagedIdentityType = { + enabled: true + name: '${solutionPrefix}mgid' + location: solutionLocation + tags: tags +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the backend subnet.') +param networkSecurityGroupBackendConfiguration networkSecurityGroupConfigurationType = { + enabled: enableNetworkSecurity + name: '${solutionPrefix}nsgr-backend' + location: solutionLocation + tags: tags + securityRules: [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the containers subnet.') +param networkSecurityGroupContainersConfiguration networkSecurityGroupConfigurationType = { + enabled: enableNetworkSecurity + name: '${solutionPrefix}nsgr-containers' + location: solutionLocation + tags: tags + securityRules: [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the Bastion subnet.') +param networkSecurityGroupBastionConfiguration networkSecurityGroupConfigurationType = { + enabled: enableNetworkSecurity + name: '${solutionPrefix}nsgr-bastion' + location: solutionLocation + tags: tags + securityRules: [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] +} + +@description('Optional. The configuration to apply for the Multi-Agent Custom Automation Engine Network Security Group resource for the administration subnet.') +param networkSecurityGroupAdministrationConfiguration networkSecurityGroupConfigurationType = { + enabled: enableNetworkSecurity + name: '${solutionPrefix}nsgr-administration' + location: solutionLocation + tags: tags + securityRules: [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } + ] +} + +@description('Optional. Configuration for the virtual machine.') +param virtualMachineConfiguration virtualMachineConfigurationType = { + enabled: enableNetworkSecurity + adminUsername: 'adminuser' + adminPassword: guid(solutionPrefix, subscription().subscriptionId) +} +var virtualMachineEnabled = virtualMachineConfiguration.?enabled ?? true + +@description('Optional. Configuration for the virtual machine.') +param virtualNetworkConfiguration virtualNetworkConfigurationType = { + enabled: enableNetworkSecurity +} +var virtualNetworkEnabled = virtualNetworkConfiguration.?enabled ?? true + +@description('Optional. The configuration of the Entra ID Application used to authenticate the website.') +param entraIdApplicationConfiguration entraIdApplicationConfigurationType = { + enabled: false +} + +@description('Optional. The UTC time deployment.') +param deploymentTime string = utcNow() + +// +// Add your parameters here +// + +// ============== // +// Resources // +// ============== // + +/* #disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.[[REPLACE WITH TELEMETRY IDENTIFIER]].${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } + } + } + } +} */ + +// ========== Log Analytics Workspace ========== // +// Log Analytics configuration defaults +var logAnalyticsWorkspaceEnabled = logAnalyticsWorkspaceConfiguration.?enabled ?? true +var logAnalyticsWorkspaceResourceName = logAnalyticsWorkspaceConfiguration.?name ?? '${solutionPrefix}-laws' +var logAnalyticsWorkspaceTags = logAnalyticsWorkspaceConfiguration.?tags ?? tags +var logAnalyticsWorkspaceLocation = logAnalyticsWorkspaceConfiguration.?location ?? solutionLocation +var logAnalyticsWorkspaceSkuName = logAnalyticsWorkspaceConfiguration.?sku ?? 'PerGB2018' +var logAnalyticsWorkspaceDataRetentionInDays = logAnalyticsWorkspaceConfiguration.?dataRetentionInDays ?? 30 +module logAnalyticsWorkspace 'br/public:avm/res/operational-insights/workspace:0.11.2' = if (logAnalyticsWorkspaceEnabled) { + name: take('operational-insights.workspace.${logAnalyticsWorkspaceResourceName}', 64) + params: { + name: logAnalyticsWorkspaceResourceName + tags: logAnalyticsWorkspaceTags + location: logAnalyticsWorkspaceLocation + enableTelemetry: enableTelemetry + skuName: logAnalyticsWorkspaceSkuName + dataRetention: logAnalyticsWorkspaceDataRetentionInDays + diagnosticSettings: [{ useThisWorkspace: true }] + } +} + +// ========== Application Insights ========== // +// Application Insights configuration defaults +var applicationInsightsEnabled = applicationInsightsConfiguration.?enabled ?? true +var applicationInsightsResourceName = applicationInsightsConfiguration.?name ?? '${solutionPrefix}appi' +var applicationInsightsTags = applicationInsightsConfiguration.?tags ?? tags +var applicationInsightsLocation = applicationInsightsConfiguration.?location ?? solutionLocation +var applicationInsightsRetentionInDays = applicationInsightsConfiguration.?retentionInDays ?? 365 +module applicationInsights 'br/public:avm/res/insights/component:0.6.0' = if (applicationInsightsEnabled) { + name: take('insights.component.${applicationInsightsResourceName}', 64) + params: { + name: applicationInsightsResourceName + workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId + location: applicationInsightsLocation + enableTelemetry: enableTelemetry + tags: applicationInsightsTags + retentionInDays: applicationInsightsRetentionInDays + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + kind: 'web' + disableIpMasking: false + flowType: 'Bluefield' + } +} + +// ========== User assigned identity Web App ========== // +var userAssignedManagedIdentityEnabled = userAssignedManagedIdentityConfiguration.?enabled ?? true +var userAssignedManagedIdentityResourceName = userAssignedManagedIdentityConfiguration.?name ?? '${solutionPrefix}uaid' +var userAssignedManagedIdentityTags = userAssignedManagedIdentityConfiguration.?tags ?? tags +var userAssignedManagedIdentityLocation = userAssignedManagedIdentityConfiguration.?location ?? solutionLocation +module userAssignedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.1' = if (userAssignedManagedIdentityEnabled) { + name: take('managed-identity.user-assigned-identity.${userAssignedManagedIdentityResourceName}', 64) + params: { + name: userAssignedManagedIdentityResourceName + tags: userAssignedManagedIdentityTags + location: userAssignedManagedIdentityLocation + enableTelemetry: enableTelemetry + } +} + +// ========== Network Security Groups ========== // +var networkSecurityGroupBackendEnabled = networkSecurityGroupBackendConfiguration.?enabled ?? true +var networkSecurityGroupBackendResourceName = networkSecurityGroupBackendConfiguration.?name ?? '${solutionPrefix}nsgr-backend' +var networkSecurityGroupBackendTags = networkSecurityGroupBackendConfiguration.?tags ?? tags +var networkSecurityGroupBackendLocation = networkSecurityGroupBackendConfiguration.?location ?? solutionLocation +var networkSecurityGroupBackendSecurityRules = networkSecurityGroupBackendConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } +] +module networkSecurityGroupBackend 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupBackendEnabled) { + name: take('network.network-security-group.${networkSecurityGroupBackendResourceName}', 64) + params: { + name: networkSecurityGroupBackendResourceName + location: networkSecurityGroupBackendLocation + tags: networkSecurityGroupBackendTags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupBackendSecurityRules + } +} + +var networkSecurityGroupContainersEnabled = networkSecurityGroupContainersConfiguration.?enabled ?? true +var networkSecurityGroupContainersResourceName = networkSecurityGroupContainersConfiguration.?name ?? '${solutionPrefix}nsgr-containers' +var networkSecurityGroupContainersTags = networkSecurityGroupContainersConfiguration.?tags ?? tags +var networkSecurityGroupContainersLocation = networkSecurityGroupContainersConfiguration.?location ?? solutionLocation +var networkSecurityGroupContainersSecurityRules = networkSecurityGroupContainersConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } +] +module networkSecurityGroupContainers 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupContainersEnabled) { + name: take('network.network-security-group.${networkSecurityGroupContainersResourceName}', 64) + params: { + name: networkSecurityGroupContainersResourceName + location: networkSecurityGroupContainersLocation + tags: networkSecurityGroupContainersTags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupContainersSecurityRules + } +} + +var networkSecurityGroupBastionEnabled = networkSecurityGroupBastionConfiguration.?enabled ?? true +var networkSecurityGroupBastionResourceName = networkSecurityGroupBastionConfiguration.?name ?? '${solutionPrefix}nsgr-bastion' +var networkSecurityGroupBastionTags = networkSecurityGroupBastionConfiguration.?tags ?? tags +var networkSecurityGroupBastionLocation = networkSecurityGroupBastionConfiguration.?location ?? solutionLocation +var networkSecurityGroupBastionSecurityRules = networkSecurityGroupBastionConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } +] +module networkSecurityGroupBastion 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupBastionEnabled) { + name: take('network.network-security-group.${networkSecurityGroupBastionResourceName}', 64) + params: { + name: networkSecurityGroupBastionResourceName + location: networkSecurityGroupBastionLocation + tags: networkSecurityGroupBastionTags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupBastionSecurityRules + } +} + +var networkSecurityGroupAdministrationEnabled = networkSecurityGroupAdministrationConfiguration.?enabled ?? true +var networkSecurityGroupAdministrationResourceName = networkSecurityGroupAdministrationConfiguration.?name ?? '${solutionPrefix}nsgr-administration' +var networkSecurityGroupAdministrationTags = networkSecurityGroupAdministrationConfiguration.?tags ?? tags +var networkSecurityGroupAdministrationLocation = networkSecurityGroupAdministrationConfiguration.?location ?? solutionLocation +var networkSecurityGroupAdministrationSecurityRules = networkSecurityGroupAdministrationConfiguration.?securityRules ?? [ + // { + // name: 'DenySshRdpOutbound' //Azure Bastion + // properties: { + // priority: 200 + // access: 'Deny' + // protocol: '*' + // direction: 'Outbound' + // sourceAddressPrefix: 'VirtualNetwork' + // sourcePortRange: '*' + // destinationAddressPrefix: '*' + // destinationPortRanges: [ + // '3389' + // '22' + // ] + // } + // } +] +module networkSecurityGroupAdministration 'br/public:avm/res/network/network-security-group:0.5.1' = if (virtualNetworkEnabled && networkSecurityGroupAdministrationEnabled) { + name: take('network.network-security-group.${networkSecurityGroupAdministrationResourceName}', 64) + params: { + name: networkSecurityGroupAdministrationResourceName + location: networkSecurityGroupAdministrationLocation + tags: networkSecurityGroupAdministrationTags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + securityRules: networkSecurityGroupAdministrationSecurityRules + } +} + +// ========== Virtual Network ========== // + +module virtualNetwork 'br/public:avm/res/network/virtual-network:0.6.1' = if (virtualNetworkEnabled) { + name: 'network-virtual-network' + params: { + name: '${solutionPrefix}vnet' + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + addressPrefixes: ['10.0.0.0/8'] + subnets: [ + // The default subnet **must** be the first in the subnets array + { + name: 'backend' + addressPrefix: '10.0.0.0/27' + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + networkSecurityGroupResourceId: networkSecurityGroupBackend.outputs.resourceId + } + { + name: 'administration' + addressPrefix: '10.0.0.32/27' + networkSecurityGroupResourceId: networkSecurityGroupAdministration.outputs.resourceId + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + //natGatewayResourceId: natGateway.outputs.resourceId + } + { + // For Azure Bastion resources deployed on or after November 2, 2021, the minimum AzureBastionSubnet size is /26 or larger (/25, /24, etc.). + // https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet + name: 'AzureBastionSubnet' //This exact name is required for Azure Bastion + addressPrefix: '10.0.0.64/26' + networkSecurityGroupResourceId: networkSecurityGroupBastion.outputs.resourceId + } + { + // If you use your own VNet, you need to provide a subnet that is dedicated exclusively to the Container App environment you deploy. This subnet isn't available to other services + // https://learn.microsoft.com/en-us/azure/container-apps/networking?tabs=workload-profiles-env%2Cazure-cli#custom-vnet-configuration + name: 'containers' + addressPrefix: '10.0.1.0/23' //subnet of size /23 is required for container app + //defaultOutboundAccess: false TODO: check this configuration for a more restricted outbound access + delegation: 'Microsoft.App/environments' + networkSecurityGroupResourceId: networkSecurityGroupContainers.outputs.resourceId + privateEndpointNetworkPolicies: 'Disabled' + privateLinkServiceNetworkPolicies: 'Enabled' + } + ] + } +} + +// ========== Bastion host ========== // + +module bastionHost 'br/public:avm/res/network/bastion-host:0.6.1' = if (virtualNetworkEnabled) { + name: 'network-dns-zone-bastion-host' + params: { + name: '${solutionPrefix}bstn' + location: solutionLocation + skuName: 'Standard' + enableTelemetry: enableTelemetry + tags: tags + virtualNetworkResourceId: virtualNetwork.outputs.resourceId + publicIPAddressObject: { + name: '${solutionPrefix}pbipbstn' + } + disableCopyPaste: false + enableFileCopy: false + enableIpConnect: true + //enableKerberos: bastionConfiguration.?enableKerberos + enableShareableLink: true + //scaleUnits: bastionConfiguration.?scaleUnits + } +} + +// ========== Virtual machine ========== // + +module virtualMachine 'br/public:avm/res/compute/virtual-machine:0.13.0' = if (virtualNetworkEnabled && virtualMachineEnabled) { + name: 'compute-virtual-machine' + params: { + name: '${solutionPrefix}vmws' + computerName: take('${solutionPrefix}vmws', 15) + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + adminUsername: virtualMachineConfiguration.?adminUsername! + adminPassword: virtualMachineConfiguration.?adminPassword! + nicConfigurations: [ + { + //networkSecurityGroupResourceId: virtualMachineConfiguration.?nicConfigurationConfiguration.networkSecurityGroupResourceId + nicSuffix: 'nic01' + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + ipConfigurations: [ + { + name: 'ipconfig01' + subnetResourceId: virtualNetwork.outputs.subnetResourceIds[1] + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + } + ] + } + ] + imageReference: { + publisher: 'microsoft-dsvm' + offer: 'dsvm-win-2022' + sku: 'winserver-2022' + version: 'latest' + } + osDisk: { + createOption: 'FromImage' + managedDisk: { + storageAccountType: 'Premium_ZRS' + } + diskSizeGB: 128 + caching: 'ReadWrite' + } + //patchMode: virtualMachineConfiguration.?patchMode + osType: 'Windows' + encryptionAtHost: false //The property 'securityProfile.encryptionAtHost' is not valid because the 'Microsoft.Compute/EncryptionAtHost' feature is not enabled for this subscription. + vmSize: 'Standard_D2s_v3' + zone: 0 + extensionAadJoinConfig: { + enabled: true + typeHandlerVersion: '1.0' + } + // extensionMonitoringAgentConfig: { + // enabled: true + // } + // maintenanceConfigurationResourceId: virtualMachineConfiguration.?maintenanceConfigurationResourceId + } +} +// ========== DNS Zone for AI Foundry: Open AI ========== // +var openAiSubResource = 'account' +var openAiPrivateDnsZones = { + 'privatelink.cognitiveservices.azure.com': openAiSubResource + 'privatelink.openai.azure.com': openAiSubResource + 'privatelink.services.ai.azure.com': openAiSubResource +} + +module privateDnsZonesAiServices 'br/public:avm/res/network/private-dns-zone:0.7.1' = [ + for zone in objectKeys(openAiPrivateDnsZones): if (virtualNetworkEnabled) { + name: 'network-dns-zone-${uniqueString(deployment().name, zone)}' + params: { + name: zone + tags: tags + enableTelemetry: enableTelemetry + virtualNetworkLinks: [{ virtualNetworkResourceId: virtualNetwork.outputs.resourceId }] + } + } +] + +// ========== AI Foundry: AI Services ========== +// NOTE: Required version 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' not available in AVM +var aiFoundryAiServicesModelDeployment = { + format: 'OpenAI' + name: 'gpt-4o' + version: '2024-08-06' + sku: { + name: 'GlobalStandard' + capacity: 50 + } + raiPolicyName: 'Microsoft.Default' +} + +var aiFoundryAiServicesAccountName = '${solutionPrefix}aifdaisv' +module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.10.2' = { + name: 'cognitive-services-account' + params: { + name: aiFoundryAiServicesAccountName + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + sku: 'S0' + kind: 'AIServices' + disableLocalAuth: false //Should be set to true for WAF aligned configuration + customSubDomainName: aiFoundryAiServicesAccountName + apiProperties: { + //staticsEnabled: false + } + //publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + publicNetworkAccess: 'Enabled' //TODO: connection via private endpoint is not working from containers network. Change this when fixed + privateEndpoints: virtualNetworkEnabled + ? ([ + { + subnetResourceId: virtualNetwork.outputs.subnetResourceIds[0] + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: map(objectKeys(openAiPrivateDnsZones), zone => { + name: replace(zone, '.', '-') + privateDnsZoneResourceId: resourceId('Microsoft.Network/privateDnsZones', zone) + }) + } + } + ]) + : [] + roleAssignments: [ + // { + // principalId: userAssignedIdentity.outputs.principalId + // principalType: 'ServicePrincipal' + // roleDefinitionIdOrName: 'Cognitive Services OpenAI User' + // } + { + principalId: containerApp.outputs.?systemAssignedMIPrincipalId! + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Cognitive Services OpenAI User' + } + ] + deployments: [ + { + name: aiFoundryAiServicesModelDeployment.name + model: { + format: aiFoundryAiServicesModelDeployment.format + name: aiFoundryAiServicesModelDeployment.name + version: aiFoundryAiServicesModelDeployment.version + } + raiPolicyName: aiFoundryAiServicesModelDeployment.raiPolicyName + sku: { + name: aiFoundryAiServicesModelDeployment.sku.name + capacity: aiFoundryAiServicesModelDeployment.sku.capacity + } + } + ] + } +} + +// AI Foundry: storage account + +var storageAccountPrivateDnsZones = { + 'privatelink.blob.${environment().suffixes.storage}': 'blob' + 'privatelink.file.${environment().suffixes.storage}': 'file' +} + +module privateDnsZonesAiFoundryStorageAccount 'br/public:avm/res/network/private-dns-zone:0.3.1' = [ + for zone in objectKeys(storageAccountPrivateDnsZones): if (virtualNetworkEnabled) { + name: 'network-dns-zone-aifd-stac-${zone}' + params: { + name: zone + tags: tags + enableTelemetry: enableTelemetry + virtualNetworkLinks: [ + { + virtualNetworkResourceId: virtualNetwork.outputs.resourceId + } + ] + } + } +] + +var aiFoundryStorageAccountName = '${solutionPrefix}aifdstrg' +module aiFoundryStorageAccount 'br/public:avm/res/storage/storage-account:0.18.2' = { + name: 'storage-storage-account' + dependsOn: [ + privateDnsZonesAiFoundryStorageAccount + ] + params: { + name: aiFoundryStorageAccountName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + skuName: 'Standard_LRS' + allowSharedKeyAccess: false + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Allow' + } + blobServices: { + deleteRetentionPolicyEnabled: false + containerDeleteRetentionPolicyDays: 7 + containerDeleteRetentionPolicyEnabled: false + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + } + publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + allowBlobPublicAccess: virtualNetworkEnabled ? false : true + privateEndpoints: virtualNetworkEnabled + ? map(items(storageAccountPrivateDnsZones), zone => { + name: 'pep-${zone.value}-${aiFoundryStorageAccountName}' + customNetworkInterfaceName: 'nic-${zone.value}-${aiFoundryStorageAccountName}' + service: zone.value + subnetResourceId: virtualNetwork.outputs.subnetResourceIds[0] ?? '' + privateDnsZoneResourceIds: [resourceId('Microsoft.Network/privateDnsZones', zone.key)] + }) + : null + roleAssignments: [ + { + principalId: userAssignedIdentity.outputs.principalId + roleDefinitionIdOrName: 'Storage Blob Data Contributor' + } + ] + } +} + +// AI Foundry: AI Hub +var mlTargetSubResource = 'amlworkspace' +var mlPrivateDnsZones = { + 'privatelink.api.azureml.ms': mlTargetSubResource + 'privatelink.notebooks.azure.net': mlTargetSubResource +} +module privateDnsZonesAiFoundryWorkspaceHub 'br/public:avm/res/network/private-dns-zone:0.3.1' = [ + for zone in objectKeys(mlPrivateDnsZones): if (virtualNetworkEnabled) { + name: 'network-dns-zone-${zone}' + params: { + name: zone + enableTelemetry: enableTelemetry + tags: tags + virtualNetworkLinks: [ + { + virtualNetworkResourceId: virtualNetwork.outputs.resourceId + } + ] + } + } +] +var aiFoundryAiHubName = '${solutionPrefix}aifdaihb' +module aiFoundryAiHub 'modules/ai-hub.bicep' = { + name: 'modules-ai-hub' + dependsOn: [ + privateDnsZonesAiFoundryWorkspaceHub + ] + params: { + name: aiFoundryAiHubName + location: solutionLocation + tags: tags + aiFoundryAiServicesName: aiFoundryAiServices.outputs.name + applicationInsightsResourceId: applicationInsights.outputs.resourceId + enableTelemetry: enableTelemetry + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.resourceId + storageAccountResourceId: aiFoundryStorageAccount.outputs.resourceId + virtualNetworkEnabled: virtualNetworkEnabled + privateEndpoints: virtualNetworkEnabled + ? [ + { + name: 'pep-${mlTargetSubResource}-${aiFoundryAiHubName}' + customNetworkInterfaceName: 'nic-${mlTargetSubResource}-${aiFoundryAiHubName}' + service: mlTargetSubResource + subnetResourceId: virtualNetworkEnabled ? virtualNetwork.?outputs.?subnetResourceIds[0] : null + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: map(objectKeys(mlPrivateDnsZones), zone => { + name: replace(zone, '.', '-') + privateDnsZoneResourceId: resourceId('Microsoft.Network/privateDnsZones', zone) + }) + } + } + ] + : [] + } +} + +// AI Foundry: AI Project +var aiFoundryAiProjectName = '${solutionPrefix}aifdaipj' + +module aiFoundryAiProject 'br/public:avm/res/machine-learning-services/workspace:0.12.0' = { + name: 'machine-learning-services-workspace-project' + params: { + name: aiFoundryAiProjectName + location: solutionLocation + tags: tags + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + sku: 'Basic' + kind: 'Project' + hubResourceId: aiFoundryAiHub.outputs.resourceId + roleAssignments: [ + { + principalId: containerApp.outputs.?systemAssignedMIPrincipalId! + // Assigning the role with the role name instead of the role ID freezes the deployment at this point + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' //'Azure AI Developer' + } + ] + } +} + +// ========== DNS Zone for Cosmos DB ========== // +module privateDnsZonesCosmosDb 'br/public:avm/res/network/private-dns-zone:0.7.0' = if (virtualNetworkEnabled) { + name: 'network-dns-zone-cosmos-db' + params: { + name: 'privatelink.documents.azure.com' + enableTelemetry: enableTelemetry + virtualNetworkLinks: [{ virtualNetworkResourceId: virtualNetwork.outputs.resourceId }] + tags: tags + } +} + +// ========== Cosmos DB ========== // +var cosmosDbName = '${solutionPrefix}csdb' +var cosmosDbDatabaseName = 'autogen' +var cosmosDbDatabaseMemoryContainerName = 'memory' +module cosmosDb 'br/public:avm/res/document-db/database-account:0.12.0' = { + name: 'cosmos-db' + params: { + // Required parameters + name: cosmosDbName + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + databaseAccountOfferType: 'Standard' + enableFreeTier: false + networkRestrictions: { + networkAclBypass: 'None' + publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' + } + privateEndpoints: virtualNetworkEnabled + ? [ + { + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [{ privateDnsZoneResourceId: privateDnsZonesCosmosDb.outputs.resourceId }] + } + service: 'Sql' + subnetResourceId: virtualNetwork.outputs.subnetResourceIds[0] + } + ] + : [] + sqlDatabases: [ + { + name: cosmosDbDatabaseName + containers: [ + { + name: cosmosDbDatabaseMemoryContainerName + paths: [ + '/session_id' + ] + kind: 'Hash' + version: 2 + } + ] + } + ] + locations: [ + { + locationName: solutionLocation + failoverPriority: 0 + } + ] + capabilitiesToAdd: [ + 'EnableServerless' + ] + sqlRoleAssignmentsPrincipalIds: [ + //userAssignedIdentity.outputs.principalId + containerApp.outputs.?systemAssignedMIPrincipalId + ] + sqlRoleDefinitions: [ + { + // Replace this with built-in role definition Cosmos DB Built-in Data Contributor: https://docs.azure.cn/en-us/cosmos-db/nosql/security/reference-data-plane-roles#cosmos-db-built-in-data-contributor + roleType: 'CustomRole' + roleName: 'Cosmos DB SQL Data Contributor' + name: 'cosmos-db-sql-data-contributor' + dataAction: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + ] + } + ] + } +} + +// ========== Backend Container App Environment ========== // + +module containerAppEnvironment 'modules/container-app-environment.bicep' = { + name: 'modules-container-app-environment' + params: { + name: '${solutionPrefix}cenv' + tags: tags + location: solutionLocation + logAnalyticsResourceName: logAnalyticsWorkspace.outputs.name + publicNetworkAccess: 'Enabled' + zoneRedundant: virtualNetworkEnabled ? true : false + aspireDashboardEnabled: !virtualNetworkEnabled + vnetConfiguration: virtualNetworkEnabled + ? { + internal: false + infrastructureSubnetId: virtualNetwork.?outputs.?subnetResourceIds[2] ?? '' + } + : {} + } +} + +// module containerAppEnvironment 'br/public:avm/res/app/managed-environment:0.11.0' = { +// name: 'container-app-environment' +// params: { +// name: '${solutionPrefix}cenv' +// location: solutionLocation +// tags: tags +// enableTelemetry: enableTelemetry +// //daprAIConnectionString: applicationInsights.outputs.connectionString //Troubleshoot: ContainerAppsConfiguration.DaprAIConnectionString is invalid. DaprAIConnectionString can not be set when AppInsightsConfiguration has been set, please set DaprAIConnectionString to null. (Code:InvalidRequestParameterWithDetails +// appLogsConfiguration: { +// destination: 'log-analytics' +// logAnalyticsConfiguration: { +// customerId: logAnalyticsWorkspace.outputs.logAnalyticsWorkspaceId +// sharedKey: listKeys( +// '${resourceGroup().id}/providers/Microsoft.OperationalInsights/workspaces/${logAnalyticsWorkspaceName}', +// '2023-09-01' +// ).primarySharedKey +// } +// } +// appInsightsConnectionString: applicationInsights.outputs.connectionString +// publicNetworkAccess: virtualNetworkEnabled ? 'Disabled' : 'Enabled' //TODO: use Azure Front Door WAF or Application Gateway WAF instead +// zoneRedundant: true //TODO: make it zone redundant for waf aligned +// infrastructureSubnetResourceId: virtualNetworkEnabled +// ? virtualNetwork.outputs.subnetResourceIds[1] +// : null +// internal: false +// } +// } + +// ========== Backend Container App Service ========== // +module containerApp 'br/public:avm/res/app/container-app:0.14.2' = { + name: 'container-app' + params: { + name: '${solutionPrefix}capp' + tags: tags + location: solutionLocation + enableTelemetry: enableTelemetry + //environmentResourceId: containerAppEnvironment.outputs.resourceId + environmentResourceId: containerAppEnvironment.outputs.resourceId + managedIdentities: { + systemAssigned: true //Replace with user assigned identity + userAssignedResourceIds: [userAssignedIdentity.outputs.resourceId] + } + ingressTargetPort: 8000 + ingressExternal: true + activeRevisionsMode: 'Single' + corsPolicy: { + allowedOrigins: [ + 'https://${webSiteName}.azurewebsites.net' + 'http://${webSiteName}.azurewebsites.net' + ] + } + scaleSettings: { + //TODO: Make maxReplicas and minReplicas parameterized + maxReplicas: 1 + minReplicas: 1 + rules: [ + { + name: 'http-scaler' + http: { + metadata: { + concurrentRequests: '100' + } + } + } + ] + } + containers: [ + { + name: 'backend' + //TODO: Make image parameterized for the registry name and the appversion + image: 'biabcontainerreg.azurecr.io/macaebackend:fnd01' + resources: { + //TODO: Make cpu and memory parameterized + cpu: '2.0' + memory: '4.0Gi' + } + env: [ + { + name: 'COSMOSDB_ENDPOINT' + value: 'https://${cosmosDbName}.documents.azure.com:443/' + } + { + name: 'COSMOSDB_DATABASE' + value: cosmosDbDatabaseName + } + { + name: 'COSMOSDB_CONTAINER' + value: cosmosDbDatabaseMemoryContainerName + } + { + name: 'AZURE_OPENAI_ENDPOINT' + value: 'https://${aiFoundryAiServicesAccountName}.openai.azure.com/' + } + { + name: 'AZURE_OPENAI_MODEL_NAME' + value: aiFoundryAiServicesModelDeployment.name + } + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME' + value: aiFoundryAiServicesModelDeployment.name + } + { + name: 'AZURE_OPENAI_API_VERSION' + value: '2025-01-01-preview' //TODO: set parameter/variable + } + { + name: 'APPLICATIONINSIGHTS_INSTRUMENTATION_KEY' + value: applicationInsights.outputs.instrumentationKey + } + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: applicationInsights.outputs.connectionString + } + { + name: 'AZURE_AI_AGENT_PROJECT_CONNECTION_STRING' + value: '${toLower(replace(solutionLocation,' ',''))}.api.azureml.ms;${subscription().subscriptionId};${resourceGroup().name};${aiFoundryAiProjectName}' + //Location should be the AI Foundry AI Project location + } + { + name: 'AZURE_AI_SUBSCRIPTION_ID' + value: subscription().subscriptionId + } + { + name: 'AZURE_AI_RESOURCE_GROUP' + value: resourceGroup().name + } + { + name: 'AZURE_AI_PROJECT_NAME' + value: aiFoundryAiProjectName + } + { + name: 'FRONTEND_SITE_NAME' + value: 'https://${webSiteName}.azurewebsites.net' + } + ] + } + ] + } +} + +// ========== Frontend server farm ========== // +module webServerfarm 'br/public:avm/res/web/serverfarm:0.4.1' = { + name: 'web-server-farm' + params: { + tags: tags + location: solutionLocation + name: '${solutionPrefix}sfrm' + skuName: 'P1v2' + skuCapacity: 1 + reserved: true + diagnosticSettings: [{ workspaceResourceId: logAnalyticsWorkspace.outputs.resourceId }] + kind: 'linux' + zoneRedundant: false //TODO: make it zone redundant for waf aligned + } +} + +// ========== Entra ID Application ========== // +resource entraIdApplication 'Microsoft.Graph/applications@v1.0' = if (entraIdApplicationConfiguration.?enabled!) { + displayName: '${webSiteName}-app' + uniqueName: '${webSiteName}-app-${uniqueString(resourceGroup().id, webSiteName)}' + description: 'EntraId Application for ${webSiteName} authentication' + passwordCredentials: [ + { + displayName: 'Credential for website ${webSiteName}' + endDateTime: dateTimeAdd(deploymentTime, 'P180D') + // keyId: 'string' + // startDateTime: 'string' + } + ] +} + +var graphAppId = '00000003-0000-0000-c000-000000000000' //Microsoft Graph ID +// Get the Microsoft Graph service principal so that the scope names can be looked up and mapped to a permission ID +resource msGraphSP 'Microsoft.Graph/servicePrincipals@v1.0' existing = { + appId: graphAppId +} + +// ========== Entra ID Service Principal ========== // +resource entraIdServicePrincipal 'Microsoft.Graph/servicePrincipals@v1.0' = if (entraIdApplicationConfiguration.?enabled!) { + appId: entraIdApplication.appId +} + +// Grant the OAuth2.0 scopes (requested in parameters) to the basic app, for all users in the tenant +resource graphScopesAssignment 'Microsoft.Graph/oauth2PermissionGrants@v1.0' = if (entraIdApplicationConfiguration.?enabled!) { + clientId: entraIdServicePrincipal.id + resourceId: msGraphSP.id + consentType: 'AllPrincipals' + scope: 'User.Read' +} + +// ========== Frontend web site ========== // +var webSiteName = '${solutionPrefix}wapp' +var entraIdApplicationCredentialSecretSettingName = 'MICROSOFT_PROVIDER_AUTHENTICATION_SECRET' +module webSite 'br/public:avm/res/web/site:0.15.1' = { + name: 'web-site' + params: { + tags: tags + kind: 'app,linux,container' + name: webSiteName + location: solutionLocation + serverFarmResourceId: webServerfarm.outputs.resourceId + appInsightResourceId: applicationInsights.outputs.resourceId + siteConfig: { + linuxFxVersion: 'DOCKER|biabcontainerreg.azurecr.io/macaefrontend:fnd01' + } + publicNetworkAccess: 'Enabled' //TODO: use Azure Front Door WAF or Application Gateway WAF instead + //privateEndpoints: [{ subnetResourceId: virtualNetwork.outputs.subnetResourceIds[0] }] + //Not required, this resource only serves a static website + appSettingsKeyValuePairs: union( + { + SCM_DO_BUILD_DURING_DEPLOYMENT: 'true' + DOCKER_REGISTRY_SERVER_URL: 'https://biabcontainerreg.azurecr.io' + WEBSITES_PORT: '3000' + WEBSITES_CONTAINER_START_TIME_LIMIT: '1800' // 30 minutes, adjust as needed + BACKEND_API_URL: 'https://${containerApp.outputs.fqdn}' + AUTH_ENABLED: 'false' + }, + (entraIdApplicationConfiguration.?enabled! + ? { '${entraIdApplicationCredentialSecretSettingName}': entraIdApplication.passwordCredentials[0].secretText } + : {}) + ) + authSettingV2Configuration: { + platform: { + enabled: entraIdApplicationConfiguration.?enabled! + runtimeVersion: '~1' + } + login: { + cookieExpiration: { + convention: 'FixedTime' + timeToExpiration: '08:00:00' + } + nonce: { + nonceExpirationInterval: '00:05:00' + validateNonce: true + } + preserveUrlFragmentsForLogins: false + routes: {} + tokenStore: { + azureBlobStorage: {} + enabled: true + fileSystem: {} + tokenRefreshExtensionHours: 72 + } + } + globalValidation: { + requireAuthentication: true + unauthenticatedClientAction: 'RedirectToLoginPage' + redirectToProvider: 'azureactivedirectory' + } + httpSettings: { + forwardProxy: { + convention: 'NoProxy' + } + requireHttps: true + routes: { + apiPrefix: '/.auth' + } + } + identityProviders: { + azureActiveDirectory: entraIdApplicationConfiguration.?enabled! + ? { + isAutoProvisioned: true + enabled: true + login: { + disableWWWAuthenticate: false + } + registration: { + clientId: entraIdApplication.appId //create application in AAD + clientSecretSettingName: entraIdApplicationCredentialSecretSettingName + openIdIssuer: 'https://sts.windows.net/${tenant().tenantId}/v2.0/' + } + validation: { + allowedAudiences: [ + 'api://${entraIdApplication.appId}' + ] + defaultAuthorizationPolicy: { + allowedPrincipals: {} + allowedApplications: ['86e2d249-6832-461f-8888-cfa0394a5f8c'] + } + jwtClaimChecks: {} + } + } + : {} + } + } + } +} + +// ============ // +// Outputs // +// ============ // + +// Add your outputs here + +// @description('The resource ID of the resource.') +// output resourceId string = .id + +// @description('The name of the resource.') +// output name string = .name + +// @description('The location the resource was deployed into.') +// output location string = .location + +// ================ // +// Definitions // +// ================ // +// +// Add your User-defined-types here, if any +// + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Log Analytics Workspace resource configuration.') +type logAnalyticsWorkspaceConfigurationType = { + @description('Optional. If the Log Analytics Workspace resource should be enabled or not.') + enabled: bool? + + @description('Optional. The name of the Log Analytics Workspace resource.') + @maxLength(63) + name: string? + + @description('Optional. Location for the Log Analytics Workspace resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to for the Log Analytics Workspace resource.') + tags: object? + + @description('Optional. The SKU for the Log Analytics Workspace resource.') + sku: ('CapacityReservation' | 'Free' | 'LACluster' | 'PerGB2018' | 'PerNode' | 'Premium' | 'Standalone' | 'Standard')? + + @description('Optional. The number of days to retain the data in the Log Analytics Workspace. If empty, it will be set to 30 days.') + @maxValue(730) + dataRetentionInDays: int? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Application Insights resource configuration.') +type applicationInsightsConfigurationType = { + @description('Optional. If the Application Insights resource should be enabled or not.') + enabled: bool? + + @description('Optional. The name of the Application Insights resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Application Insights resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Application Insights resource.') + tags: object? + + @description('Optional. The retention of Application Insights data in days. If empty, Standard will be used.') + retentionInDays: (120 | 180 | 270 | 30 | 365 | 550 | 60 | 730 | 90)? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Application User Assigned Managed Identity resource configuration.') +type userAssignedManagedIdentityType = { + @description('Optional. If the User Assigned Managed Identity resource should be enabled or not.') + enabled: bool? + + @description('Optional. The name of the User Assigned Managed Identity resource.') + @maxLength(128) + name: string? + + @description('Optional. Location for the User Assigned Managed Identity resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the User Assigned Managed Identity resource.') + tags: object? +} + +@export() +import { securityRuleType } from 'br/public:avm/res/network/network-security-group:0.5.1' +@description('The type for the Multi-Agent Custom Automation Engine Network Security Group resource configuration.') +type networkSecurityGroupConfigurationType = { + @description('Optional. If the Network Security Group resource should be enabled or not.') + enabled: bool? + + @description('Optional. The name of the Network Security Group resource.') + @maxLength(90) + name: string? + + @description('Optional. Location for the Network Security Group resource.') + @metadata({ azd: { type: 'location' } }) + location: string? + + @description('Optional. The tags to set for the Network Security Group resource.') + tags: object? + + @description('Optional. The security rules to set for the Network Security Group resource.') + securityRules: securityRuleType[]? +} + +@export() +@description('The type for the Multi-Agent Custom Automation virtual machine resource configuration.') +type virtualMachineConfigurationType = { + @description('Optional. If the Virtual Machine resource should be enabled or not.') + enabled: bool? + + @description('Required. The username for the administrator account on the virtual machine. Required if a virtual machine is created as part of the module.') + adminUsername: string? + + @description('Required. The password for the administrator account on the virtual machine. Required if a virtual machine is created as part of the module.') + @secure() + adminPassword: string? +} + +@export() +@description('The type for the Multi-Agent Custom Automation virtual network resource configuration.') +type virtualNetworkConfigurationType = { + @description('Optional. If the Virtual Network resource should be enabled or not.') + enabled: bool? +} + +@export() +@description('The type for the Multi-Agent Custom Automation Engine Entra ID Application resource configuration.') +type entraIdApplicationConfigurationType = { + @description('Optional. If the Entra ID Application for website authentication should be enabled or not.') + enabled: bool? +} diff --git a/infra/main2.bicep b/infra/old/main2.bicep similarity index 100% rename from infra/main2.bicep rename to infra/old/main2.bicep diff --git a/infra/resources.bicep b/infra/old/resources.bicep similarity index 100% rename from infra/resources.bicep rename to infra/old/resources.bicep diff --git a/src/frontend/wwwroot/task/task.js b/src/frontend/wwwroot/task/task.js index d0bf85dac..5766d9001 100644 --- a/src/frontend/wwwroot/task/task.js +++ b/src/frontend/wwwroot/task/task.js @@ -536,6 +536,12 @@ ${markdownConverter.makeHtml( message.content )} ${approveAllStagesButton} + ${message.source !== "Planner_Agent" && message.source !== "Group_Chat_Manager" ? ` +
+ Disclaimer: Example function stub - not implemented +
+ ` : ''} +