diff --git a/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json index 5edf3043..fbb96f35 100644 --- a/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/00-basic/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4687470877814835157" + "version": "0.40.2.10011", + "templateHash": "15247532385970128893" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json index 672e7892..3c7ec409 100644 --- a/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/03-custom-dns/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17555450976668837106" + "version": "0.40.2.10011", + "templateHash": "14038434470119297573" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json index d2cee29a..396a1b9a 100644 --- a/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/04-disable-local-auth/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "6087024663439753870" + "version": "0.40.2.10011", + "templateHash": "6518302005159872058" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json index 840868a3..7ea345c1 100644 --- a/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/10-private-network-basic/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "258335635459710717" + "version": "0.40.2.10011", + "templateHash": "6904353034679125432" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json index 06c33519..a87e4060 100644 --- a/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/20-user-assigned-identity/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3240349148352159726" + "version": "0.40.2.10011", + "templateHash": "11385179130648578926" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json index 64192cbd..6cb7392c 100644 --- a/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/30-customer-managed-keys/azuredeploy.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "16694821934772097765" + "version": "0.40.2.10011", + "templateHash": "16822506733457783114" } }, "parameters": { @@ -142,8 +142,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "10984074837058238866" + "version": "0.40.2.10011", + "templateHash": "812724488663360404" } }, "parameters": { diff --git a/infrastructure/infrastructure-setup-bicep/32-customer-managed-keys-user-assigned-identity/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/32-customer-managed-keys-user-assigned-identity/azuredeploy.json new file mode 100644 index 00000000..cf143128 --- /dev/null +++ b/infrastructure/infrastructure-setup-bicep/32-customer-managed-keys-user-assigned-identity/azuredeploy.json @@ -0,0 +1,428 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "9619608194674690722" + } + }, + "parameters": { + "aiFoundryName": { + "type": "string", + "defaultValue": "ai-foundry-complete-cmk", + "metadata": { + "description": "That name is the name of our application. It has to be unique." + } + }, + "aiProjectName": { + "type": "string", + "defaultValue": "[format('{0}-proj', parameters('aiFoundryName'))]", + "metadata": { + "description": "Name of the AI Foundry project" + } + }, + "location": { + "type": "string", + "defaultValue": "eastus2", + "metadata": { + "description": "Location for all resources." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Name of the Azure Key Vault target" + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Name of the Azure Key Vault key" + } + }, + "keyVersion": { + "type": "string", + "metadata": { + "description": "Version of the Azure Key Vault key" + } + }, + "userAssignedIdentityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user-assigned managed identity to use for CMK encryption" + } + }, + "userAssignedIdentityClientId": { + "type": "string", + "metadata": { + "description": "Client ID of the user-assigned managed identity" + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "foundryAccount", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiFoundryName": { + "value": "[parameters('aiFoundryName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "userAssignedIdentityId": { + "value": "[parameters('userAssignedIdentityId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2042207162176060264" + } + }, + "parameters": { + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "Name of the AI Foundry account" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Location for the resource" + } + }, + "userAssignedIdentityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user-assigned managed identity" + } + } + }, + "variables": { + "identityConfig": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', parameters('userAssignedIdentityId'))]": {} + } + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-04-01-preview", + "name": "[parameters('aiFoundryName')]", + "location": "[parameters('location')]", + "identity": "[variables('identityConfig')]", + "kind": "AIServices", + "sku": { + "name": "S0" + }, + "properties": { + "allowProjectManagement": true, + "publicNetworkAccess": "Enabled", + "customSubDomainName": "[parameters('aiFoundryName')]", + "disableLocalAuth": true + } + } + ], + "outputs": { + "accountId": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('aiFoundryName'))]" + }, + "accountName": { + "type": "string", + "value": "[parameters('aiFoundryName')]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "cmkEncryption", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiFoundryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountName.value]" + }, + "location": { + "value": "[parameters('location')]" + }, + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "keyName": { + "value": "[parameters('keyName')]" + }, + "keyVersion": { + "value": "[parameters('keyVersion')]" + }, + "userAssignedIdentityId": { + "value": "[parameters('userAssignedIdentityId')]" + }, + "userAssignedIdentityClientId": { + "value": "[parameters('userAssignedIdentityClientId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "1200570362967678125" + } + }, + "parameters": { + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "Name of the AI Foundry account" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Location for the resource" + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Name of the Azure Key Vault" + } + }, + "keyName": { + "type": "string", + "metadata": { + "description": "Name of the Azure Key Vault key" + } + }, + "keyVersion": { + "type": "string", + "metadata": { + "description": "Version of the Azure Key Vault key" + } + }, + "userAssignedIdentityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user-assigned managed identity" + } + }, + "userAssignedIdentityClientId": { + "type": "string", + "metadata": { + "description": "Client ID of the user-assigned managed identity" + } + } + }, + "variables": { + "keyVaultUri": "[format('https://{0}{1}/', parameters('keyVaultName'), environment().suffixes.keyvaultDns)]" + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2025-04-01-preview", + "name": "[parameters('aiFoundryName')]", + "location": "[parameters('location')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', parameters('userAssignedIdentityId'))]": {} + } + }, + "kind": "AIServices", + "sku": { + "name": "S0" + }, + "properties": { + "encryption": { + "keySource": "Microsoft.KeyVault", + "keyVaultProperties": { + "keyVaultUri": "[variables('keyVaultUri')]", + "keyName": "[parameters('keyName')]", + "keyVersion": "[parameters('keyVersion')]", + "identityClientId": "[parameters('userAssignedIdentityClientId')]" + } + }, + "allowProjectManagement": true, + "publicNetworkAccess": "Enabled", + "customSubDomainName": "[parameters('aiFoundryName')]", + "disableLocalAuth": true + } + } + ], + "outputs": { + "encryptionStatus": { + "type": "string", + "value": "CMK encryption enabled" + }, + "keyVaultUri": { + "type": "string", + "value": "[variables('keyVaultUri')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'foundryAccount')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "foundryProject", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "aiFoundryName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountName.value]" + }, + "projectName": { + "value": "[parameters('aiProjectName')]" + }, + "projectDisplayName": { + "value": "[parameters('aiProjectName')]" + }, + "projectDescription": { + "value": "AI Foundry project with customer-managed keys" + }, + "location": { + "value": "[parameters('location')]" + }, + "userAssignedIdentityId": { + "value": "[parameters('userAssignedIdentityId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14690874522422526659" + } + }, + "parameters": { + "aiFoundryName": { + "type": "string", + "metadata": { + "description": "Name of the AI Foundry account (parent)" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Location for the resource" + } + }, + "projectName": { + "type": "string", + "metadata": { + "description": "Name of the project" + } + }, + "projectDisplayName": { + "type": "string", + "metadata": { + "description": "Display name for the project" + } + }, + "projectDescription": { + "type": "string", + "metadata": { + "description": "Description for the project" + } + }, + "userAssignedIdentityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user-assigned managed identity" + } + } + }, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/projects", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}', parameters('aiFoundryName'), parameters('projectName'))]", + "location": "[parameters('location')]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', parameters('userAssignedIdentityId'))]": {} + } + }, + "properties": { + "displayName": "[parameters('projectDisplayName')]", + "description": "[parameters('projectDescription')]" + } + } + ], + "outputs": { + "projectId": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', parameters('aiFoundryName'), parameters('projectName'))]" + }, + "projectName": { + "type": "string", + "value": "[parameters('projectName')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'foundryAccount')]", + "[resourceId('Microsoft.Resources/deployments', 'cmkEncryption')]" + ] + } + ], + "outputs": { + "accountId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountId.value]" + }, + "accountName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryAccount'), '2025-04-01').outputs.accountName.value]" + }, + "projectId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryProject'), '2025-04-01').outputs.projectId.value]" + }, + "projectName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'foundryProject'), '2025-04-01').outputs.projectName.value]" + }, + "keyVaultUri": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'cmkEncryption'), '2025-04-01').outputs.keyVaultUri.value]" + } + } +} \ No newline at end of file diff --git a/samples/csharp/FoundryA365/infra/main.bicep b/samples/csharp/FoundryA365/infra/main.bicep index b26c8a12..0bfa2c39 100644 --- a/samples/csharp/FoundryA365/infra/main.bicep +++ b/samples/csharp/FoundryA365/infra/main.bicep @@ -172,3 +172,5 @@ output AGENT_NAME string = agentName output TENANT_ID string = tenant().tenantId output PROJECT_PRINCIPAL_ID string = project.outputs.foundryProjectPrincipalId + +output AGENT_VERSION string = deploymentScriptAgent.outputs.agentVersion diff --git a/samples/csharp/FoundryA365/infra/modules/agent-deployment-script.bicep b/samples/csharp/FoundryA365/infra/modules/agent-deployment-script.bicep index 287e3e59..f01782a4 100644 --- a/samples/csharp/FoundryA365/infra/modules/agent-deployment-script.bicep +++ b/samples/csharp/FoundryA365/infra/modules/agent-deployment-script.bicep @@ -97,7 +97,16 @@ resource psScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = { Write-Host "Response:" $response | ConvertTo-Json -Depth 100 | Write-Host + # Output the agent version + $agentVersion = $response.version + Write-Host "Agent Version: $agentVersion" + $DeploymentScriptOutputs = @{ + agentVersion = $agentVersion + } + ''' } } + +output agentVersion string = psScript.properties.outputs.agentVersion diff --git a/samples/csharp/FoundryA365/readme.md b/samples/csharp/FoundryA365/readme.md index d53526dc..78fb4b58 100644 --- a/samples/csharp/FoundryA365/readme.md +++ b/samples/csharp/FoundryA365/readme.md @@ -13,7 +13,6 @@ Ensure you have the following installed: | Requirement | Description | |------------|-------------| | [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd) | Infrastructure deployment tool | -| [Docker](https://docker.com) | Container runtime | | [.NET 9.0 SDK](https://dotnet.microsoft.com/download) | Development framework | @@ -31,6 +30,8 @@ Ensure you have the following installed: Login to your Azure tenant and authenticate with Azure Developer CLI: +Based on tenant security settings, sometimes just az login might be sufficient, sometimes one will need to login to each scope that is used in these scripts. + ```powershell # Login to Azure CLI az login @@ -39,6 +40,7 @@ az login --scope https://ai.azure.com/.default az login --scope https://graph.microsoft.com//.default +az login --scope https://management.azure.com/.default # Login to Azure Developer CLI azd auth login ``` @@ -58,7 +60,7 @@ Before deploying, you can customize: Ensure Docker is running, then execute: ```powershell -azd provision --verbose +azd provision ``` After deployment completes, retrieve your resource values: diff --git a/samples/csharp/FoundryA365/scripts/build-docker-image-acr.ps1 b/samples/csharp/FoundryA365/scripts/build-docker-image-acr.ps1 new file mode 100644 index 00000000..9d6cd742 --- /dev/null +++ b/samples/csharp/FoundryA365/scripts/build-docker-image-acr.ps1 @@ -0,0 +1,47 @@ +# Build Docker image using Azure Container Registry (ACR) Build +# This script uses ACR Tasks to build the image in the cloud instead of locally + +Set-Location "$($PSScriptRoot)/../src/hello_world_a365_agent" + +Remove-Item "./publish" -Recurse -Force -ErrorAction SilentlyContinue + +dotnet publish -c Release -o "./publish" + +$authorityEndpoint = "https://login.microsoftonline.com/$($env:TENANT_ID)" +$azureOpenAIEndpoint = "https://$($env:ACCOUNT_NAME).openai.azure.com/" + +$projectClientId = az ad sp show --id $env:PROJECT_PRINCIPAL_ID --query appId -o tsv + +# if the projectClientId is null or empty, throw an error +if ([string]::IsNullOrEmpty($projectClientId)) { + throw "Failed to get project client ID for principal ID $($env:PROJECT_PRINCIPAL_ID)" +} + +$acrLoginServer = $env:AZURE_CONTAINER_REGISTRY_ENDPOINT + +# split the login server to get the registry name +$registryName = $acrLoginServer.Split(".")[0] + +$imageName = "hello-world-a365-agent:a365preview001" + +Write-Host "Building image using ACR Build in registry: $registryName" + +# Build image using ACR Build (builds in the cloud) +az acr build ` + --registry $registryName ` + --image $imageName ` + --file "./foundry-infra/Dockerfile" ` + --build-arg BLUEPRINT_CLIENT_ID=$env:AGENT_IDENTITY_BLUEPRINT_ID ` + --build-arg AUTHORITY_ENDPOINT=$authorityEndpoint ` + --build-arg FEDERATED_CLIENT_ID=$projectClientId ` + --build-arg AZURE_OPENAI_ENDPOINT=$azureOpenAIEndpoint ` + --build-arg MODEL_DEPLOYMENT='gpt-4o' ` + . + +if ($LASTEXITCODE -ne 0) { + throw "ACR build failed with exit code $LASTEXITCODE" +} + +Write-Host "Image built and pushed successfully: $acrLoginServer/$imageName" + +Remove-Item "./publish" -Recurse -Force -ErrorAction SilentlyContinue diff --git a/samples/csharp/FoundryA365/scripts/create-application-deployment.ps1 b/samples/csharp/FoundryA365/scripts/create-application-deployment.ps1 index 5906e258..d6ef0f4e 100644 --- a/samples/csharp/FoundryA365/scripts/create-application-deployment.ps1 +++ b/samples/csharp/FoundryA365/scripts/create-application-deployment.ps1 @@ -3,6 +3,7 @@ param( [string]$AgentVersion ) + $ErrorActionPreference = "Stop" $applicationDeploymentBody = @{ @@ -50,21 +51,6 @@ Write-Host "Response:" $responseDeployment | ConvertTo-Json -Depth 100 | Write-Host -# Write-Host "Starting application deployment..." -# $startDeploymentUrl = "https://management.azure.com/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)/resourceGroups/$($env:AZURE_RESOURCE_GROUP)/providers/Microsoft.CognitiveServices/accounts/$($env:ACCOUNT_NAME)/projects/$($env:PROJECT_NAME)/applications/$($env:APPLICATION_NAME)/agentDeployments/foundry-agent-deployment/start?api-version=2025-10-01-preview" - -# $responseDeployment = Invoke-RestMethod -Uri $startDeploymentUrl ` -# -Method Post ` -# -Headers @{ -# "Content-Type" = "application/json" -# "Accept" = "application/json" -# "Authorization" = "Bearer $($managementToken)" -# } ` - -# Write-Host "" -# Write-Host "Response:" -# $responseDeployment | ConvertTo-Json -Depth 100 | Write-Host - while ($true) { Start-Sleep -Seconds 10 Write-Host "Get application deployment status..." diff --git a/samples/csharp/FoundryA365/scripts/post-provision.ps1 b/samples/csharp/FoundryA365/scripts/post-provision.ps1 index 4004093c..b9d3b089 100644 --- a/samples/csharp/FoundryA365/scripts/post-provision.ps1 +++ b/samples/csharp/FoundryA365/scripts/post-provision.ps1 @@ -15,11 +15,11 @@ Write-Host "===============OAuth2 grants for blueprint SP===============" Write-Host "===============Building and pushing Docker image===============" -& "$PSScriptRoot/build-docker-image.ps1" +& "$PSScriptRoot/build-docker-image-acr.ps1" -Write-Host "===============Creating Foundry container agent===============" -& "$PSScriptRoot/create-foundry-container-agent.ps1" +Write-Host "===============Creating Foundry container agent===============" +& "$PSScriptRoot/create-application-deployment.ps1" -AgentName $env:AGENT_NAME -AgentVersion $env:AGENT_VERSION Write-Host ""