Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e7c27d7
refactor(terraform): refactor to use less variables #94301
jimmykurian Oct 9, 2025
c1d7b14
docs(guide): update guide for terraform updates #93401
jimmykurian Oct 10, 2025
a517f7e
chore(readme): clean up redundancies #94301
jimmykurian Oct 10, 2025
a174a87
Merge branch 'main' into feat/94301
jimmykurian Oct 10, 2025
b378ef5
chore(docs): run linter #94301
jimmykurian Oct 10, 2025
ce5b2c6
chore(terraform): update terraform examples file #94301
jimmykurian Oct 10, 2025
3d63c5f
Update guides/implement_ai_foundry_basic_with_azure_function_integrat…
jimmykurian Oct 10, 2025
cc197b7
refactor(terraform): add feedback from copilot #94301
jimmykurian Oct 10, 2025
fe2f807
Merge branch 'main' into feat/94301
jimmykurian Oct 11, 2025
ce5a236
refactor(tests): update acceptance tests from PR Tests #94301
jimmykurian Oct 13, 2025
302e23b
Merge branch 'main' into feat/94301
jimmykurian Oct 13, 2025
fac5dcd
Merge branch 'main' into feat/94301
jimmykurian Oct 13, 2025
df79ada
refactor(terraform): update from PR feedback #94301
jimmykurian Oct 13, 2025
28cc3c1
refactor(terraform): update from PR Feedback #94301
jimmykurian Oct 14, 2025
9690a35
refactor(terraform): update to use provider function for parsing #94301
jimmykurian Oct 14, 2025
f6780fb
Merge branch 'main' into feat/94301
jimmykurian Oct 14, 2025
8221f07
refactor(terraform): update to use provider function for parsing #94301
jimmykurian Oct 15, 2025
5635632
docs(guide): update readme #94301
jimmykurian Oct 15, 2025
9ad2fdc
refactor(terraform): update from PR feedback #94301
jimmykurian Oct 15, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This guide shows you how to use CAIRA's `foundry_basic` reference architecture w
**What You'll Learn:**

- How to consume foundry_basic outputs in your function layer
- Automatic resource discovery through ID parsing
- Managed identity patterns for keyless authentication with AI Foundry
- Agent lifecycle management through serverless endpoints
- Monitoring integration with Application Insights
Expand Down Expand Up @@ -42,17 +43,26 @@ This guide demonstrates a clean separation of concerns:

1. **Foundation Layer** (foundry_basic): Manages AI Foundry infrastructure
1. **Function Layer** (this guide): Consumes foundry_basic outputs and provides application endpoints
1. **Integration Layer**: Terraform data sources and managed identity RBAC
1. **Integration Layer**: Terraform data sources with automatic resource discovery

**Key Integration Point:**

```hcl
# Grant function access using managed identity
resource "azurerm_role_assignment" "function_ai_foundry_user" {
scope = var.foundry_ai_foundry_id # From foundry_basic output
role_definition_name = "Cognitive Services User"
principal_id = azurerm_linux_function_app.main.identity[0].principal_id
variable "foundry_ai_foundry_id" {
type = string
description = "The resource ID of the AI Foundry account from foundry_basic deployment"
}

variable "foundry_ai_foundry_project_id" {
type = string
description = "The resource ID of the AI Foundry Project from foundry_basic deployment"
}

variable "foundry_log_analytics_workspace_id" {
type = string
description = "The resource ID of the Log Analytics workspace from foundry_basic deployment"
}

```

