Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/deploymentguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ After setting these variables, run `azd up` normally. The deployment will attach
| `useExistingVNet` | Reuse an existing VNet | `false` |
| `existingVnetResourceId` | Existing VNet resource ID (when `useExistingVNet=true`) | `` |
| `existingLogAnalyticsWorkspaceResourceId` | Existing Log Analytics workspace to receive PostgreSQL diagnostics. May live in another subscription within the same tenant. | `` |
| `existingAiProjectResourceId` | Existing Microsoft Foundry **project** resource ID to reuse instead of creating a new Foundry account + project. When set, `deployAiFoundry` and `deployAfProject` are auto-disabled. Read from `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID`. | `` |
| `vmUserName` | Jump box VM admin username | `VM_ADMIN_USERNAME` env var or `testvmuser` |
| `vmAdminPassword` | Jump box VM admin password | `VM_ADMIN_PASSWORD` env var |

Expand Down Expand Up @@ -283,6 +284,26 @@ When set, the deployment will:

The workspace may live in a different resource group or subscription within the same tenant. The identity running `azd up` needs **`Microsoft.Insights/diagnosticSettings/write`** on the workspace itself (covered by the built-in **Log Analytics Contributor** role scoped to the workspace or its resource group — subscription-wide rights are not required). See the **Observability — Bring Your Own Log Analytics Workspace** section in the [Parameter Guide](./parameter_guide.md) for the full output reference (including App Insights values when that component is deployed) and notes on deployment-history exposure of those values.

**Microsoft Foundry Project (BYO AI Project):**

By default the deployment creates a new Foundry account and project. To attach to an existing Foundry project instead, set:

```powershell
azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<foundry-account>/projects/<project-name>"
```

When set, the wrapper auto-disables `deployAiFoundry` and `deployAfProject`, parses the account/project/RG/subscription from the resource ID, and publishes them to azd env (`aiFoundryName`, `aiFoundryProjectName`, `aiFoundryResourceGroup`, `aiFoundrySubscriptionId`) so post-provision RBAC and Search wiring target the existing project. The existing Foundry account may live in a different resource group or subscription within the same tenant — the identity running `azd up` needs RBAC owner/contributor rights on that account to assign roles. See the **AI Foundry — Bring Your Own Existing Project** section in the [Parameter Guide](./parameter_guide.md) for details.

**Existing Resource Group:**

azd natively supports deploying into an existing resource group. Set `AZURE_RESOURCE_GROUP` before `azd up`; the wrapper template uses `targetScope = 'resourceGroup'` and performs incremental deployments, so existing resources in the RG are left untouched:

```powershell
azd env set AZURE_RESOURCE_GROUP "<existing-rg-name>"
```

If unset, the deployment defaults to `rg-<AZURE_ENV_NAME>`.

</details>

### Step 4: Deploy
Expand Down
40 changes: 40 additions & 0 deletions docs/parameter_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,46 @@ The identity running the deployment needs permission to attach diagnostic settin

---

## AI Foundry — Bring Your Own Existing Project

By default the wrapper provisions a new Azure AI Foundry account and project. If you already have an AI Foundry project you want to reuse (for example, a shared project that is centrally governed, or one that lives in a different subscription), you can point the deployment at it.

### How it works

When `AZURE_EXISTING_AI_PROJECT_RESOURCE_ID` is set (and surfaced through the bicepparam as `existingAiProjectResourceId`):

1. The wrapper flips `deployAiFoundry`, `deployAfProject`, and `deployAAfAgentSvc` to `false`, so the AI Landing Zone submodule **skips creating a new AI Foundry account, project, and agent capability hosts**.
Comment thread
Saswato-Microsoft marked this conversation as resolved.
2. The preprovision script parses the supplied resource ID into its subscription / resource group / account / project segments and publishes them to `azd env` (`aiFoundryName`, `aiFoundryProjectName`, `aiFoundryResourceGroup`, `aiFoundrySubscriptionId`).
3. Downstream post-provision automation (OneLake indexing, AI Foundry-to-Search RBAC, AI Foundry connection setup) targets the existing project instead of running discovery in the deployment resource group.
4. The wrapper outputs `aiFoundryAccountName`, `aiFoundryProjectName`, `aiFoundryResourceGroup`, `aiFoundrySubscriptionId`, `existingAiProjectResourceIdOut`, and `useExistingAiProject` so external automation can pick them up.

> **Note:** Cross-subscription IDs are supported. The identity running `azd` must have at least `Reader` on the existing AI Foundry account, plus the role-assignment rights needed by the post-provision RBAC scripts (typically `Cognitive Services Contributor` on the project and `Search Service Contributor`/`Search Index Data Contributor` on the AI Search resource).

