Skip to content

Commit d83f7b0

Browse files
Merge pull request #500 from microsoft/psl-fix-post-deployment-script
fix: post deployment script
2 parents 8494f46 + 5eb8592 commit d83f7b0

File tree

3 files changed

+38
-10
lines changed

3 files changed

+38
-10
lines changed

docs/TroubleShootingSteps.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Use these as quick reference guides to unblock your deployments.
2727
| **InternalSubscriptionIsOverQuotaForSku** | Subscription quota exceeded for the requested SKU | [View Solution](#quota--capacity-limitations) |
2828
| **InvalidResourceGroup** | Invalid resource group configuration | [View Solution](#resource-group--deployment-management) |
2929
| **RequestDisallowedByPolicy** | Azure Policy blocking the requested operation | [View Solution](#subscription--access-issues) |
30+
| **403 Forbidden - Content Understanding** | Content Understanding returns 403 in WAF/private networking deployment | [View Solution](#network--infrastructure-configuration) |
3031

3132
## 📖 Table of Contents
3233

@@ -127,6 +128,7 @@ Use these as quick reference guides to unblock your deployments.
127128
| **RouteTableCannotBeAttachedForAzureBastionSubnet** | Route table attached to Azure Bastion subnet | This error occurs because Azure Bastion subnet (`AzureBastionSubnet`) has a platform restriction that prevents route tables from being attached.<br><br>**How to reproduce:**<br><ul><li>In `virtualNetwork.bicep`, add `attachRouteTable: true` to the `AzureBastionSubnet` configuration:<br>`{ name: 'AzureBastionSubnet', addressPrefixes: ['10.0.10.0/26'], attachRouteTable: true }`</li><li>Add a Route Table module to the template</li><li>Update subnet creation to attach route table conditionally:<br>`routeTableResourceId: subnet.?attachRouteTable == true ? routeTable.outputs.resourceId : null`</li><li>Deploy the template → Azure throws `RouteTableCannotBeAttachedForAzureBastionSubnet`</li></ul><br>**Resolution:**<br><ul><li>Remove the `attachRouteTable: true` flag from `AzureBastionSubnet` configuration</li><li>Ensure no route table is associated with `AzureBastionSubnet`</li><li>Route tables can only be attached to other subnets, not `AzureBastionSubnet`</li><li>For more details, refer to [Azure Bastion subnet requirements](https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet)</li></ul> |
128129
| **VMSizeIsNotPermittedToEnableAcceleratedNetworking** | VM size does not support accelerated networking | This error occurs when you attempt to enable accelerated networking on a VM size that does not support it. This deployment's jumpbox VM **requires** accelerated networking.<br><br>**Default VM size:** `Standard_D2s_v5` — supports accelerated networking.<br><br>**How this error happens:**<br><ul><li>You override the VM size (via `AZURE_ENV_VM_SIZE`) with a size that doesn't support accelerated networking (e.g., `Standard_A2m_v2`, A-series, or B-series VMs)</li><li>Azure rejects the deployment with `VMSizeIsNotPermittedToEnableAcceleratedNetworking`</li></ul><br>**Resolution:**<br><ul><li>Use the default `Standard_D2s_v5` (recommended)</li><li>If overriding VM size, choose one that supports accelerated networking:<br>`Standard_D2s_v4`, `Standard_D2as_v5` (AMD), `Standard_D2s_v3`</li><li>Verify VM size supports accelerated networking:<br>`az vm list-skus --location <region> --size <vm-size> --query "[?capabilities[?name=='AcceleratedNetworkingEnabled' && value=='True']]"`</li><li>Avoid A-series and B-series VMs — they do not support accelerated networking</li><li>See [VM sizes with accelerated networking](https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-overview)</li></ul> |
129130
| **NetworkSecurityGroupNotCompliantForAzureBastionSubnet** / **SecurityRuleParameterContainsUnsupportedValue** | NSG rules blocking required Azure Bastion ports | This error occurs when the Network Security Group (NSG) attached to `AzureBastionSubnet` explicitly denies inbound TCP ports 443 and/or 4443, which Azure Bastion requires for management and tunneling.<br><br>**How to reproduce:**<br><ul><li>Deploy the template with `enablePrivateNetworking=true` so the virtualNetwork module creates `AzureBastionSubnet` and a Network Security Group that denies ports 443 and 4443</li><li>Attempt to deploy Azure Bastion into that subnet</li><li>During validation, Bastion detects the deny rules and fails with `NetworkSecurityGroupNotCompliantForAzureBastionSubnet`</li></ul><br>**Resolution:**<br><ul><li>**Remove or modify deny rules** for ports 443 and 4443 in the NSG attached to `AzureBastionSubnet`</li><li>**Ensure required inbound rules** per [Azure Bastion NSG requirements](https://learn.microsoft.com/en-us/azure/bastion/bastion-nsg)</li><li>**Use Bicep conditions** to skip NSG attachments for `AzureBastionSubnet` if deploying Bastion</li><li>**Validate the NSG configuration** before deploying Bastion into the subnet</li></ul> |
131+
| **403 Forbidden - Content Understanding** | Azure AI Content Understanding returns 403 Forbidden in WAF (private networking) deployment | This error occurs when the **Azure AI Content Understanding** service returns a `403 Forbidden` response during document processing in a **WAF-enabled (private networking)** deployment.<br><br>**Why this happens:**<br>In WAF deployments (`enablePrivateNetworking=true`), the Content Understanding AI Services account (`aicu-<suffix>`) is configured with `publicNetworkAccess: Disabled`. All traffic must flow through the **private endpoint** (`pep-aicu-<suffix>`) and resolve via private DNS zones (`privatelink.cognitiveservices.azure.com`, `privatelink.services.ai.azure.com`, `privatelink.contentunderstanding.ai.azure.com`). If any part of this chain is misconfigured, the request either reaches the public endpoint (which is blocked) or fails to route entirely, resulting in a 403.<br><br>**Common causes:**<br><ul><li>Private DNS zones not linked to the VNet — DNS resolution falls back to the public IP, which is blocked</li><li>Private endpoint connection is not in **Approved** state</li><li>Content Understanding is deployed in a different region (`contentUnderstandingLocation`, defaults to `WestUS`) than the main deployment — the private endpoint still works cross-region, but DNS misconfiguration is more likely</li><li>Container Apps are not injected into the VNet or are on a subnet that cannot reach the private endpoint</li><li>Managed Identity used by the Container App does not have the required **Cognitive Services User** role on the Content Understanding resource</li></ul><br>**Resolution:**<br><ul><li>**Verify private endpoint status:**<br>`az network private-endpoint show --name pep-aicu-<suffix> --resource-group <rg-name> --query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status"`<br>Expected: `Approved`</li><li>**Verify private DNS zone VNet links:**<br>`az network private-dns zone list --resource-group <rg-name> -o table`<br>Ensure `privatelink.cognitiveservices.azure.com`, `privatelink.services.ai.azure.com`, and `privatelink.contentunderstanding.ai.azure.com` all have VNet links</li><li>**Test DNS resolution from the jumpbox VM** (inside the VNet):<br>`nslookup aicu-<suffix>.cognitiveservices.azure.com`<br>Should resolve to a private IP (e.g., `10.x.x.x`), NOT a public IP</li><li>**Verify RBAC role assignments:** Ensure the Container App managed identity has **Cognitive Services User** role on the Content Understanding resource:<br>`az role assignment list --scope /subscriptions/<sub-id>/resourceGroups/<rg-name>/providers/Microsoft.CognitiveServices/accounts/aicu-<suffix> --query "[?roleDefinitionName=='Cognitive Services User']" -o table`</li><li>**Check Container App VNet integration:** Confirm the Container App Environment is deployed into the VNet and can reach the backend subnet where the private endpoint resides</li><li>**Redeploy if needed:**<br>`azd up`</li></ul><br>**Reference:**<br><ul><li>[Configure private endpoints for Azure AI Services](https://learn.microsoft.com/en-us/azure/ai-services/cognitive-services-virtual-networks)</li><li>[Azure Private DNS zones](https://learn.microsoft.com/en-us/azure/dns/private-dns-overview)</li></ul> |
130132

131133
---------------------------------
132134

infra/main.bicep

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,5 +1890,8 @@ output CONTAINER_REGISTRY_NAME string = avmContainerRegistry.outputs.name
18901890
@description('The login server of the Azure Container Registry.')
18911891
output CONTAINER_REGISTRY_LOGIN_SERVER string = avmContainerRegistry.outputs.loginServer
18921892

1893+
@description('The name of the Content Understanding AI Services account.')
1894+
output CONTENT_UNDERSTANDING_ACCOUNT_NAME string = avmAiServices_cu.outputs.name
1895+
18931896
@description('The resource group the resources were deployed into.')
18941897
output AZURE_RESOURCE_GROUP string = resourceGroup().name

infra/scripts/post_deployment.sh

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ else
9898

9999
# Read schema entries from manifest
100100
SCHEMA_COUNT=$(cat "$SCHEMA_INFO_FILE" | grep -o '"File"' | wc -l)
101-
REGISTERED_IDS=""
102-
REGISTERED_NAMES=""
101+
REGISTERED_IDS=()
102+
REGISTERED_NAMES=()
103103

104104
for idx in $(seq 0 $((SCHEMA_COUNT - 1))); do
105105
# Parse entry fields using grep/sed (no python needed)
@@ -128,8 +128,8 @@ else
128128

129129
if [ -n "$EXISTING_ID" ]; then
130130
echo " Schema '$CLASS_NAME' already exists with ID: $EXISTING_ID"
131-
REGISTERED_IDS="$REGISTERED_IDS $EXISTING_ID"
132-
REGISTERED_NAMES="$REGISTERED_NAMES $CLASS_NAME"
131+
REGISTERED_IDS+=("$EXISTING_ID")
132+
REGISTERED_NAMES+=("$CLASS_NAME")
133133
continue
134134
fi
135135

@@ -148,8 +148,8 @@ else
148148
if [ "$HTTP_CODE" = "200" ]; then
149149
SCHEMA_ID=$(echo "$BODY" | sed 's/.*"Id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
150150
echo " Successfully registered: $DESCRIPTION's Schema Id - $SCHEMA_ID"
151-
REGISTERED_IDS="$REGISTERED_IDS $SCHEMA_ID"
152-
REGISTERED_NAMES="$REGISTERED_NAMES $CLASS_NAME"
151+
REGISTERED_IDS+=("$SCHEMA_ID")
152+
REGISTERED_NAMES+=("$CLASS_NAME")
153153
else
154154
echo " Failed to upload '$FILE_NAME'. HTTP Status: $HTTP_CODE"
155155
echo " Error Response: $BODY"
@@ -205,10 +205,9 @@ else
205205
ALREADY_IN_SET=$(curl -s "${SCHEMASETVAULT_URL}${SCHEMASET_ID}/schemas" 2>/dev/null || echo "[]")
206206

207207
# Iterate over registered schemas
208-
IDX=0
209-
for SCHEMA_ID in $REGISTERED_IDS; do
210-
IDX=$((IDX + 1))
211-
CLASS_NAME=$(echo "$REGISTERED_NAMES" | tr ' ' '\n' | sed -n "${IDX}p")
208+
for i in "${!REGISTERED_IDS[@]}"; do
209+
SCHEMA_ID="${REGISTERED_IDS[$i]}"
210+
CLASS_NAME="${REGISTERED_NAMES[$i]}"
212211

213212
if echo "$ALREADY_IN_SET" | grep -q "\"Id\"[[:space:]]*:[[:space:]]*\"$SCHEMA_ID\""; then
214213
echo " Schema '$CLASS_NAME' ($SCHEMA_ID) already in schema set - skipped"
@@ -236,5 +235,29 @@ else
236235
echo ""
237236
echo "============================================================"
238237
echo "Schema registration process completed."
238+
echo " Schemas registered: ${#REGISTERED_IDS[@]}"
239239
echo "============================================================"
240240
fi
241+
242+
# --- Refresh Content Understanding Cognitive Services account ---
243+
echo ""
244+
echo "============================================================"
245+
echo "Refreshing Content Understanding Cognitive Services account..."
246+
echo "============================================================"
247+
248+
CU_ACCOUNT_NAME=$(azd env get-value CONTENT_UNDERSTANDING_ACCOUNT_NAME 2>/dev/null || echo "")
249+
250+
if [ -z "$CU_ACCOUNT_NAME" ]; then
251+
echo " ⚠️ CONTENT_UNDERSTANDING_ACCOUNT_NAME not found in azd env. Skipping refresh."
252+
else
253+
echo " Refreshing account: $CU_ACCOUNT_NAME in resource group: $RESOURCE_GROUP"
254+
if az cognitiveservices account update \
255+
-g "$RESOURCE_GROUP" \
256+
-n "$CU_ACCOUNT_NAME" \
257+
--tags refresh=true \
258+
--output none; then
259+
echo " ✅ Successfully refreshed Cognitive Services account '$CU_ACCOUNT_NAME'."
260+
else
261+
echo " ❌ Failed to refresh Cognitive Services account '$CU_ACCOUNT_NAME'."
262+
fi
263+
fi

0 commit comments

Comments
 (0)