Skip to content

Commit 959d0e8

Browse files
authored
Adding Projects to Vnet Injected Foundry (#324)
* Add Bicep file for private network agent project setup creating additional projects for account * Add parameters for second project setup Added parameters for a new project setup including details for existing AI services and shared resources. * Add script to get existing Azure resources This script retrieves the names of existing Azure resources such as AI Services, Storage Account, AI Search Service, and Cosmos DB Account from a specified Resource Group to be used on add-project.bicepparam * Add Bicep file for AI project identity setup This Bicep file defines parameters and resources for setting up a project with unique connection names for CosmosDB, Azure Storage, and Azure Cognitive Search, utilizing system-assigned identity. * Add role assignments for blob storage container adds roles for projects in add-project.bicep * Enhance README with multi-project deployment guide Added a guide for adding multiple projects to AI Foundry deployment, including prerequisites and steps
1 parent 17c6169 commit 959d0e8

File tree

6 files changed

+594
-1
lines changed

6 files changed

+594
-1
lines changed

samples/microsoft/infrastructure-setup/15-private-network-standard-agent-setup/README.md

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ Click the deploy to Azure button above to open the Azure portal and deploy the t
165165

166166
> **Note:** To access your Foundry resource securely, use either a VM, VPN, or ExpressRoute.
167167
168-
---
168+
---
169169

170170
## Network Secured Agent Project Architecture Deep Dive
171171

@@ -349,6 +349,167 @@ modules-network-secured/
349349
4. Review network security groups
350350

351351
---
352+
---
353+
# (Optional) Adding Multiple Projects to AI Foundry Deployment
354+
355+
This guide explains how to add additional projects to your existing AI Foundry deployment with network security and capability hosts.
356+
357+
## Overview
358+
359+
After deploying your initial AI Foundry setup using `main.bicep`, you can add additional projects using the modular approach provided in this repository. Each new project will:
360+
361+
-**Reuse existing shared infrastructure** (AI Services account, Storage, Cosmos DB, AI Search, VNet)
362+
-**Create independent projects** with unique identities and connections
363+
-**Set up proper role assignments** and capability hosts for each project
364+
-**Maintain network security** configurations from your original deployment
365+
-**Deploy independently** without affecting existing projects
366+
367+
## Files Added
368+
369+
### Core Deployment Files
370+
371+
| File | Purpose |
372+
|------|---------|
373+
| `add-project.bicep` | Main Bicep template for adding new projects |
374+
| `add-project.bicepparam` | Parameters file template for new projects |
375+
| `modules-network-secured/ai-project-identity-unique.bicep` | Modified project module with unique connection names |
376+
| `modules-network-secured/blob-storage-container-role-assignments-unique.bicep` | Modified storage role assignment module |
377+
378+
### Helper Files
379+
380+
| File | Purpose |
381+
|------|---------|
382+
| `get-existing-resources.ps1` | PowerShell script to discover existing resource names |
383+
384+
## Prerequisites
385+
386+
1.**Existing AI Foundry deployment** completed using `main.bicep`
387+
2.**Azure CLI** installed and logged in
388+
3.**Proper permissions** on the resource group and existing resources
389+
4.**Resource names** from your existing deployment
390+
391+
## Step-by-Step Guide
392+
393+
### Step 1: Discover Existing Resource Names
394+
395+
Run the PowerShell script to automatically discover your existing resource names:
396+
397+
```powershell
398+
# Navigate to your repository folder
399+
cd "path\to\your\AgentRepro\folder"
400+
401+
# Run the discovery script
402+
.\get-existing-resources.ps1 -ResourceGroupName "your-resource-group-name"
403+
404+
# Optional: Include subscription ID if needed
405+
.\get-existing-resources.ps1 -ResourceGroupName "your-resource-group-name" -SubscriptionId "your-subscription-id"
406+
```
407+
408+
**Example output:**
409+
```
410+
=== Summary for add-project.bicepparam ===
411+
param existingAccountName = 'aiservicesytlz'
412+
param existingAiSearchName = 'aiservicesytlzsearch'
413+
param existingStorageName = 'aiservicesytlzstorage'
414+
param existingCosmosDBName = 'aiservicesytlzcosmosdb'
415+
param accountResourceGroupName = 'agenticvnet'
416+
param aiSearchResourceGroupName = 'agenticvnet'
417+
param storageResourceGroupName = 'agenticvnet'
418+
param cosmosDBResourceGroupName = 'agenticvnet'
419+
```
420+
421+
### Step 2: Configure Parameters File
422+
423+
Copy the output from Step 1 and update your `add-project.bicepparam` file:
424+
425+
### Step 3: Deploy the New Project
426+
427+
Deploy using Azure CLI:
428+
429+
```powershell
430+
az deployment group create `
431+
--resource-group "your-resource-group" `
432+
--template-file "add-project.bicep" `
433+
--parameters "add-project.bicepparam"
434+
```
435+
436+
## Adding Multiple Projects
437+
438+
To add additional projects, repeat the process with different parameter values:
439+
440+
### For a Third Project:
441+
442+
1. **Update project-specific parameters:**
443+
```bicep
444+
param projectName = 'thirdproject' // Must be unique
445+
param displayName = 'Third Project'
446+
param projectCapHost = 'caphostthird' // Must be unique
447+
```
448+
449+
3. **Deploy using the new parameters file:**
450+
```powershell
451+
az deployment group create `
452+
--resource-group "your-resource-group" `
453+
--template-file "add-project.bicep" `
454+
--parameters "add-project.bicepparam"
455+
```
456+
457+
## What Gets Created
458+
459+
Each new project deployment creates:
460+
461+
| Resource | Description |
462+
|----------|-------------|
463+
| **AI Foundry Project** | New project under your existing AI Services account |
464+
| **Managed Identity** | Project-specific system-assigned identity |
465+
| **Unique Connections** | Project-specific connections to shared resources |
466+
| **Capability Host** | Configured for Agents with proper connections |
467+
| **RBAC Assignments** | Proper permissions on shared resources |
468+
469+
### Role Assignments Created:
470+
471+
-**Storage Blob Data Contributor** on Storage Account
472+
-**Storage Blob Data Owner** on project-specific containers
473+
-**Cosmos DB Operator** on Cosmos DB Account
474+
-**Cosmos Built-In Data Contributor** on project-specific containers
475+
-**Search Index Data Contributor** on AI Search Service
476+
-**Search Service Contributor** on AI Search Service
477+
478+
## Configuration Reference
479+
480+
### Required Parameters (Must Customize for Each Project)
481+
482+
| Parameter | Description | Example |
483+
|-----------|-------------|---------|
484+
| `projectName` | Unique name for the project | `'secondproject'` |
485+
| `displayName` | Display name in Azure portal | `'Second Project'` |
486+
| `projectCapHost` | Unique capability host name | `'caphostsecond'` |
487+
| `projectDescription` | Description of the project | `'My second AI project'` |
488+
489+
### Existing Resource Parameters (From Script)
490+
491+
| Parameter | Description | Source |
492+
|-----------|-------------|---------|
493+
| `existingAccountName` | AI Services account name | Output from `get-existing-resources.ps1` |
494+
| `existingAiSearchName` | AI Search service name | Output from `get-existing-resources.ps1` |
495+
| `existingStorageName` | Storage account name | Output from `get-existing-resources.ps1` |
496+
| `existingCosmosDBName` | Cosmos DB account name | Output from `get-existing-resources.ps1` |
497+
| `*ResourceGroupName` | Resource group names | Usually same as deployment RG |
498+
| `*SubscriptionId` | Subscription IDs | Usually same subscription |
499+
500+
501+
## Security Considerations
502+
503+
-**Least Privilege**: Each project gets only the permissions it needs
504+
-**Isolated Containers**: Projects get separate storage containers
505+
-**Network Security**: Inherits network security from original deployment
506+
-**Unique Identities**: Each project has its own managed identity
507+
508+
## Limitations
509+
510+
- 📝 All projects share the same model deployments
511+
- 📝 Projects must be in the same region as the original deployment
512+
- 📝 Network configuration is inherited from original deployment
352513

353514
## References
354515

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
@description('Location for the project resources.')
2+
param location string = 'westus'
3+
4+
@description('Name of the existing AI Services account')
5+
param existingAccountName string
6+
7+
@description('Resource group containing the AI Services account')
8+
param accountResourceGroupName string = resourceGroup().name
9+
10+
@description('Subscription ID containing the AI Services account')
11+
param accountSubscriptionId string = subscription().subscriptionId
12+
13+
@description('Name for the new project')
14+
param projectName string
15+
16+
@description('Description for the new project')
17+
param projectDescription string = 'Additional AI Foundry project with network secured deployed Agent'
18+
19+
@description('Display name for the new project')
20+
param displayName string
21+
22+
@description('Name for the project capability host')
23+
param projectCapHost string = 'caphostproj'
24+
25+
// Existing shared resources (from your original deployment)
26+
@description('Name of the existing AI Search service')
27+
param existingAiSearchName string
28+
29+
@description('Resource group containing the AI Search service')
30+
param aiSearchResourceGroupName string
31+
32+
@description('Subscription ID containing the AI Search service')
33+
param aiSearchSubscriptionId string
34+
35+
@description('Name of the existing Storage Account')
36+
param existingStorageName string
37+
38+
@description('Resource group containing the Storage Account')
39+
param storageResourceGroupName string
40+
41+
@description('Subscription ID containing the Storage Account')
42+
param storageSubscriptionId string
43+
44+
@description('Name of the existing Cosmos DB account')
45+
param existingCosmosDBName string
46+
47+
@description('Resource group containing the Cosmos DB account')
48+
param cosmosDBResourceGroupName string
49+
50+
@description('Subscription ID containing the Cosmos DB account')
51+
param cosmosDBSubscriptionId string
52+
53+
// Create a short, unique suffix for this project
54+
param deploymentTimestamp string = utcNow('yyyyMMddHHmmss')
55+
var uniqueSuffix = substring(uniqueString('${resourceGroup().id}-${deploymentTimestamp}'), 0, 4)
56+
var finalProjectName = toLower('${projectName}${uniqueSuffix}')
57+
58+
// Reference existing AI Services account
59+
resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = {
60+
name: existingAccountName
61+
scope: resourceGroup(accountSubscriptionId, accountResourceGroupName)
62+
}
63+
64+
// Reference existing shared resources
65+
resource aiSearch 'Microsoft.Search/searchServices@2023-11-01' existing = {
66+
name: existingAiSearchName
67+
scope: resourceGroup(aiSearchSubscriptionId, aiSearchResourceGroupName)
68+
}
69+
70+
resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' existing = {
71+
name: existingStorageName
72+
scope: resourceGroup(storageSubscriptionId, storageResourceGroupName)
73+
}
74+
75+
resource cosmosDB 'Microsoft.DocumentDB/databaseAccounts@2024-11-15' existing = {
76+
name: existingCosmosDBName
77+
scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName)
78+
}
79+
80+
// Create the new project using the unique connection module
81+
module aiProject 'modules-network-secured/ai-project-identity-unique.bicep' = {
82+
name: 'ai-${finalProjectName}-${uniqueSuffix}-deployment'
83+
params: {
84+
projectName: finalProjectName
85+
projectDescription: projectDescription
86+
displayName: displayName
87+
location: location
88+
89+
aiSearchName: existingAiSearchName
90+
aiSearchServiceResourceGroupName: aiSearchResourceGroupName
91+
aiSearchServiceSubscriptionId: aiSearchSubscriptionId
92+
93+
cosmosDBName: existingCosmosDBName
94+
cosmosDBSubscriptionId: cosmosDBSubscriptionId
95+
cosmosDBResourceGroupName: cosmosDBResourceGroupName
96+
97+
azureStorageName: existingStorageName
98+
azureStorageSubscriptionId: storageSubscriptionId
99+
azureStorageResourceGroupName: storageResourceGroupName
100+
101+
accountName: existingAccountName
102+
103+
// Pass unique suffix for connection names
104+
uniqueConnectionSuffix: '-${finalProjectName}'
105+
}
106+
}
107+
108+
module formatProjectWorkspaceId 'modules-network-secured/format-project-workspace-id.bicep' = {
109+
name: 'format-project-workspace-id-${uniqueSuffix}-deployment'
110+
params: {
111+
projectWorkspaceId: aiProject.outputs.projectWorkspaceId
112+
}
113+
}
114+
115+
// Assign storage account role
116+
module storageAccountRoleAssignment 'modules-network-secured/azure-storage-account-role-assignment.bicep' = {
117+
name: 'storage-${existingStorageName}-${uniqueSuffix}-deployment'
118+
scope: resourceGroup(storageSubscriptionId, storageResourceGroupName)
119+
params: {
120+
azureStorageName: existingStorageName
121+
projectPrincipalId: aiProject.outputs.projectPrincipalId
122+
}
123+
}
124+
125+
// Assign Cosmos DB account role
126+
module cosmosAccountRoleAssignments 'modules-network-secured/cosmosdb-account-role-assignment.bicep' = {
127+
name: 'cosmos-account-ra-${finalProjectName}-${uniqueSuffix}-deployment'
128+
scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName)
129+
params: {
130+
cosmosDBName: existingCosmosDBName
131+
projectPrincipalId: aiProject.outputs.projectPrincipalId
132+
}
133+
}
134+
135+
// Assign AI Search role
136+
module aiSearchRoleAssignments 'modules-network-secured/ai-search-role-assignments.bicep' = {
137+
name: 'ai-search-ra-${finalProjectName}-${uniqueSuffix}-deployment'
138+
scope: resourceGroup(aiSearchSubscriptionId, aiSearchResourceGroupName)
139+
params: {
140+
aiSearchName: existingAiSearchName
141+
projectPrincipalId: aiProject.outputs.projectPrincipalId
142+
}
143+
}
144+
145+
// Create capability host for the new project
146+
module addProjectCapabilityHost 'modules-network-secured/add-project-capability-host.bicep' = {
147+
name: 'capabilityHost-configuration-${uniqueSuffix}-deployment'
148+
params: {
149+
accountName: existingAccountName
150+
projectName: aiProject.outputs.projectName
151+
cosmosDBConnection: aiProject.outputs.cosmosDBConnection
152+
azureStorageConnection: aiProject.outputs.azureStorageConnection
153+
aiSearchConnection: aiProject.outputs.aiSearchConnection
154+
projectCapHost: projectCapHost
155+
}
156+
dependsOn: [
157+
cosmosAccountRoleAssignments
158+
storageAccountRoleAssignment
159+
aiSearchRoleAssignments
160+
]
161+
}
162+
163+
// Assign storage container roles after capability host creation
164+
module storageContainersRoleAssignment 'modules-network-secured/blob-storage-container-role-assignments-unique.bicep' = {
165+
name: 'storage-containers-${uniqueSuffix}-deployment'
166+
scope: resourceGroup(storageSubscriptionId, storageResourceGroupName)
167+
params: {
168+
aiProjectPrincipalId: aiProject.outputs.projectPrincipalId
169+
storageName: existingStorageName
170+
workspaceId: formatProjectWorkspaceId.outputs.projectWorkspaceIdGuid
171+
uniqueSuffix: uniqueSuffix // Add this line
172+
}
173+
dependsOn: [
174+
addProjectCapabilityHost
175+
]
176+
}
177+
178+
// Assign Cosmos container roles after capability host creation
179+
module cosmosContainerRoleAssignments 'modules-network-secured/cosmos-container-role-assignments.bicep' = {
180+
name: 'cosmos-ra-${uniqueSuffix}-deployment'
181+
scope: resourceGroup(cosmosDBSubscriptionId, cosmosDBResourceGroupName)
182+
params: {
183+
cosmosAccountName: existingCosmosDBName
184+
projectWorkspaceId: formatProjectWorkspaceId.outputs.projectWorkspaceIdGuid
185+
projectPrincipalId: aiProject.outputs.projectPrincipalId
186+
}
187+
dependsOn: [
188+
addProjectCapabilityHost
189+
storageContainersRoleAssignment
190+
]
191+
}
192+
193+
// Outputs
194+
output projectName string = aiProject.outputs.projectName
195+
output projectPrincipalId string = aiProject.outputs.projectPrincipalId
196+
output projectWorkspaceId string = aiProject.outputs.projectWorkspaceId
197+
output capabilityHostName string = addProjectCapabilityHost.outputs.projectCapHost

0 commit comments

Comments
 (0)