Skip to content

Commit 3a386db

Browse files
Merge pull request #156 from microsoft/dev
chore: Dev to Main merge
2 parents e16fac2 + 54002b4 commit 3a386db

20 files changed

Lines changed: 3696 additions & 21302 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: AZD Template Validation
2+
on:
3+
schedule:
4+
- cron: '30 1 * * 4' # Every Thursday at 7:00 AM IST (1:30 AM UTC)
5+
workflow_dispatch:
6+
7+
permissions:
8+
contents: read
9+
id-token: write
10+
pull-requests: write
11+
12+
jobs:
13+
template_validation:
14+
runs-on: ubuntu-latest
15+
name: azd template validation
16+
environment: production
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: microsoft/template-validation-action@v0.4.3
21+
with:
22+
validateAzd: ${{ vars.TEMPLATE_VALIDATE_AZD }}
23+
validateTests: ${{ vars.TEMPLATE_VALIDATE_TESTS }}
24+
useDevContainer: ${{ vars.TEMPLATE_USE_DEV_CONTAINER }}
25+
id: validation
26+
env:
27+
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
28+
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
29+
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
30+
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
31+
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
32+
AZURE_AI_SERVICE_LOCATION: ${{ vars.AZURE_LOCATION }}
33+
AZURE_AI_MODEL_CAPACITY: 1 # keep low to avoid potential quota issues
34+
GITHUB_TOKEN: ${{ secrets.AZD_GITHUB_TOKEN }}
35+
36+
- name: print result
37+
run: cat ${{ steps.validation.outputs.resultFile }}

.github/workflows/azure-dev.yml

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,51 @@
1-
name: AZD Template Validation
1+
name: Azure Dev Deploy
2+
23
on:
34
workflow_dispatch:
45

56
permissions:
67
contents: read
78
id-token: write
8-
pull-requests: write
99

1010
jobs:
11-
template_validation:
11+
deploy:
1212
runs-on: ubuntu-latest
13-
name: azd template validation
14-
environment: azd-template-gallery
13+
environment: production
14+
env:
15+
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
16+
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
17+
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
18+
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
19+
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
20+
AZURE_DEV_COLLECT_TELEMETRY: ${{ vars.AZURE_DEV_COLLECT_TELEMETRY }}
1521
steps:
16-
- uses: actions/checkout@v4
22+
- name: Checkout Code
23+
uses: actions/checkout@v4
24+
25+
- name: Install azd
26+
uses: Azure/setup-azd@v2
1727

18-
- uses: microsoft/template-validation-action@v0.4.3
28+
- name: Login to Azure
29+
uses: azure/login@v2
1930
with:
20-
validateAzd: ${{ vars.AZD_VALIDATE }}
21-
useDevContainer: ${{ vars.AZD_USE_DEV_CONTAINER }}
22-
id: validation
23-
env:
24-
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
25-
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
26-
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
27-
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
28-
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
29-
AZURE_AI_MODEL_CAPACITY: 1 # keep low to avoid potential quota issues
30-
GITHUB_TOKEN: ${{ secrets.AZD_GITHUB_TOKEN }}
31+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
32+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
33+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
34+
35+
- name: Login to AZD
36+
shell: bash
37+
run: |
38+
azd auth login \
39+
--client-id "$AZURE_CLIENT_ID" \
40+
--federated-credential-provider "github" \
41+
--tenant-id "$AZURE_TENANT_ID"
3142
32-
- name: print result
33-
run: cat ${{ steps.validation.outputs.resultFile }}
43+
- name: Provision and Deploy
44+
shell: bash
45+
run: |
46+
if ! azd env select "$AZURE_ENV_NAME"; then
47+
azd env new "$AZURE_ENV_NAME" --subscription "$AZURE_SUBSCRIPTION_ID" --location "$AZURE_LOCATION" --no-prompt
48+
fi
49+
azd config set defaults.subscription "$AZURE_SUBSCRIPTION_ID"
50+
azd env set AZURE_AI_SERVICE_LOCATION="$AZURE_LOCATION"
51+
azd up --no-prompt