See the complete implementation in [`terraform/main.tf`](terraform/main.tf) and [`terraform/function.tf`](terraform/function.tf).
Expand All @@ -77,8 +87,8 @@ identity {
type = "SystemAssigned" # Azure creates and manages the identity
}
app_settings = {
"AI_FOUNDRY_ENDPOINT" = var.foundry_ai_foundry_endpoint
"AI_FOUNDRY_PROJECT_NAME" = var.foundry_ai_foundry_project_name
"AI_FOUNDRY_ENDPOINT" = local.ai_foundry_endpoint # Discovered via data source
"AI_FOUNDRY_PROJECT_NAME" = local.ai_foundry_project_name # Parsed from project ID
"AI_FOUNDRY_PROJECT_ID" = var.foundry_ai_foundry_project_id
}
```
Expand All @@ -98,9 +108,36 @@ resource "azurerm_role_assignment" "function_ai_foundry_user" {

See complete infrastructure code: [`terraform/function.tf`](terraform/function.tf)

### Part 2: Connecting Function Layer to foundry_basic
### Part 2: Automatic Resource Discovery

Your function layer automatically discovers resources from just 3 resource IDs. See [`terraform/variables.tf`](terraform/variables.tf) for the complete interface.

**How it works:**

```hcl
# 1. Parse AI Foundry resource ID to extract names
locals {
ai_foundry_id_parts = split("/", var.foundry_ai_foundry_id)
foundry_resource_group_name = local.ai_foundry_id_parts[4] # Extract RG name
ai_foundry_name = local.ai_foundry_id_parts[8] # Extract account name
}

# 2. Use parsed names in data sources to discover resources
data "azurerm_cognitive_account" "ai_foundry" {
name = local.ai_foundry_name
resource_group_name = local.foundry_resource_group_name
}

Your function layer receives foundry_basic outputs as input variables. See [`terraform/variables.tf`](terraform/variables.tf) for the complete interface.
# 3. Discover Application Insights using naming convention
locals {
application_insights_name = "appi-${replace(local.foundry_resource_group_name, "rg-", "")}"
}

data "azurerm_application_insights" "this" {
name = local.application_insights_name
resource_group_name = local.foundry_resource_group_name
}
```

See complete setup: [`terraform/main.tf`](terraform/main.tf)

Expand All @@ -115,7 +152,7 @@ from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient

def get_project_client() -> AIProjectClient:
credential = DefaultAzureCredential() # Automatically uses managed identity!
credential = DefaultAzureCredential() # Automatically uses managed identity
endpoint = os.getenv("AI_FOUNDRY_ENDPOINT") # From terraform config

# Transform to AI Foundry project endpoint format
Expand Down Expand Up @@ -179,7 +216,7 @@ This validates the entire integration: agent creation, conversation, code interp
**Connection flow:**

1. User sends HTTPS request to function endpoint
1. Function runtime loads environment variables (AI Foundry endpoint from terraform)
1. Function runtime loads environment variables (AI Foundry endpoint discovered from data source)
1. DefaultAzureCredential requests token using managed identity
1. Azure AD validates the identity and returns access token
1. AI Projects SDK calls AI Foundry with the token
Expand All @@ -205,17 +242,27 @@ cd scripts
./configure-local-settings.sh
```

The script automatically parses terraform state to extract all needed configuration values.

See local setup details: [`scripts/configure-local-settings.sh`](scripts/configure-local-settings.sh)

## Key CAIRA Integration Patterns

### 1. Consuming foundry_basic Outputs
### 1. Configuration with Automatic Discovery

Your function layer receives everything it needs from foundry_basic as terraform variables:
Your function layer receives only 3 resource IDs from foundry_basic:

- AI Foundry endpoint and project details
- Application Insights connection
- Log Analytics workspace for diagnostic logs
- AI Foundry resource ID (contains resource group and account name)
- AI Foundry project resource ID (contains project name)
- Log Analytics workspace resource ID

Everything else is automatically discovered:

- Resource group name β†’ Parsed from AI Foundry ID
- AI Foundry account name β†’ Parsed from AI Foundry ID
- AI Foundry project name β†’ Parsed from project ID
- AI Foundry endpoint β†’ Retrieved via data source
- Application Insights β†’ Discovered using naming convention

See the complete variable interface: [`terraform/variables.tf`](terraform/variables.tf)

Expand Down Expand Up @@ -341,14 +388,8 @@ terraform init
terraform apply