### Setting it via azd env

```powershell
azd env set AZURE_EXISTING_AI_PROJECT_RESOURCE_ID "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<aiFoundryAccount>/projects/<aiFoundryProject>"
```

Or set it directly in `infra/main.bicepparam` (it is already read from the env variable by default):

```bicep
param existingAiProjectResourceId = '/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<aiFoundryAccount>/projects/<aiFoundryProject>'
```

### Outputs

| Output | Description |
|--------|-------------|
| `useExistingAiProject` | `true` when BYO mode is active |
| `existingAiProjectResourceIdOut` | Echo of the supplied AI Foundry project resource ID |
| `aiFoundryAccountName` | Existing AI Foundry account name (parsed from the resource ID) |
| `aiFoundryProjectName` | Existing AI Foundry project name (parsed from the resource ID) |
| `aiFoundryResourceGroup` | Resource group of the existing AI Foundry account |
| `aiFoundrySubscriptionId` | Subscription ID hosting the existing AI Foundry account |

---

## Table of Contents
1. [Basic Parameters](#basic-parameters)
2. [Deployment Toggles](#deployment-toggles)
Expand Down
28 changes: 27 additions & 1 deletion infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ param aiFoundryStorageAccountResourceId string = ''
param aiFoundryCosmosDBAccountResourceId string = ''
param keyVaultResourceId string = ''

@description('Optional. Full ARM resource ID of an existing Azure AI Foundry project to reuse. When provided, the wrapper and AI Landing Zone submodule will skip creating a new AI Foundry account/project, and downstream automation (RBAC, OneLake indexing, AI Foundry connections) will target the existing project. Cross-subscription resource IDs are supported. Format: /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{account}/projects/{project}.')
param existingAiProjectResourceId string = ''

@description('Optional. Full ARM resource ID of an existing Log Analytics workspace to use for observability of the deployed Foundry application and wrapper-managed PostgreSQL. When provided, an Application Insights component is created in the deployment resource group and linked to this workspace, and diagnostic settings on the wrapper-managed PostgreSQL flexible server are routed to it. Leave empty to skip BYO behavior. Format: /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{name}.')
param existingLogAnalyticsWorkspaceResourceId string = ''

Expand Down Expand Up @@ -524,10 +527,33 @@ var effectiveAiSearchResourceId = !empty(aiSearchResourceId)

var effectiveStorageAccountResourceId = resourceId('Microsoft.Storage/storageAccounts', storageAccountName)

// ----------------------------------------------------------------------
// BYO existing AI Foundry Project parsing.
// When existingAiProjectResourceId is provided, parse it into its
// subscription / resource group / account / project segments so downstream
// automation can target the existing project instead of a wrapper-created
// one. Cross-subscription resource IDs are supported.
// ----------------------------------------------------------------------
var byoAiProjectEnabled = !empty(existingAiProjectResourceId)
var byoAiProjectIdSegments = byoAiProjectEnabled ? split(existingAiProjectResourceId, '/') : []
Comment thread
Saswato-Microsoft marked this conversation as resolved.
var byoAiProjectSubscriptionId = length(byoAiProjectIdSegments) >= 3 ? byoAiProjectIdSegments[2] : ''
var byoAiProjectResourceGroupName = length(byoAiProjectIdSegments) >= 5 ? byoAiProjectIdSegments[4] : ''
var byoAiFoundryAccountName = length(byoAiProjectIdSegments) >= 9 ? byoAiProjectIdSegments[8] : ''
var byoAiFoundryProjectName = length(byoAiProjectIdSegments) >= 11 ? byoAiProjectIdSegments[10] : ''
var effectiveAiFoundryAccountName = byoAiProjectEnabled ? byoAiFoundryAccountName : aiFoundryAccountName
var effectiveAiFoundryProjectName = byoAiProjectEnabled ? byoAiFoundryProjectName : aiFoundryProjectName
var effectiveAiFoundryResourceGroup = byoAiProjectEnabled ? byoAiProjectResourceGroupName : resourceGroup().name
var effectiveAiFoundrySubscriptionId = byoAiProjectEnabled ? byoAiProjectSubscriptionId : subscription().subscriptionId
Comment thread
Saswato-Microsoft marked this conversation as resolved.

output virtualNetworkResourceId string = effectiveVnetResourceId
output keyVaultResourceId string = effectiveKeyVaultResourceId
output storageAccountResourceId string = effectiveStorageAccountResourceId
output aiFoundryProjectName string = aiFoundryProjectName
output aiFoundryProjectName string = effectiveAiFoundryProjectName
output aiFoundryAccountName string = effectiveAiFoundryAccountName
output aiFoundryResourceGroup string = effectiveAiFoundryResourceGroup
output aiFoundrySubscriptionId string = effectiveAiFoundrySubscriptionId
output existingAiProjectResourceIdOut string = existingAiProjectResourceId
output useExistingAiProject bool = byoAiProjectEnabled
output aiSearchResourceId string = effectiveAiSearchResourceId
output aiSearchName string = searchServiceName
output aiSearchAdditionalAccessObjectIds array = aiSearchAdditionalAccessObjectIds
Expand Down
18 changes: 16 additions & 2 deletions infra/main.bicepparam
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ param keyVaultResourceId = ''
param useExistingVNet = false
param existingVnetResourceId = readEnvironmentVariable('EXISTING_VNET_RESOURCE_ID', '')

// BYO existing Azure AI Foundry Project (cross-subscription supported).
// When AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is set, the wrapper and the
// AI Landing Zone submodule will skip creating a new AI Foundry account and
// project; downstream automation (RBAC, OneLake indexing, AI Foundry
// connections) will target the existing project instead.
// Format: /subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{aiFoundryAccount}/projects/{aiFoundryProject}
var existingAiProjectResourceIdVar = readEnvironmentVariable('AZURE_EXISTING_AI_PROJECT_RESOURCE_ID', '')
param existingAiProjectResourceId = existingAiProjectResourceIdVar
var useExistingAiProjectVar = !empty(existingAiProjectResourceId)
Comment thread
Saswato-Microsoft marked this conversation as resolved.

// BYO Log Analytics Workspace for observability of the deployed Foundry
// application and wrapper-managed PostgreSQL resources.
// When provided, diagnostic settings on the wrapper-managed PostgreSQL
Expand Down Expand Up @@ -91,7 +101,9 @@ param postgreSqlStorageSizeGB = 32
// ========================================

param deployGroundingWithBing = false
param deployAiFoundry = true
// When AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is set, skip creating a new
// AI Foundry account/project (BYO mode).
param deployAiFoundry = !useExistingAiProjectVar
param deployAiFoundrySubnet = false
param deployAppConfig = true
param deployKeyVault = true
Expand All @@ -110,7 +122,9 @@ param deployNsgs = true
param sideBySideDeploy = readEnvironmentVariable('SIDE_BY_SIDE', 'true') == 'true'
param deploySoftware = false
param deployApim = false
param deployAfProject = true
// When AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is set, skip creating a new
// AI Foundry project (BYO mode).
param deployAfProject = !useExistingAiProjectVar
param deployAAfAgentSvc = false
param enableAgenticRetrieval = readEnvironmentVariable('ENABLE_AGENTIC_RETRIEVAL', 'false') == 'true'

Expand Down
75 changes: 59 additions & 16 deletions scripts/preprovision-integrated.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -449,27 +449,64 @@ if ([string]::IsNullOrWhiteSpace($aiSearchName)) {
try { $aiSearchName = (az search service list --resource-group $ResourceGroup --query "[0].name" -o tsv 2>$null).Trim() } catch { }
}

# BYO existing AI Foundry project: when AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is
# set (and surfaced through the bicepparam as existingAiProjectResourceId), parse
# its segments and use them for the downstream azd env values so postprovision
# automation targets the existing account/project instead of attempting RG
# discovery in this deployment's resource group.
$existingAiProjectResourceId = $null
try { $existingAiProjectResourceId = [string]$parentJson.parameters.existingAiProjectResourceId.value } catch { }
if ([string]::IsNullOrWhiteSpace($existingAiProjectResourceId)) {
$existingAiProjectResourceId = $env:AZURE_EXISTING_AI_PROJECT_RESOURCE_ID
}

$aiFoundryName = $null
try { $aiFoundryName = [string]$parentJson.parameters.aiFoundryAccountName.value } catch { }
$aiFoundryProjectName = $null
$aiFoundryResourceGroupForEnv = $ResourceGroup
$aiFoundrySubscriptionForEnv = $SubscriptionId

if (-not [string]::IsNullOrWhiteSpace($existingAiProjectResourceId)) {
Write-Host " [i] BYO AI Foundry project detected: $existingAiProjectResourceId" -ForegroundColor Cyan
$segments = $existingAiProjectResourceId.Trim('/').Split('/')
# Expected: subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{account}/projects/{project}
if ($segments.Length -ge 10 -and $segments[0] -ieq 'subscriptions' -and $segments[2] -ieq 'resourceGroups' -and $segments[6] -ieq 'accounts' -and $segments[8] -ieq 'projects') {
$aiFoundrySubscriptionForEnv = $segments[1]
$aiFoundryResourceGroupForEnv = $segments[3]
$aiFoundryName = $segments[7]
$aiFoundryProjectName = $segments[9]
Write-Host " Account: $aiFoundryName" -ForegroundColor Green
Write-Host " Project: $aiFoundryProjectName" -ForegroundColor Green
Write-Host " ResourceGrp: $aiFoundryResourceGroupForEnv" -ForegroundColor Green
Write-Host " Subscription: $aiFoundrySubscriptionForEnv" -ForegroundColor Green
} else {
Write-Host " [!] AZURE_EXISTING_AI_PROJECT_RESOURCE_ID is set but the resource ID format is not recognized. Falling back to discovery." -ForegroundColor Yellow
$existingAiProjectResourceId = $null
Comment thread
Saswato-Microsoft marked this conversation as resolved.
}
}

if ([string]::IsNullOrWhiteSpace($aiFoundryName)) {
try {
$aiFoundryName = (az cognitiveservices account list --resource-group $ResourceGroup --query "[?kind=='AIServices']|[0].name" -o tsv 2>$null).Trim()
} catch { }
try { $aiFoundryName = [string]$parentJson.parameters.aiFoundryAccountName.value } catch { }
if ([string]::IsNullOrWhiteSpace($aiFoundryName)) {
try {
$aiFoundryName = (az cognitiveservices account list --resource-group $ResourceGroup --query "[?kind=='AIServices']|[0].name" -o tsv 2>$null).Trim()
} catch { }
}
}

$aiFoundryProjectName = $null
try { $aiFoundryProjectName = [string]$parentJson.parameters.aiFoundryProjectName.value } catch { }
if ([string]::IsNullOrWhiteSpace($aiFoundryProjectName) -and -not [string]::IsNullOrWhiteSpace($aiFoundryName)) {
try {
$projectCandidatesRaw = az resource list --resource-group $ResourceGroup --resource-type "Microsoft.CognitiveServices/accounts/projects" --query "[?contains(id, '/accounts/$aiFoundryName/')].name" -o tsv 2>$null
if ($projectCandidatesRaw) {
[string[]]$projectCandidates = ($projectCandidatesRaw -split "\r?\n") | Where-Object { $_ -and $_.Trim() } | ForEach-Object { $_.Trim() }
if ($projectCandidates.Length -ge 1) {
$aiFoundryProjectName = $projectCandidates[0]
if ([string]::IsNullOrWhiteSpace($aiFoundryProjectName)) {
try { $aiFoundryProjectName = [string]$parentJson.parameters.aiFoundryProjectName.value } catch { }
if ([string]::IsNullOrWhiteSpace($aiFoundryProjectName) -and -not [string]::IsNullOrWhiteSpace($aiFoundryName)) {
try {
$projectCandidatesRaw = az resource list --resource-group $aiFoundryResourceGroupForEnv --subscription $aiFoundrySubscriptionForEnv --resource-type "Microsoft.CognitiveServices/accounts/projects" --query "[?contains(id, '/accounts/$aiFoundryName/')].name" -o tsv 2>$null
if ($projectCandidatesRaw) {
[string[]]$projectCandidates = ($projectCandidatesRaw -split "\r?\n") | Where-Object { $_ -and $_.Trim() } | ForEach-Object { $_.Trim() }
if ($projectCandidates.Length -ge 1) {
$aiFoundryProjectName = $projectCandidates[0]
}
}
} catch {
# Ignore discovery failures and continue.
}
} catch {
# Ignore discovery failures and continue.
}
}

Expand All @@ -484,8 +521,14 @@ Set-AzdEnvValue -Name 'aiSearchResourceId' -Value $aiSearchResourceId
Set-AzdEnvValue -Name 'aiSearchResourceGroup' -Value $ResourceGroup
Set-AzdEnvValue -Name 'aiSearchSubscriptionId' -Value $SubscriptionId
Set-AzdEnvValue -Name 'aiFoundryName' -Value $aiFoundryName
Set-AzdEnvValue -Name 'aiFoundryResourceGroup' -Value $ResourceGroup
Set-AzdEnvValue -Name 'aiFoundryResourceGroup' -Value $aiFoundryResourceGroupForEnv
Set-AzdEnvValue -Name 'aiFoundrySubscriptionId' -Value $aiFoundrySubscriptionForEnv
Set-AzdEnvValue -Name 'aiFoundryProjectName' -Value $aiFoundryProjectName
if (-not [string]::IsNullOrWhiteSpace($existingAiProjectResourceId)) {
Set-AzdEnvValue -Name 'AZURE_EXISTING_AI_PROJECT_RESOURCE_ID' -Value $existingAiProjectResourceId
Set-AzdEnvValue -Name 'existingAiProjectResourceId' -Value $existingAiProjectResourceId
Set-AzdEnvValue -Name 'useExistingAiProject' -Value 'true'
}


Write-Host ""
Expand Down
Loading