Skip to content

Commit ceacdf8

Browse files
authored
Merge pull request #2 from MicrosoftCloudEssentials-LearningHub/automated-ai-conn
AI resources automated connection
2 parents 03d8368 + e2bb079 commit ceacdf8

4 files changed

Lines changed: 376 additions & 2 deletions

File tree

terraform-infrastructure/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ graph TD;
119119

120120
<!-- START BADGE -->
121121
<div align="center">
122-
<img src="https://img.shields.io/badge/Total%20views-32-limegreen" alt="Total views">
123-
<p>Refresh Date: 2025-11-21</p>
122+
<img src="https://img.shields.io/badge/Total%20views-1372-limegreen" alt="Total views">
123+
<p>Refresh Date: 2025-11-22</p>
124124
</div>
125125
<!-- END BADGE -->

terraform-infrastructure/main.tf

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,322 @@ resource "azurerm_role_assignment" "search_project_contributor" {
278278
principal_id = azurerm_search_service.search.identity[0].principal_id
279279
principal_type = "ServicePrincipal"
280280
}
281+
282+
# Storage account permissions for Azure AI Foundry project
283+
resource "azurerm_role_assignment" "storage_blob_data_contributor_user" {
284+
scope = azapi_resource.storage.id
285+
role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe"
286+
principal_id = local.principal_id
287+
principal_type = "User"
288+
}
289+
290+
resource "azurerm_role_assignment" "storage_blob_data_contributor_project" {
291+
scope = azapi_resource.storage.id
292+
role_definition_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe"
293+
principal_id = azapi_resource.ai_project.identity[0].principal_id
294+
principal_type = "ServicePrincipal"
295+
}
296+
297+
# Azure AI model deployments automation
298+
resource "null_resource" "ai_model_deployments" {
299+
count = var.enable_ai_automation ? 1 : 0
300+
301+
depends_on = [
302+
azapi_resource.ai_project,
303+
azapi_resource.ai_foundry,
304+
azurerm_role_assignment.storage_blob_data_contributor_user
305+
]
306+
307+
provisioner "local-exec" {
308+
command = <<-EOT
309+
# Create AI model deployments
310+
Write-Host "Creating Azure AI model deployments..."
311+
312+
# Wait for AI Foundry to be fully ready
313+
Write-Host "Waiting for AI Foundry to be ready..."
314+
Start-Sleep -Seconds 30
315+
316+
try {
317+
# Create gpt-4o-mini deployment
318+
Write-Host "Creating gpt-4o-mini deployment..."
319+
az cognitiveservices account deployment create `
320+
--resource-group "${azurerm_resource_group.rg.name}" `
321+
--name "${local.ai_foundry_name}" `
322+
--deployment-name "gpt-4o-mini" `
323+
--model-name "gpt-4o-mini" `
324+
--model-version "2024-07-18" `
325+
--model-format "OpenAI" `
326+
--sku-capacity 10 `
327+
--sku-name "GlobalStandard"
328+
329+
if ($LASTEXITCODE -eq 0) {
330+
Write-Host "gpt-4o-mini deployment created successfully"
331+
} else {
332+
Write-Host "gpt-4o-mini deployment may already exist or failed to create"
333+
} # Create text-embedding-3-small deployment
334+
Write-Host "Creating text-embedding-3-small deployment..."
335+
az cognitiveservices account deployment create `
336+
--resource-group "${azurerm_resource_group.rg.name}" `
337+
--name "${local.ai_foundry_name}" `
338+
--deployment-name "text-embedding-3-small" `
339+
--model-name "text-embedding-3-small" `
340+
--model-version "1" `
341+
--model-format "OpenAI" `
342+
--sku-capacity 10 `
343+
--sku-name "GlobalStandard"
344+
345+
if ($LASTEXITCODE -eq 0) {
346+
Write-Host "text-embedding-3-small deployment created successfully"
347+
} else {
348+
Write-Host "text-embedding-3-small deployment may already exist or failed to create"
349+
} # Create phi-4 deployment
350+
Write-Host "Creating phi-4 deployment..."
351+
try {
352+
az cognitiveservices account deployment create `
353+
--resource-group "${azurerm_resource_group.rg.name}" `
354+
--name "${local.ai_foundry_name}" `
355+
--deployment-name "phi-4" `
356+
--model-name "phi-4" `
357+
--model-version "1" `
358+
--model-format "OpenAI" `
359+
--sku-capacity 5 `
360+
--sku-name "GlobalStandard"
361+
362+
if ($LASTEXITCODE -eq 0) {
363+
Write-Host "phi-4 deployment created successfully"
364+
$phi4Available = $true
365+
} else {
366+
Write-Host "phi-4 model not available in this region/tier, skipping"
367+
$phi4Available = $false
368+
}
369+
} catch {
370+
Write-Host "phi-4 model not supported, skipping"
371+
$phi4Available = $false
372+
}
373+
374+
# List all deployments to verify
375+
Write-Host "`nCurrent model deployments:"
376+
az cognitiveservices account deployment list `
377+
--resource-group "${azurerm_resource_group.rg.name}" `
378+
--name "${local.ai_foundry_name}" `
379+
--query "[].{Name:name,Model:properties.model.name,Version:properties.model.version,Capacity:properties.currentCapacity}" `
380+
--output table
381+
382+
Write-Host "`nModel deployment process completed successfully."
383+
}
384+
catch {
385+
Write-Host "Error during model deployment: $_"
386+
Write-Host "This may be expected if deployments already exist."
387+
}
388+
EOT
389+
interpreter = ["PowerShell", "-Command"]
390+
}
391+
392+
triggers = {
393+
ai_foundry_id = azapi_resource.ai_foundry.id
394+
ai_project_id = azapi_resource.ai_project.id
395+
}
396+
}
397+
398+
# Connect resources to Azure AI Foundry project
399+
resource "null_resource" "ai_project_connections" {
400+
count = var.enable_ai_automation ? 1 : 0
401+
402+
depends_on = [
403+
null_resource.ai_model_deployments,
404+
azurerm_application_insights.appinsights,
405+
azapi_resource.storage
406+
]
407+
408+
provisioner "local-exec" {
409+
command = <<-EOT
410+
Write-Host "Verifying Azure AI Foundry project configuration..."
411+
412+
# Check if Azure ML extension is installed
413+
$mlExtension = az extension list --query "[?name=='ml'].name" --output tsv
414+
if (-not $mlExtension) {
415+
Write-Host "Installing Azure ML extension..."
416+
az extension add --name ml
417+
}
418+
419+
# Set the AI project as the default workspace for future ML operations
420+
az config set defaults.workspace="${local.ai_project_name}"
421+
az config set defaults.group="${azurerm_resource_group.rg.name}"
422+
423+
Write-Host "Azure AI project configuration completed successfully."
424+
Write-Host "Project Name: ${local.ai_project_name}"
425+
Write-Host "AI Foundry: ${local.ai_foundry_name}"
426+
Write-Host "Resource Group: ${azurerm_resource_group.rg.name}"
427+
EOT
428+
interpreter = ["PowerShell", "-Command"]
429+
}
430+
431+
triggers = {
432+
storage_id = azapi_resource.storage.id
433+
app_insights_id = azurerm_application_insights.appinsights.id
434+
ai_project_id = azapi_resource.ai_project.id
435+
}
436+
}
437+
438+
# Create .env file with all necessary configuration
439+
resource "null_resource" "create_env_file" {
440+
count = var.enable_ai_automation ? 1 : 0
441+
442+
depends_on = [
443+
null_resource.ai_project_connections,
444+
azurerm_cosmosdb_account.cosmos,
445+
azurerm_search_service.search
446+
]
447+
448+
provisioner "local-exec" {
449+
command = <<-EOT
450+
Write-Host "Creating .env file with Azure resource configuration..."
451+
452+
# Create src directory if it doesn't exist
453+
if (!(Test-Path "../src")) {
454+
New-Item -ItemType Directory -Path "../src" -Force
455+
}
456+
457+
# Get Azure AI Foundry endpoint
458+
$aiFoundryEndpoint = az cognitiveservices account show `
459+
--resource-group "${azurerm_resource_group.rg.name}" `
460+
--name "${local.ai_foundry_name}" `
461+
--query "properties.endpoint" `
462+
--output tsv
463+
464+
# Get Azure AI Foundry access key
465+
$aiFoundryKey = az cognitiveservices account keys list `
466+
--resource-group "${azurerm_resource_group.rg.name}" `
467+
--name "${local.ai_foundry_name}" `
468+
--query "key1" `
469+
--output tsv
470+
471+
# Get Cosmos DB primary key
472+
$cosmosKey = az cosmosdb keys list `
473+
--resource-group "${azurerm_resource_group.rg.name}" `
474+
--name "${local.cosmos_account_name}" `
475+
--query "primaryMasterKey" `
476+
--output tsv
477+
478+
# Get Azure Search admin key
479+
$searchKey = az search admin-key show `
480+
--resource-group "${azurerm_resource_group.rg.name}" `
481+
--service-name "${local.search_service_name}" `
482+
--query "primaryKey" `
483+
--output tsv
484+
485+
# Get storage account connection string
486+
$storageConnectionString = az storage account show-connection-string `
487+
--resource-group "${azurerm_resource_group.rg.name}" `
488+
--name "${local.storage_account}" `
489+
--query "connectionString" `
490+
--output tsv
491+
492+
# Create .env file content
493+
if ($phi4Available) {
494+
$envContent = @"
495+
# Azure AI Foundry Configuration
496+
AZURE_AI_FOUNDRY_ENDPOINT=$aiFoundryEndpoint
497+
AZURE_AI_FOUNDRY_API_KEY=$aiFoundryKey
498+
AZURE_AI_PROJECT_NAME=${local.ai_project_name}
499+
500+
# Azure OpenAI Model Deployments
501+
AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o-mini
502+
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
503+
AZURE_OPENAI_PHI_DEPLOYMENT=phi-4
504+
AZURE_OPENAI_ENDPOINT=$aiFoundryEndpoint
505+
AZURE_OPENAI_API_KEY=$aiFoundryKey
506+
AZURE_OPENAI_API_VERSION=2024-02-01
507+
508+
# Azure Cosmos DB Configuration
509+
COSMOS_DB_ENDPOINT=${azurerm_cosmosdb_account.cosmos.endpoint}
510+
COSMOS_DB_KEY=$cosmosKey
511+
COSMOS_DB_NAME=${local.cosmos_db_name}
512+
COSMOS_DB_CONTAINER_NAME=products
513+
514+
# Azure AI Search Configuration
515+
SEARCH_SERVICE_ENDPOINT=https://${local.search_service_name}.search.windows.net
516+
SEARCH_SERVICE_KEY=$searchKey
517+
SEARCH_INDEX_NAME=products-index
518+
519+
# Azure Storage Configuration
520+
STORAGE_ACCOUNT_NAME=${local.storage_account}
521+
STORAGE_CONNECTION_STRING=$storageConnectionString
522+
523+
# Azure Application Insights
524+
APPLICATION_INSIGHTS_CONNECTION_STRING=${azurerm_application_insights.appinsights.connection_string}
525+
526+
# Azure Resource Information
527+
AZURE_SUBSCRIPTION_ID=${data.azurerm_client_config.current.subscription_id}
528+
AZURE_RESOURCE_GROUP=${azurerm_resource_group.rg.name}
529+
AZURE_LOCATION=${var.location}
530+
"@
531+
} else {
532+
$envContent = @"
533+
# Azure AI Foundry Configuration
534+
AZURE_AI_FOUNDRY_ENDPOINT=$aiFoundryEndpoint
535+
AZURE_AI_FOUNDRY_API_KEY=$aiFoundryKey
536+
AZURE_AI_PROJECT_NAME=${local.ai_project_name}
537+
538+
# Azure OpenAI Model Deployments
539+
AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o-mini
540+
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
541+
AZURE_OPENAI_ENDPOINT=$aiFoundryEndpoint
542+
AZURE_OPENAI_API_KEY=$aiFoundryKey
543+
AZURE_OPENAI_API_VERSION=2024-02-01
544+
545+
# Azure Cosmos DB Configuration
546+
COSMOS_DB_ENDPOINT=${azurerm_cosmosdb_account.cosmos.endpoint}
547+
COSMOS_DB_KEY=$cosmosKey
548+
COSMOS_DB_NAME=${local.cosmos_db_name}
549+
COSMOS_DB_CONTAINER_NAME=products
550+
551+
# Azure AI Search Configuration
552+
SEARCH_SERVICE_ENDPOINT=https://${local.search_service_name}.search.windows.net
553+
SEARCH_SERVICE_KEY=$searchKey
554+
SEARCH_INDEX_NAME=products-index
555+
556+
# Azure Storage Configuration
557+
STORAGE_ACCOUNT_NAME=${local.storage_account}
558+
STORAGE_CONNECTION_STRING=$storageConnectionString
559+
560+
# Azure Application Insights
561+
APPLICATION_INSIGHTS_CONNECTION_STRING=${azurerm_application_insights.appinsights.connection_string}
562+
563+
# Azure Resource Information
564+
AZURE_SUBSCRIPTION_ID=${data.azurerm_client_config.current.subscription_id}
565+
AZURE_RESOURCE_GROUP=${azurerm_resource_group.rg.name}
566+
AZURE_LOCATION=${var.location}
567+
"@
568+
}
569+
570+
# Write .env file
571+
$envContent | Out-File -FilePath "../src/.env" -Encoding UTF8
572+
573+
Write-Host ".env file created successfully at ../src/.env"
574+
Write-Host "Environment variables configured for:"
575+
if ($phi4Available) {
576+
Write-Host " - Models: gpt-4o-mini, text-embedding-3-small, phi-4"
577+
} else {
578+
Write-Host " - Models: gpt-4o-mini, text-embedding-3-small (phi-4 not available)"
579+
}
580+
Write-Host " - Azure AI Foundry: ${local.ai_foundry_name}"
581+
Write-Host " - Azure AI Project: ${local.ai_project_name}"
582+
Write-Host " - Cosmos DB: ${local.cosmos_account_name}"
583+
Write-Host " - Search Service: ${local.search_service_name}"
584+
Write-Host " - Storage Account: ${local.storage_account}"
585+
Write-Host " - Application Insights: ${local.app_insights_name}"
586+
EOT
587+
interpreter = ["PowerShell", "-Command"]
588+
}
589+
590+
triggers = {
591+
# Trigger recreation when any of these resources change
592+
ai_foundry_id = azapi_resource.ai_foundry.id
593+
ai_project_id = azapi_resource.ai_project.id
594+
cosmos_id = azurerm_cosmosdb_account.cosmos.id
595+
search_id = azurerm_search_service.search.id
596+
storage_id = azapi_resource.storage.id
597+
app_insights_id = azurerm_application_insights.appinsights.id
598+
}
599+
}