# Capture outputs for the function layer
RG_NAME=$(terraform output -raw resource_group_name)
AI_FOUNDRY_NAME=$(terraform output -raw ai_foundry_name)
AI_FOUNDRY_ENDPOINT=$(terraform output -raw ai_foundry_endpoint)
AI_FOUNDRY_ID=$(terraform output -raw ai_foundry_id)
AI_PROJECT_ID=$(terraform output -raw ai_foundry_project_id)
AI_PROJECT_NAME=$(terraform output -raw ai_foundry_project_name)
APPINSIGHTS_ID=$(terraform output -raw application_insights_id)
APPINSIGHTS_NAME=$(basename "$APPINSIGHTS_ID")
LOG_WORKSPACE_ID=$(terraform output -raw log_analytics_workspace_id)
```

Expand All @@ -359,12 +400,8 @@ cd ../../guides/implement_ai_foundry_basic_with_azure_function_integration/terra

# Create terraform.tfvars with the captured values
cat > terraform.tfvars <<EOF
foundry_resource_group_name = "$RG_NAME"
foundry_ai_foundry_id = "$AI_FOUNDRY_ID"
foundry_ai_foundry_endpoint = "$AI_FOUNDRY_ENDPOINT"
foundry_ai_foundry_project_id = "$AI_PROJECT_ID"
foundry_ai_foundry_project_name = "$AI_PROJECT_NAME"
foundry_application_insights_name = "$APPINSIGHTS_NAME"
foundry_log_analytics_workspace_id = "$LOG_WORKSPACE_ID"
project_name = "ai-integration"
function_sku_size = "B1"
Expand All @@ -381,7 +418,7 @@ terraform apply
- RBAC role granting AI Foundry access
- Storage account for function runtime
- Application Insights integration
- All configuration wired automatically
- All configuration wired automatically through resource discovery

### Step 3: Deploy Function Code

Expand Down Expand Up @@ -531,6 +568,12 @@ curl -X POST http://localhost:7071/api/agent \

- Solution: `az login` and `az account set --subscription <your-subscription-id>`

**Application Insights not found error**

- Cause: Naming convention mismatch between foundry_basic and function layer
- Solution: Verify Application Insights name matches pattern: `appi-{base_name}-{suffix}`
- Check: `az resource list --resource-group <foundry-rg> --resource-type Microsoft.Insights/components`

### Debugging Commands

```bash
Expand All @@ -545,6 +588,13 @@ az functionapp identity show --name $FUNCTION_APP_NAME --resource-group $RESOURC

# Check deployed functions
az functionapp function list --name $FUNCTION_APP_NAME --resource-group $RESOURCE_GROUP --output table