azure.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ metadata:
55
template: container-migration-solution-accelerator@1.0
66

77
requiredVersions:
8-
azd: '>=1.18.2'
8+
azd: '>=1.18.2 != 1.23.9'
99

1010
hooks:
1111
postdeploy:

azure_custom.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ requiredVersions:
1616
# Require a recent version of azd that supports the packaging
1717
# functionality used here. Versions less than 1.17.1 had a bug in
1818
# remoteBuild.
19-
azd: ">=1.18.2"
19+
azd: '>=1.18.2 != 1.23.9'
2020

2121
infra:
2222
parameters:

docs/CustomizingAzdParameters.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ By default this template will use the environment name as the prefix to prevent
2020
| `AZURE_ENV_VM_ADMIN_USERNAME` | string | `` | The administrator username for the virtual machine. |
2121
| `AZURE_ENV_VM_ADMIN_PASSWORD` | string | `` | The administrator password for the virtual machine. |
2222
| `AZURE_ENV_IMAGETAG` | string | `latest` | Specifies the container image tag to use for deployment. |
23+
| `AZURE_ENV_VM_SIZE` | string | `Standard_D2s_v5` | Specifies the VM size for the jumpbox virtual machine (production deployment only). |
2324

2425
## How to Set a Parameter
2526

docs/TroubleShootingSteps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Use these as quick reference guides to unblock your deployments.
6161
| **ServiceQuotaExceeded** | Free tier service quota limit reached for Azure AI Search | This error occurs when you attempt to deploy an Azure AI Search service but have already reached the **free tier quota limit** for your subscription. Each Azure subscription is limited to **one free tier Search service**.<br><br>**Example error message:**<br>`ServiceQuotaExceeded: Operation would exceed 'free' tier service quota. You are using 1 out of 1 'free' tier service quota.`<br><br>**Common causes:**<br><ul><li>Already have a free tier Azure AI Search service in the subscription</li><li>Previous deployment created a free tier Search service that wasn't deleted</li><li>Attempting to deploy multiple environments with free tier Search services</li></ul><br>**Resolution:**<br><ul><li>**Option 1: Delete existing free tier Search service:**<br>`az search service list --query "[?sku.name=='free']" -o table`<br>`az search service delete --name <service-name> --resource-group <rg-name> --yes`</li><li>**Option 2: Upgrade to a paid SKU:**<br>Modify your Bicep/ARM template to use `basic`, `standard`, or higher SKU instead of `free`</li><li>**Option 3: Use existing Search service:**<br>Reference the existing free tier Search service in your deployment instead of creating a new one</li><li>**Request quota increase:**<br>Submit a support request with issue type 'Service and subscription limits (quota)' and quota type 'Search' via [Azure Quota Request](https://aka.ms/AddQuotaSubscription)</li></ul><br>**Reference:**<br><ul><li>[Azure AI Search service limits](https://learn.microsoft.com/en-us/azure/search/search-limits-quotas-capacity)</li><li>[Azure AI Search pricing tiers](https://learn.microsoft.com/en-us/azure/search/search-sku-tier)</li></ul> |
6262
| **InsufficientQuota** | Not enough quota available in subscription | <ul><li>Check if you have sufficient quota available in your subscription before deployment</li><li>To verify, refer to the [quota_check](../docs/QuotaCheck.md) file for details</li></ul> |
6363
| **MaxNumberOfRegionalEnvironmentsInSubExceeded** | Maximum Container App Environments limit reached for region |This error occurs when you attempt to create more **Azure Container App Environments** than the regional quota limit allows for your subscription. Each Azure region has a specific limit on the number of Container App Environments that can be created per subscription.<br><br>**Common Causes:**<br><ul><li>Deploying to regions with low quota limits (e.g., Sweden Central allows only 1 environment)</li><li>Multiple deployments without cleaning up previous environments</li><li>Exceeding the standard limit of 15 environments in most major regions</li></ul><br>**Resolution:**<br><ul><li>**Delete unused environments** in the target region, OR</li><li>**Deploy to a different region** with available capacity, OR</li><li>**Request quota increase** via [Azure Support](https://go.microsoft.com/fwlink/?linkid=2208872)</li></ul><br>**Reference:**<br><ul><li>[Azure Container Apps quotas](https://learn.microsoft.com/en-us/azure/container-apps/quotas)</li><li>[Azure subscription and service limits](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits)</li></ul> |
64-
| **SkuNotAvailable** | Requested SKU not available in selected location or zone | You receive this error in the following scenarios:<br><ul><li>When the resource SKU you've selected, such as VM size, isn't available for a location or zone</li><li>If you're deploying an Azure Spot VM or Spot scale set instance, and there isn't any capacity for Azure Spot in this location. For more information, see Spot error messages</li></ul> |
64+
| **SkuNotAvailable** | Requested SKU not available in selected location or zone | This error occurs when the resource SKU you've selected (such as VM size) isn't available for the target location or availability zone.<br><br>**In this deployment**, the jumpbox VM defaults to `Standard_D2s_v5`. While this size is available in most regions, certain regions or zones may not support it.<br><br>**Resolution:**<br><ul><li>**Check SKU availability** for your target region:<br>`az vm list-skus --location <region> --size Standard_D2s --output table`</li><li>**Override the VM size** if the default isn't available in your region:<br>`azd env set AZURE_ENV_VM_SIZE Standard_D2s_v4`</li><li>**Recommended alternatives** (all support accelerated networking + Premium SSD):<br>- `Standard_D2s_v4` — previous gen, identical pricing<br>- `Standard_D2as_v5` — AMD-based, similar pricing<br>- `Standard_D2s_v3` — older gen, widely available</li><li>**Avoid A-series VMs** (e.g., `Standard_A2m_v2`) — they do not support accelerated networking or Premium SSD, which are required by this deployment</li></ul><br>**Reference:**<br><ul><li>[Resolve errors for SKU not available](https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/error-sku-not-available)</li><li>[Azure VM sizes - Dsv5 series](https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dsv5-series)</li></ul> |
6565
| **Conflict - No available instances to satisfy this request** | Azure App Service has insufficient capacity in the region | This error occurs when Azure App Service doesn't have enough available compute instances in the selected region to provision or scale your app.<br><br>**Common Causes:**<br><ul><li>High demand in the selected region (e.g., East US, West Europe)</li><li>Specific SKUs experiencing capacity constraints (Free, Shared, or certain Premium tiers)</li><li>Multiple rapid deployments in the same region</li></ul><br>**Resolution:**<br><ul><li>**Wait and Retry** (15-30 minutes): `azd up`</li><li>**Deploy to a New Resource Group** (Recommended for urgent cases):<br>```<br>azd down --force --purge<br>azd up<br>```</li><li>**Try a Different Region:**<br>Update region in `main.bicep` or `azure.yaml` to a less congested region (e.g., `westus2`, `centralus`, `northeurope`)</li><li>**Use a Different SKU/Tier:**<br>If using Free/Shared tier, upgrade to Basic or Standard<br>Check SKU availability: `az appservice list-locations --sku <sku-name>`</li></ul><br>**Reference:** [Azure App Service Plans](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans) |
6666

6767
--------------------------------

infra/main.bicep

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ module jumpboxVM 'br/public:avm/res/compute/virtual-machine:0.15.0' = if (enable
357357
name: take('avm.res.compute.virtual-machine.${jumpboxVmName}', 64)
358358
params: {
359359
name: take(jumpboxVmName, 15) // Shorten VM name to 15 characters to avoid Azure limits
360-
vmSize: vmSize ?? 'Standard_DS2_v2'
360+
vmSize: vmSize ?? 'Standard_D2s_v5'
361361
location: location
362362
adminUsername: vmAdminUsername ?? 'JumpboxAdminUser'
363363
adminPassword: vmAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!'
@@ -734,6 +734,8 @@ var aiFoundryAiServicesResourceName = useExistingAiFoundryAiProject
734734
? split(existingFoundryProjectResourceId, '/')[8]
735735
: 'aif-${solutionSuffix}'
736736

737+
var aiFoundryAiProjectResourceName = 'proj-${solutionSuffix}'
738+
var aiFoundryAiProjectDescription = 'AI Foundry project for ${solutionName}'
737739

738740
resource existingAiFoundryAiServices 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = if (useExistingAiFoundryAiProject) {
739741
name: aiFoundryAiServicesResourceName
@@ -791,57 +793,19 @@ module existingAiFoundryAiServicesDeployments 'modules/ai-services-deployments.b
791793
}
792794
}
793795

794-
// Temporarily disabled AI Foundry due to AML workspace creation issues
795-
module aiFoundry 'br/public:avm/ptn/ai-ml/ai-foundry:0.4.0' = if(!useExistingAiFoundryAiProject) {
796-
name: take('avm.ptn.ai-ml.ai-foundry.${solutionSuffix}', 64)
796+
// ========== AI Foundry AI Services ========== //
797+
module aiFoundryAiServices 'br/public:avm/res/cognitive-services/account:0.13.2' = if (!useExistingAiFoundryAiProject) {
798+
name: take('avm.res.cognitive-services.account.${aiFoundryAiServicesResourceName}', 64)
797799
params: {
798-
#disable-next-line BCP334
799-
baseName: take(aiFoundryAiServicesResourceName, 12)
800-
baseUniqueName: null
800+
name: aiFoundryAiServicesResourceName
801801
location: empty(azureAiServiceLocation) ? location : azureAiServiceLocation
802-
aiFoundryConfiguration: {
803-
accountName:aiFoundryAiServicesResourceName
804-
allowProjectManagement: true
805-
roleAssignments: [
806-
// Service Principal permissions
807-
{
808-
principalId: appIdentity.outputs.principalId
809-
principalType: 'ServicePrincipal'
810-
roleDefinitionIdOrName: 'Cognitive Services OpenAI Contributor'
811-
}
812-
{
813-
principalId: appIdentity.outputs.principalId
814-
principalType: 'ServicePrincipal'
815-
roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer
816-
}
817-
{
818-
principalId: appIdentity.outputs.principalId
819-
principalType: 'ServicePrincipal'
820-
roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User
821-
}
822-
// Deployer permissions for local debugging
823-
{
824-
principalId: deployingUserPrincipalId
825-
principalType: deployingUserType
826-
roleDefinitionIdOrName: 'Cognitive Services OpenAI Contributor'
827-
}
828-
{
829-
principalId: deployingUserPrincipalId
830-
principalType: deployingUserType
831-
roleDefinitionIdOrName: 'Cognitive Services User'
832-
}
833-
]
834-
// Remove networking configuration to avoid AML workspace creation issues
835-
networking: enablePrivateNetworking? {
836-
aiServicesPrivateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.aiServices]!.outputs.resourceId
837-
openAiPrivateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.openAI]!.outputs.resourceId
838-
cognitiveServicesPrivateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId
839-
} : null
840-
}
841-
// Disable private endpoints temporarily to fix AML workspace issue
842-
privateEndpointSubnetResourceId: enablePrivateNetworking ? virtualNetwork!.outputs.backendSubnetResourceId : null
843-
// Only attempt model deployment when explicitly enabled to avoid AccountIsNotSucceeded failures due to quota or model availability.
844-
aiModelDeployments: [
802+
tags: allTags
803+
sku: 'S0'
804+
kind: 'AIServices'
805+
disableLocalAuth: true
806+
allowProjectManagement: true
807+
customSubDomainName: aiFoundryAiServicesResourceName
808+
deployments: [
845809
{
846810
name: aiModelDeploymentName
847811
model: {
@@ -855,8 +819,102 @@ module aiFoundry 'br/public:avm/ptn/ai-ml/ai-foundry:0.4.0' = if(!useExistingAiF
855819
}
856820
}
857821
]
822+
networkAcls: {
823+
defaultAction: 'Allow'
824+
virtualNetworkRules: []
825+
ipRules: []
826+
}
827+
managedIdentities: {
828+
systemAssigned: true
829+
userAssignedResourceIds: [appIdentity.outputs.resourceId]
830+
}
831+
roleAssignments: [
832+
// Service Principal permissions
833+
{
834+
roleDefinitionIdOrName: 'Cognitive Services OpenAI Contributor'
835+
principalId: appIdentity.outputs.principalId
836+
principalType: 'ServicePrincipal'
837+
}
838+
{
839+
roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer
840+
principalId: appIdentity.outputs.principalId
841+
principalType: 'ServicePrincipal'
842+
}
843+
{
844+
roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User
845+
principalId: appIdentity.outputs.principalId
846+
principalType: 'ServicePrincipal'
847+
}
848+
// Deployer permissions for local debugging
849+
{
850+
roleDefinitionIdOrName: 'Cognitive Services OpenAI Contributor'
851+
principalId: deployingUserPrincipalId
852+
principalType: deployingUserType
853+
}
854+
{
855+
roleDefinitionIdOrName: 'Cognitive Services User'
856+
principalId: deployingUserPrincipalId
857+
principalType: deployingUserType
858+
}
859+
]
860+
// WAF aligned configuration for Monitoring
861+
diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null
862+
publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled'
863+
// Private endpoints are deployed separately via the aiFoundryPrivateEndpoint module below
864+
privateEndpoints: []
865+
enableTelemetry: enableTelemetry
866+
}
867+
}
868+
869+
// ========== AI Foundry Private Endpoint ========== //
870+
module aiFoundryPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.8.1' = if (enablePrivateNetworking && !useExistingAiFoundryAiProject) {
871+
name: take('pep-${aiFoundryAiServicesResourceName}-deployment', 64)
872+
params: {
873+
name: 'pep-${aiFoundryAiServicesResourceName}'
874+
customNetworkInterfaceName: 'nic-${aiFoundryAiServicesResourceName}'
875+
location: solutionLocation
858876
tags: allTags
859877
enableTelemetry: enableTelemetry
878+
privateLinkServiceConnections: [
879+
{
880+
name: 'pep-${aiFoundryAiServicesResourceName}-connection'
881+
properties: {
882+
privateLinkServiceId: aiFoundryAiServices!.outputs.resourceId
883+
groupIds: ['account']
884+
}
885+
}
886+
]
887+
privateDnsZoneGroup: {
888+
privateDnsZoneGroupConfigs: [
889+
{
890+
name: 'ai-services-dns-zone-cognitiveservices'
891+
privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.cognitiveServices]!.outputs.resourceId
892+
}
893+
{
894+
name: 'ai-services-dns-zone-openai'
895+
privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.openAI]!.outputs.resourceId
896+
}
897+
{
898+
name: 'ai-services-dns-zone-aiservices'
899+
privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.aiServices]!.outputs.resourceId
900+
}
901+
]
902+
}
903+
subnetResourceId: virtualNetwork!.outputs.backendSubnetResourceId
904+
}
905+
}
906+
907+
// ========== AI Foundry Project ========== //
908+
module aiFoundryProject 'modules/ai-project.bicep' = if (!useExistingAiFoundryAiProject) {
909+
name: take('module.ai-project.${aiFoundryAiProjectResourceName}', 64)
910+
dependsOn: enablePrivateNetworking ? [aiFoundryPrivateEndpoint] : []
911+
params: {
912+
name: aiFoundryAiProjectResourceName
913+
location: azureAiServiceLocation
914+
tags: tags
915+
desc: aiFoundryAiProjectDescription
916+
//Implicit dependencies below
917+
aiServicesName: aiFoundryAiServices!.outputs.name
860918
}
861919
}
862920

@@ -976,7 +1034,7 @@ module appConfiguration 'br/public:avm/res/app-configuration/configuration-store
9761034
publicNetworkAccess: 'Enabled'
9771035
}
9781036
// Add explicit dependency
979-
dependsOn: useExistingAiFoundryAiProject ? [] : [aiFoundry]
1037+
dependsOn: useExistingAiFoundryAiProject ? [] : [aiFoundryAiServices]
9801038
}
9811039

9821040
module avmAppConfigUpdated 'br/public:avm/res/app-configuration/configuration-store:0.6.3' = if (enablePrivateNetworking) {

0 commit comments

Comments
 (0)