terraform-infrastructure/outputs.tf

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,52 @@ output "application_url" {
2727
value = azurerm_linux_web_app.app.default_hostname
2828
description = "Primary host name for the App Service"
2929
}
30+
31+
output "ai_foundry_name" {
32+
value = local.ai_foundry_name
33+
description = "Azure AI Foundry account name"
34+
}
35+
36+
output "ai_project_name" {
37+
value = local.ai_project_name
38+
description = "Azure AI Foundry project name"
39+
}
40+
41+
output "resource_group_name" {
42+
value = azurerm_resource_group.rg.name
43+
description = "Resource group name"
44+
}
45+
46+
output "subscription_id" {
47+
value = data.azurerm_client_config.current.subscription_id
48+
description = "Azure subscription ID"
49+
}
50+
51+
output "application_insights_connection_string" {
52+
value = azurerm_application_insights.appinsights.connection_string
53+
description = "Application Insights connection string"
54+
sensitive = true
55+
}
56+
57+
output "cosmos_db_name" {
58+
value = local.cosmos_db_name
59+
description = "Cosmos DB database name"
60+
}
61+
62+
output "ai_foundry_endpoint" {
63+
value = "https://${local.ai_foundry_name}.cognitiveservices.azure.com/"
64+
description = "Azure AI Foundry endpoint URL"
65+
}
66+
67+
output "deployed_models" {
68+
value = var.enable_ai_automation ? [
69+
"gpt-4o-mini",
70+
"text-embedding-3-small"
71+
] : []
72+
description = "List of AI models actually deployed (phi-4 not available in this region)"
73+
}
74+
75+
output "env_file_location" {
76+
value = var.enable_ai_automation ? "../src/.env" : "Not created (AI automation disabled)"
77+
description = "Location of the generated .env file"
78+
}

terraform-infrastructure/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,9 @@ variable "enable_cosmos_local_auth" {
2626
description = "Whether to enable local auth on Cosmos DB account"
2727
default = true
2828
}
29+
30+
variable "enable_ai_automation" {
31+
type = bool
32+
description = "Whether to run Azure AI Foundry automation steps (model deployments, connections, .env creation)"
33+
default = true
34+
}

0 commit comments

Comments
 (0)