# Verify parsed values from terraform
cd terraform
terraform console
> local.foundry_resource_group_name
> local.ai_foundry_name
> local.ai_foundry_project_name
```

## Monitoring
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/bin/bash
# configure-local-settings.sh - Configure local settings for AI Foundry integration
# Gets all values from the functions layer deployment

set -e

Expand All @@ -20,28 +19,40 @@ if [ ! -f "terraform.tfstate" ]; then
exit 1
fi

echo "Fetching configuration from functions layer..."
echo "Parsing configuration from terraform state..."

# Get values from terraform outputs
FUNCTION_APP_NAME=$(terraform output -raw function_app_name)
FUNCTION_APP_URL=$(terraform output -raw function_app_url)

# Get the foundry values from terraform state (these are from data sources)
AI_FOUNDRY_NAME=$(terraform state show data.azurerm_cognitive_account.ai_foundry | grep "^\s*name\s*=" | head -1 | awk -F'"' '{print $2}')
RESOURCE_GROUP=$(terraform state show data.azurerm_resource_group.this | grep "^\s*name\s*=" | head -1 | awk -F'"' '{print $2}')
# Parse terraform state to extract internal values
# These are not outputs but internal state values we need for local development

# Get project name and ID from terraform variables/state
AI_FOUNDRY_PROJECT_NAME=$(terraform state show var.foundry_ai_foundry_project_name | grep "^\s*value\s*=" | awk -F'"' '{print $2}')
AI_FOUNDRY_PROJECT_ID=$(terraform state show var.foundry_ai_foundry_project_id | grep "^\s*value\s*=" | awk -F'"' '{print $2}')
# Get parsed resource group name from locals (used in data sources)
RESOURCE_GROUP=$(terraform show -json | jq -r '
.values.root_module.resources[] |
select(.type == "azurerm_resource_group" and .name == "function") |
.values.name
')

# Get AI Foundry endpoint using Azure CLI
AI_FOUNDRY_ENDPOINT=$(az cognitiveservices account show \
--name "$AI_FOUNDRY_NAME" \
--resource-group "$RESOURCE_GROUP" \
--query "properties.endpoint" -o tsv)
# Get AI Foundry endpoint from data source
AI_FOUNDRY_ENDPOINT=$(terraform show -json | jq -r '
.values.root_module.resources[] |
select(.type == "azurerm_cognitive_account" and .mode == "data") |
.values.endpoint
')

# Get subscription ID from Azure CLI
AZURE_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Get AI Foundry project name from input variable
AI_FOUNDRY_PROJECT_ID=$(terraform show -json | jq -r '.values.root_module.variables.foundry_ai_foundry_project_id.value')

# Parse project name from project ID (last segment after final /)
AI_FOUNDRY_PROJECT_NAME="${AI_FOUNDRY_PROJECT_ID##*/}"

# Get subscription ID from input variable (foundry_ai_foundry_id)
AI_FOUNDRY_ID=$(terraform show -json | jq -r '.values.root_module.variables.foundry_ai_foundry_id.value')

# Parse subscription ID from AI Foundry resource ID (3rd segment)
AZURE_SUBSCRIPTION_ID=$(echo "$AI_FOUNDRY_ID" | cut -d'/' -f3)

# Navigate to function-app directory
cd ../function-app
Expand Down Expand Up @@ -77,10 +88,10 @@ echo ""
echo "Configuration Summary:"
echo "----------------------"
echo "Resource Group: $RESOURCE_GROUP"
echo "AI Foundry Name: $AI_FOUNDRY_NAME"
echo "AI Foundry Endpoint: $AI_FOUNDRY_ENDPOINT"
echo "AI Foundry Project: $AI_FOUNDRY_PROJECT_NAME"
echo "AI Foundry Project ID: $AI_FOUNDRY_PROJECT_ID"
echo "Subscription ID: $AZURE_SUBSCRIPTION_ID"
echo "Function App Name: $FUNCTION_APP_NAME"
echo "Function App URL: $FUNCTION_APP_URL"
echo ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Storage Account for Function App
resource "azurerm_storage_account" "function" {
name = replace(module.naming.storage_account.name_unique, "-", "")
resource_group_name = local.resource_group_name
resource_group_name = local.function_resource_group_name
location = local.location
account_tier = "Standard"
account_replication_type = "LRS"
Expand Down Expand Up @@ -35,7 +35,7 @@ resource "azurerm_storage_account" "function" {
# App Service Plan for Function App
resource "azurerm_service_plan" "function" {
name = module.naming.app_service_plan.name_unique
resource_group_name = local.resource_group_name
resource_group_name = local.function_resource_group_name
location = local.location
os_type = "Linux"
sku_name = var.function_sku_size
Expand All @@ -48,7 +48,7 @@ resource "azurerm_service_plan" "function" {
# Linux Function App with Managed Identity
resource "azurerm_linux_function_app" "main" {
name = local.function_app_name
resource_group_name = local.resource_group_name
resource_group_name = local.function_resource_group_name
location = local.location
service_plan_id = azurerm_service_plan.function.id

Expand Down Expand Up @@ -82,8 +82,8 @@ resource "azurerm_linux_function_app" "main" {
# Application settings
app_settings = {
"FUNCTIONS_WORKER_RUNTIME" = "python"
"AI_FOUNDRY_ENDPOINT" = var.foundry_ai_foundry_endpoint
"AI_FOUNDRY_PROJECT_NAME" = var.foundry_ai_foundry_project_name
"AI_FOUNDRY_ENDPOINT" = local.ai_foundry_endpoint
"AI_FOUNDRY_PROJECT_NAME" = local.ai_foundry_project_name
"AI_FOUNDRY_PROJECT_ID" = var.foundry_ai_foundry_project_id
"APPLICATIONINSIGHTS_CONNECTION_STRING" = data.azurerm_application_insights.this.connection_string
"AzureWebJobsStorage__accountName" = azurerm_storage_account.function.name
Expand Down
Loading
Loading