From 0894b64f43ff6064a59d9f810706213762e51e42 Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Wed, 18 Feb 2026 17:05:09 -0800 Subject: [PATCH 1/8] Add support for existing container registry parameters in main.bicep Added parameters for existing container registry and endpoint. Signed-off-by: Travis Angevine --- infra/main.bicep | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 3675047..e647b95 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -84,6 +84,12 @@ param enableHostedAgents bool @description('Enable monitoring for the AI project') param enableMonitoring bool = true +@description('Optional. Existing container registry resource ID. If provided, no new ACR will be created and a connection to this ACR will be established.') +param existingContainerRegistryResourceId string = '' + +@description('Optional. Existing container registry endpoint (login server). Required if existingContainerRegistryResourceId is provided.') +param existingContainerRegistryEndpoint string = '' + // Tags that should be applied to all resources. // // Note that 'azd-service-name' tags should be applied separately to service host resources. @@ -102,8 +108,10 @@ resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { // Build dependent resources array conditionally // Check if ACR already exists in the user-provided array to avoid duplicates +// Also skip if user provided an existing container registry endpoint var hasAcr = contains(map(aiProjectDependentResources, r => r.resource), 'registry') -var dependentResources = (enableHostedAgents) && !hasAcr ? union(aiProjectDependentResources, [ +var shouldCreateAcr = enableHostedAgents && !hasAcr && empty(existingContainerRegistryResourceId) +var dependentResources = shouldCreateAcr ? union(aiProjectDependentResources, [ { resource: 'registry' connectionName: 'acr-connection' @@ -126,6 +134,8 @@ module aiProject 'core/ai/ai-project.bicep' = { additionalDependentResources: dependentResources enableMonitoring: enableMonitoring enableHostedAgents: enableHostedAgents + existingContainerRegistryResourceId: existingContainerRegistryResourceId + existingContainerRegistryEndpoint: existingContainerRegistryEndpoint } } @@ -166,3 +176,4 @@ output AZURE_AI_SEARCH_SERVICE_NAME string = aiProject.outputs.dependentResource output AZURE_STORAGE_CONNECTION_NAME string = aiProject.outputs.dependentResources.storage.connectionName output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName + From f49f2539f72cef5efadd4428db624df106e75b19 Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Wed, 18 Feb 2026 17:05:38 -0800 Subject: [PATCH 2/8] Add existing container registry parameters Added parameters for existing container registry. Signed-off-by: Travis Angevine --- infra/main.parameters.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 323829e..2c441b3 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -40,6 +40,13 @@ }, "enableHostedAgents": { "value": "${ENABLE_HOSTED_AGENTS=false}" + }, + "existingContainerRegistryResourceId": { + "value": "${AZURE_CONTAINER_REGISTRY_RESOURCE_ID=}" + }, + "existingContainerRegistryEndpoint": { + "value": "${AZURE_CONTAINER_REGISTRY_ENDPOINT=}" } } } + From 916d43da257939a08c5bf30fb73f307dcfe10f22 Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Wed, 18 Feb 2026 17:07:14 -0800 Subject: [PATCH 3/8] Enhance AI project Bicep file with existing ACR support Added support for existing container registry connections and enhanced resource provisioning logic. Signed-off-by: Travis Angevine --- infra/core/ai/ai-project.bicep | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/infra/core/ai/ai-project.bicep b/infra/core/ai/ai-project.bicep index d0e8753..d94e7e6 100644 --- a/infra/core/ai/ai-project.bicep +++ b/infra/core/ai/ai-project.bicep @@ -34,12 +34,19 @@ param enableMonitoring bool = true @description('Enable hosted agent deployment') param enableHostedAgents bool = false +@description('Optional. Existing container registry resource ID. If provided, a connection will be created to this ACR instead of creating a new one.') +param existingContainerRegistryResourceId string = '' + +@description('Optional. Existing container registry login server (e.g., myregistry.azurecr.io). Required if existingContainerRegistryResourceId is provided.') +param existingContainerRegistryEndpoint string = '' + // Load abbreviations var abbrs = loadJsonContent('../../abbreviations.json') // Determine which resources to create based on connections var hasStorageConnection = length(filter(additionalDependentResources, conn => conn.resource == 'storage')) > 0 var hasAcrConnection = length(filter(additionalDependentResources, conn => conn.resource == 'registry')) > 0 +var hasExistingAcr = !empty(existingContainerRegistryResourceId) var hasSearchConnection = length(filter(additionalDependentResources, conn => conn.resource == 'azure_ai_search')) > 0 var hasBingConnection = length(filter(additionalDependentResources, conn => conn.resource == 'bing_grounding')) > 0 var hasBingCustomConnection = length(filter(additionalDependentResources, conn => conn.resource == 'bing_custom_grounding')) > 0 @@ -233,6 +240,29 @@ module acr '../host/acr.bicep' = if (hasAcrConnection) { } } +// Connection for existing ACR - create if user provided an existing ACR resource ID +module existingAcrConnection './connection.bicep' = if (hasExistingAcr) { + name: 'existing-acr-connection' + params: { + aiServicesAccountName: aiAccount.name + aiProjectName: aiAccount::project.name + connectionConfig: { + name: 'acr-connection' + category: 'ContainerRegistry' + target: existingContainerRegistryEndpoint + authType: 'ManagedIdentity' + credentials: { + clientId: aiAccount::project.identity.principalId + resourceId: existingContainerRegistryResourceId + } + isSharedToAll: true + metadata: { + ResourceId: existingContainerRegistryResourceId + } + } + } +} + // Bing Search grounding module - deploy if Bing connection is defined in ai.yaml or parameter is enabled module bingGrounding '../search/bing_grounding.bicep' = if (hasBingConnection) { name: 'bing-grounding' @@ -291,8 +321,8 @@ output APPLICATIONINSIGHTS_CONNECTION_STRING string = applicationInsights.output output dependentResources object = { registry: { name: hasAcrConnection ? acr!.outputs.containerRegistryName : '' - loginServer: hasAcrConnection ? acr!.outputs.containerRegistryLoginServer : '' - connectionName: hasAcrConnection ? acr!.outputs.containerRegistryConnectionName : '' + loginServer: hasAcrConnection ? acr!.outputs.containerRegistryLoginServer : (hasExistingAcr ? existingContainerRegistryEndpoint : '') + connectionName: hasAcrConnection ? acr!.outputs.containerRegistryConnectionName : (hasExistingAcr ? 'acr-connection' : '') } bing_grounding: { name: (hasBingConnection) ? bingGrounding!.outputs.bingGroundingName : '' @@ -347,3 +377,4 @@ type dependentResourcesType = { @description('The connection name for this resource') connectionName: string }[] + From f5efe6838d5aa3175a5b057f5a5475a7c7439350 Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Thu, 19 Feb 2026 08:28:35 -0800 Subject: [PATCH 4/8] Refactor AI project Bicep file structure and parameters Signed-off-by: Travis Angevine --- infra/core/ai/ai-project.bicep | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/infra/core/ai/ai-project.bicep b/infra/core/ai/ai-project.bicep index d94e7e6..7581b98 100644 --- a/infra/core/ai/ai-project.bicep +++ b/infra/core/ai/ai-project.bicep @@ -263,6 +263,25 @@ module existingAcrConnection './connection.bicep' = if (hasExistingAcr) { } } +// Extract resource group name from the existing ACR resource ID +// Resource ID format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ContainerRegistry/registries/{name} +var existingAcrResourceGroup = hasExistingAcr ? split(existingContainerRegistryResourceId, '/')[4] : '' +var existingAcrName = hasExistingAcr ? last(split(existingContainerRegistryResourceId, '/')) : '' + +// Grant AcrPull role to the AI project's managed identity on the existing ACR +// This allows the hosted agents to pull images from the user-provided registry +// Note: User must have permission to assign roles on the existing ACR (Owner or User Access Administrator) +// Using a module allows scoping to a different resource group if the ACR isn't in the same RG +module existingAcrRoleAssignment './acr-role-assignment.bicep' = if (hasExistingAcr) { + name: 'existing-acr-role-assignment' + scope: resourceGroup(existingAcrResourceGroup) + params: { + acrName: existingAcrName + acrResourceId: existingContainerRegistryResourceId + principalId: aiAccount::project.identity.principalId + } +} + // Bing Search grounding module - deploy if Bing connection is defined in ai.yaml or parameter is enabled module bingGrounding '../search/bing_grounding.bicep' = if (hasBingConnection) { name: 'bing-grounding' @@ -378,3 +397,4 @@ type dependentResourcesType = { connectionName: string }[] + From de83819a7c62dddf73c7e6bf2df6f206d3af192c Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Thu, 19 Feb 2026 08:29:11 -0800 Subject: [PATCH 5/8] Add Bicep file for ACR role assignment This Bicep file defines a role assignment for granting the AcrPull role to a specified principal in an existing Azure Container Registry. Signed-off-by: Travis Angevine --- infra/core/ai/acr-role-assignment.bicep | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 infra/core/ai/acr-role-assignment.bicep diff --git a/infra/core/ai/acr-role-assignment.bicep b/infra/core/ai/acr-role-assignment.bicep new file mode 100644 index 0000000..3e0c2b2 --- /dev/null +++ b/infra/core/ai/acr-role-assignment.bicep @@ -0,0 +1,27 @@ +targetScope = 'resourceGroup' + +@description('Name of the existing container registry') +param acrName string + +@description('Principal ID to grant AcrPull role') +param principalId string + +@description('Full resource ID of the ACR (for generating unique GUID)') +param acrResourceId string + +// Reference the existing ACR in this resource group +resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = { + name: acrName +} + +// Grant AcrPull role to the AI project's managed identity +resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: acr + name: guid(acrResourceId, principalId, '7f951dda-4ed3-4680-a7ca-43fe172d538d') + properties: { + principalId: principalId + principalType: 'ServicePrincipal' + // AcrPull role + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + } +} From 0a481936277e599d3d7e324ea17c1b6800c8ca6e Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Fri, 20 Feb 2026 09:49:57 -0800 Subject: [PATCH 6/8] Update parameters for AI project Bicep file Added optional parameters for existing ACR and Application Insights connections. Signed-off-by: Travis Angevine --- infra/main.bicep | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index e647b95..66e7327 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -90,6 +90,18 @@ param existingContainerRegistryResourceId string = '' @description('Optional. Existing container registry endpoint (login server). Required if existingContainerRegistryResourceId is provided.') param existingContainerRegistryEndpoint string = '' +@description('Optional. Name of an existing ACR connection on the Foundry project. If provided, no new ACR or connection will be created.') +param existingAcrConnectionName string = '' + +@description('Optional. Existing Application Insights connection string. If provided, a connection will be created but no new App Insights resource.') +param existingApplicationInsightsConnectionString string = '' + +@description('Optional. Existing Application Insights resource ID. Used for connection metadata when providing an existing App Insights.') +param existingApplicationInsightsResourceId string = '' + +@description('Optional. Name of an existing Application Insights connection on the Foundry project. If provided, no new App Insights or connection will be created.') +param existingAppInsightsConnectionName string = '' + // Tags that should be applied to all resources. // // Note that 'azd-service-name' tags should be applied separately to service host resources. @@ -108,9 +120,9 @@ resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { // Build dependent resources array conditionally // Check if ACR already exists in the user-provided array to avoid duplicates -// Also skip if user provided an existing container registry endpoint +// Also skip if user provided an existing container registry endpoint or connection name var hasAcr = contains(map(aiProjectDependentResources, r => r.resource), 'registry') -var shouldCreateAcr = enableHostedAgents && !hasAcr && empty(existingContainerRegistryResourceId) +var shouldCreateAcr = enableHostedAgents && !hasAcr && empty(existingContainerRegistryResourceId) && empty(existingAcrConnectionName) var dependentResources = shouldCreateAcr ? union(aiProjectDependentResources, [ { resource: 'registry' @@ -136,6 +148,10 @@ module aiProject 'core/ai/ai-project.bicep' = { enableHostedAgents: enableHostedAgents existingContainerRegistryResourceId: existingContainerRegistryResourceId existingContainerRegistryEndpoint: existingContainerRegistryEndpoint + existingAcrConnectionName: existingAcrConnectionName + existingApplicationInsightsConnectionString: existingApplicationInsightsConnectionString + existingApplicationInsightsResourceId: existingApplicationInsightsResourceId + existingAppInsightsConnectionName: existingAppInsightsConnectionName } } @@ -175,5 +191,3 @@ output AZURE_AI_SEARCH_SERVICE_NAME string = aiProject.outputs.dependentResource // Azure Storage output AZURE_STORAGE_CONNECTION_NAME string = aiProject.outputs.dependentResources.storage.connectionName output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName - - From 549adcbd178aa3c61085b38a94440b940b429245 Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Fri, 20 Feb 2026 09:50:16 -0800 Subject: [PATCH 7/8] Update main.parameters.json with new parameters Added new parameters for existing ACR and Application Insights connections. Signed-off-by: Travis Angevine --- infra/main.parameters.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 2c441b3..926e0e2 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -46,7 +46,18 @@ }, "existingContainerRegistryEndpoint": { "value": "${AZURE_CONTAINER_REGISTRY_ENDPOINT=}" + }, + "existingAcrConnectionName": { + "value": "${AZURE_AI_PROJECT_ACR_CONNECTION_NAME=}" + }, + "existingApplicationInsightsConnectionString": { + "value": "${APPLICATIONINSIGHTS_CONNECTION_STRING=}" + }, + "existingApplicationInsightsResourceId": { + "value": "${APPLICATIONINSIGHTS_RESOURCE_ID=}" + }, + "existingAppInsightsConnectionName": { + "value": "${APPLICATIONINSIGHTS_CONNECTION_NAME=}" } } } - From 3ba5bf75c58418017c22cb5d2ec1a149b0c0fed6 Mon Sep 17 00:00:00 2001 From: Travis Angevine Date: Fri, 20 Feb 2026 09:50:45 -0800 Subject: [PATCH 8/8] Enhance AI project Bicep template with existing resource support Refactor AI project Bicep template to support existing resources and connections. Added parameters for existing Application Insights and ACR connections. Signed-off-by: Travis Angevine --- infra/core/ai/ai-project.bicep | 60 ++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/infra/core/ai/ai-project.bicep b/infra/core/ai/ai-project.bicep index 7581b98..76ad057 100644 --- a/infra/core/ai/ai-project.bicep +++ b/infra/core/ai/ai-project.bicep @@ -40,6 +40,18 @@ param existingContainerRegistryResourceId string = '' @description('Optional. Existing container registry login server (e.g., myregistry.azurecr.io). Required if existingContainerRegistryResourceId is provided.') param existingContainerRegistryEndpoint string = '' +@description('Optional. Name of an existing ACR connection on the Foundry project. If provided, no new ACR or connection will be created.') +param existingAcrConnectionName string = '' + +@description('Optional. Existing Application Insights connection string. If provided, a connection will be created but no new App Insights resource.') +param existingApplicationInsightsConnectionString string = '' + +@description('Optional. Existing Application Insights resource ID. Used for connection metadata when providing an existing App Insights.') +param existingApplicationInsightsResourceId string = '' + +@description('Optional. Name of an existing Application Insights connection on the Foundry project. If provided, no new App Insights or connection will be created.') +param existingAppInsightsConnectionName string = '' + // Load abbreviations var abbrs = loadJsonContent('../../abbreviations.json') @@ -47,6 +59,11 @@ var abbrs = loadJsonContent('../../abbreviations.json') var hasStorageConnection = length(filter(additionalDependentResources, conn => conn.resource == 'storage')) > 0 var hasAcrConnection = length(filter(additionalDependentResources, conn => conn.resource == 'registry')) > 0 var hasExistingAcr = !empty(existingContainerRegistryResourceId) +var hasExistingAcrConnection = !empty(existingAcrConnectionName) +var hasExistingAppInsightsConnection = !empty(existingAppInsightsConnectionName) +var hasExistingAppInsightsConnectionString = !empty(existingApplicationInsightsConnectionString) +// Only create new App Insights resources if monitoring enabled and no existing connection/connection string +var shouldCreateAppInsights = enableMonitoring && !hasExistingAppInsightsConnection && !hasExistingAppInsightsConnectionString var hasSearchConnection = length(filter(additionalDependentResources, conn => conn.resource == 'azure_ai_search')) > 0 var hasBingConnection = length(filter(additionalDependentResources, conn => conn.resource == 'bing_grounding')) > 0 var hasBingCustomConnection = length(filter(additionalDependentResources, conn => conn.resource == 'bing_custom_grounding')) > 0 @@ -59,7 +76,7 @@ var bingConnectionName = hasBingConnection ? filter(additionalDependentResources var bingCustomConnectionName = hasBingCustomConnection ? filter(additionalDependentResources, conn => conn.resource == 'bing_custom_grounding')[0].connectionName : '' // Enable monitoring via Log Analytics and Application Insights -module logAnalytics '../monitor/loganalytics.bicep' = if (enableMonitoring) { +module logAnalytics '../monitor/loganalytics.bicep' = if (shouldCreateAppInsights) { name: 'logAnalytics' params: { location: location @@ -68,7 +85,7 @@ module logAnalytics '../monitor/loganalytics.bicep' = if (enableMonitoring) { } } -module applicationInsights '../monitor/applicationinsights.bicep' = if (enableMonitoring) { +module applicationInsights '../monitor/applicationinsights.bicep' = if (shouldCreateAppInsights) { name: 'applicationInsights' params: { location: location @@ -141,8 +158,8 @@ resource aiAccount 'Microsoft.CognitiveServices/accounts@2025-06-01' = { } -// Create connection towards appinsights -resource appInsightConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = { +// Create connection towards appinsights - only if we created a new App Insights resource +resource appInsightConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (shouldCreateAppInsights) { parent: aiAccount::project name: 'appi-connection' properties: { @@ -160,6 +177,25 @@ resource appInsightConnection 'Microsoft.CognitiveServices/accounts/projects/con } } +// Create connection to existing App Insights - if user provided connection string but no existing connection +resource existingAppInsightConnection 'Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview' = if (enableMonitoring && hasExistingAppInsightsConnectionString && !hasExistingAppInsightsConnection) { + parent: aiAccount::project + name: 'appi-connection' + properties: { + category: 'AppInsights' + target: existingApplicationInsightsResourceId + authType: 'ApiKey' + isSharedToAll: true + credentials: { + key: existingApplicationInsightsConnectionString + } + metadata: { + ApiType: 'Azure' + ResourceId: existingApplicationInsightsResourceId + } + } +} + // Create additional connections from ai.yaml configuration module aiConnections './connection.bicep' = [for (connection, index) in connections: { name: 'connection-${connection.name}' @@ -240,8 +276,8 @@ module acr '../host/acr.bicep' = if (hasAcrConnection) { } } -// Connection for existing ACR - create if user provided an existing ACR resource ID -module existingAcrConnection './connection.bicep' = if (hasExistingAcr) { +// Connection for existing ACR - create if user provided an existing ACR resource ID but no existing connection +module existingAcrConnection './connection.bicep' = if (hasExistingAcr && !hasExistingAcrConnection) { name: 'existing-acr-connection' params: { aiServicesAccountName: aiAccount.name @@ -272,7 +308,8 @@ var existingAcrName = hasExistingAcr ? last(split(existingContainerRegistryResou // This allows the hosted agents to pull images from the user-provided registry // Note: User must have permission to assign roles on the existing ACR (Owner or User Access Administrator) // Using a module allows scoping to a different resource group if the ACR isn't in the same RG -module existingAcrRoleAssignment './acr-role-assignment.bicep' = if (hasExistingAcr) { +// Skip if connection already exists (role assignment should already be in place) +module existingAcrRoleAssignment './acr-role-assignment.bicep' = if (hasExistingAcr && !hasExistingAcrConnection) { name: 'existing-acr-role-assignment' scope: resourceGroup(existingAcrResourceGroup) params: { @@ -323,7 +360,6 @@ module azureAiSearch '../search/azure_ai_search.bicep' = if (hasSearchConnection } } - // Outputs output AZURE_AI_PROJECT_ENDPOINT string = aiAccount::project.properties.endpoints['AI Foundry API'] output AZURE_OPENAI_ENDPOINT string = aiAccount.properties.endpoints['OpenAI Language Model Instance API'] @@ -334,14 +370,14 @@ output aiServicesAccountName string = aiAccount.name output aiServicesProjectName string = aiAccount::project.name output aiServicesPrincipalId string = aiAccount.identity.principalId output projectName string = aiAccount::project.name -output APPLICATIONINSIGHTS_CONNECTION_STRING string = applicationInsights.outputs.connectionString +output APPLICATIONINSIGHTS_CONNECTION_STRING string = shouldCreateAppInsights ? applicationInsights.outputs.connectionString : (hasExistingAppInsightsConnectionString ? existingApplicationInsightsConnectionString : '') // Grouped dependent resources outputs output dependentResources object = { registry: { name: hasAcrConnection ? acr!.outputs.containerRegistryName : '' - loginServer: hasAcrConnection ? acr!.outputs.containerRegistryLoginServer : (hasExistingAcr ? existingContainerRegistryEndpoint : '') - connectionName: hasAcrConnection ? acr!.outputs.containerRegistryConnectionName : (hasExistingAcr ? 'acr-connection' : '') + loginServer: hasAcrConnection ? acr!.outputs.containerRegistryLoginServer : ((hasExistingAcr || hasExistingAcrConnection) ? existingContainerRegistryEndpoint : '') + connectionName: hasAcrConnection ? acr!.outputs.containerRegistryConnectionName : (hasExistingAcrConnection ? existingAcrConnectionName : (hasExistingAcr ? 'acr-connection' : '')) } bing_grounding: { name: (hasBingConnection) ? bingGrounding!.outputs.bingGroundingName : '' @@ -396,5 +432,3 @@ type dependentResourcesType = { @description('The connection name for this resource') connectionName: string }[] - -