From 9b5d2d3ae22e00d39a7997737ef2f395ffa43a11 Mon Sep 17 00:00:00 2001 From: Prachig-Microsoft Date: Mon, 3 Aug 2026 21:08:26 +0530 Subject: [PATCH 1/5] post deployment script update --- infra/scripts/post_deployment.ps1 | 246 ++++++++++++++++++++++++++---- 1 file changed, 213 insertions(+), 33 deletions(-) diff --git a/infra/scripts/post_deployment.ps1 b/infra/scripts/post_deployment.ps1 index 4ed40a8e..0002d852 100644 --- a/infra/scripts/post_deployment.ps1 +++ b/infra/scripts/post_deployment.ps1 @@ -1,25 +1,158 @@ +<# +.SYNOPSIS + Post-deployment script for Content Processing Solution Accelerator. + +.DESCRIPTION + Supports both AVM deployment (with parameters) and azd deployment (with env file). + +.PARAMETER ResourceGroupName + Azure resource group name containing the deployed resources (required for AVM deployment). + +.PARAMETER ApiBaseUrl + Base URL of the API container app (optional - will be auto-discovered if not provided). + +.PARAMETER SubscriptionId + Azure subscription ID (optional - will use current az context if not provided). + +.PARAMETER ContentUnderstandingAccountName + Name of the Content Understanding (AI Services) account to refresh (optional - will be auto-discovered if not provided). + +.EXAMPLE + # AVM deployment with parameters + .\post_deployment.ps1 -ResourceGroupName "my-rg" -ApiBaseUrl "https://my-api.azurecontainerapps.io" + +.EXAMPLE + # AVM deployment with auto-discovery + .\post_deployment.ps1 -ResourceGroupName "my-rg" + +.EXAMPLE + # AVM deployment with specific AI Services account + .\post_deployment.ps1 -ResourceGroupName "my-rg" -ContentUnderstandingAccountName "aicu-myaccount" + +.EXAMPLE + # Traditional azd deployment (uses azd env) + .\post_deployment.ps1 +#> + +param( + [Parameter(Mandatory=$false)] + [string]$ResourceGroupName, + + [Parameter(Mandatory=$false)] + [string]$ApiBaseUrl, + + [Parameter(Mandatory=$false)] + [string]$SubscriptionId, + + [Parameter(Mandatory=$false)] + [string]$ContentUnderstandingAccountName +) + # Stop script on any error $ErrorActionPreference = "Stop" -Write-Host "[Search] Fetching container app info from azd environment..." +# Determine deployment mode: AVM (with parameters) or AZD (with env file) +$IsAvmDeployment = -not [string]::IsNullOrEmpty($ResourceGroupName) + +if ($IsAvmDeployment) { + Write-Host "[Info] Running in AVM deployment mode with resource group: $ResourceGroupName" + + # Get subscription ID from parameter or current context + if ([string]::IsNullOrEmpty($SubscriptionId)) { + $SUBSCRIPTION_ID = (az account show --query id -o tsv 2>$null) + if ([string]::IsNullOrEmpty($SUBSCRIPTION_ID)) { + Write-Host "[Error] Could not determine subscription ID. Please provide -SubscriptionId parameter or ensure you are logged in with 'az login'." + exit 1 + } + Write-Host "[Info] Using subscription ID from current context: $SUBSCRIPTION_ID" + } else { + $SUBSCRIPTION_ID = $SubscriptionId + } + + $RESOURCE_GROUP = $ResourceGroupName + + # Discover container apps in the resource group + Write-Host "[Info] Discovering container apps in resource group..." + $ContainerApps = @(az containerapp list -g $RESOURCE_GROUP --query "[].{name:name, fqdn:properties.configuration.ingress.fqdn}" -o json 2>$null | ConvertFrom-Json) + + if ($ContainerApps.Count -eq 0) { + Write-Host "[Error] No container apps found in resource group '$RESOURCE_GROUP'." + exit 1 + } + + # Identify apps by name patterns (matching AVM naming convention) + $CONTAINER_API_APP = $ContainerApps | Where-Object { $_.name -like "*-api" } | Select-Object -First 1 + $CONTAINER_WEB_APP = $ContainerApps | Where-Object { $_.name -like "*-web" } | Select-Object -First 1 + # Try multiple patterns for workflow app (AVM uses -wkfl) + $CONTAINER_WORKFLOW_APP = $ContainerApps | Where-Object { $_.name -like "*-wkfl" -or $_.name -like "*-workflow" -or $_.name -like "*-processor" -or $_.name -like "*-worker" } | Select-Object -First 1 + # Try to find the base ContentProcessor app + $CONTAINER_APP = $ContainerApps | Where-Object { $_.name -like "*-app" } | Select-Object -First 1 + + # Safely extract properties with null checks + $CONTAINER_APP_NAME = if ($CONTAINER_APP) { $CONTAINER_APP.name } else { $null } + $CONTAINER_APP_FQDN = if ($CONTAINER_APP) { $CONTAINER_APP.fqdn } else { $null } + $CONTAINER_API_APP_NAME = if ($CONTAINER_API_APP) { $CONTAINER_API_APP.name } else { $null } + $CONTAINER_API_APP_FQDN = if ($CONTAINER_API_APP) { $CONTAINER_API_APP.fqdn } else { $null } + $CONTAINER_WEB_APP_NAME = if ($CONTAINER_WEB_APP) { $CONTAINER_WEB_APP.name } else { $null } + $CONTAINER_WEB_APP_FQDN = if ($CONTAINER_WEB_APP) { $CONTAINER_WEB_APP.fqdn } else { $null } + $CONTAINER_WORKFLOW_APP_NAME = if ($CONTAINER_WORKFLOW_APP) { $CONTAINER_WORKFLOW_APP.name } else { $null } + + # Use provided API base URL or construct from discovered FQDN + if (-not [string]::IsNullOrEmpty($ApiBaseUrl)) { + # Remove trailing slash if present + $ApiBaseUrl = $ApiBaseUrl.TrimEnd('/') + Write-Host "[Info] Using provided API base URL: $ApiBaseUrl" + } elseif (-not [string]::IsNullOrEmpty($CONTAINER_API_APP_FQDN)) { + $ApiBaseUrl = "https://$CONTAINER_API_APP_FQDN" + Write-Host "[Info] Constructed API base URL from discovered FQDN: $ApiBaseUrl" + } else { + Write-Host "[Error] Could not determine API base URL. Please provide -ApiBaseUrl parameter or ensure API container app exists." + exit 1 + } + +} else { + Write-Host "[Info] Running in AZD deployment mode (using azd env)..." + + # Load values from azd env + $CONTAINER_WEB_APP_NAME = azd env get-value CONTAINER_WEB_APP_NAME + $CONTAINER_WEB_APP_FQDN = azd env get-value CONTAINER_WEB_APP_FQDN + + $CONTAINER_API_APP_NAME = azd env get-value CONTAINER_API_APP_NAME + $CONTAINER_API_APP_FQDN = azd env get-value CONTAINER_API_APP_FQDN + + $CONTAINER_WORKFLOW_APP_NAME = azd env get-value CONTAINER_WORKFLOW_APP_NAME + + # Get subscription and resource group (assuming same for both) + $SUBSCRIPTION_ID = azd env get-value AZURE_SUBSCRIPTION_ID + $RESOURCE_GROUP = azd env get-value AZURE_RESOURCE_GROUP -# Load values from azd env -$CONTAINER_WEB_APP_NAME = azd env get-value CONTAINER_WEB_APP_NAME -$CONTAINER_WEB_APP_FQDN = azd env get-value CONTAINER_WEB_APP_FQDN + $ApiBaseUrl = "https://$CONTAINER_API_APP_FQDN" +} -$CONTAINER_API_APP_NAME = azd env get-value CONTAINER_API_APP_NAME -$CONTAINER_API_APP_FQDN = azd env get-value CONTAINER_API_APP_FQDN +# Construct Azure Portal URLs (only for apps that exist) +if ($CONTAINER_APP_NAME) { + $APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_APP_NAME" +} else { + $APP_PORTAL_URL = $null +} -$CONTAINER_WORKFLOW_APP_NAME = azd env get-value CONTAINER_WORKFLOW_APP_NAME +if ($CONTAINER_WEB_APP_NAME) { + $WEB_APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_WEB_APP_NAME" +} else { + $WEB_APP_PORTAL_URL = $null +} -# Get subscription and resource group (assuming same for both) -$SUBSCRIPTION_ID = azd env get-value AZURE_SUBSCRIPTION_ID -$RESOURCE_GROUP = azd env get-value AZURE_RESOURCE_GROUP +if ($CONTAINER_API_APP_NAME) { + $API_APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_API_APP_NAME" +} else { + $API_APP_PORTAL_URL = $null +} -# Construct Azure Portal URLs -$WEB_APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_WEB_APP_NAME" -$API_APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_API_APP_NAME" -$WORKFLOW_APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_WORKFLOW_APP_NAME" +if ($CONTAINER_WORKFLOW_APP_NAME) { + $WORKFLOW_APP_PORTAL_URL = "https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.App/containerApps/$CONTAINER_WORKFLOW_APP_NAME" +} else { + $WORKFLOW_APP_PORTAL_URL = $null +} # Get the current script's directory $ScriptDir = $PSScriptRoot @@ -30,31 +163,62 @@ $DataScriptPath = Join-Path $ScriptDir "..\..\src\ContentProcessorAPI\samples\sc # Resolve to an absolute path $FullPath = Resolve-Path $DataScriptPath -# Output +# Output deployment information +Write-Host "" +Write-Host "[Info] Content Processor App Details:" +if ($CONTAINER_APP_NAME) { + Write-Host " [OK] Name: $CONTAINER_APP_NAME" + if ($CONTAINER_APP_FQDN) { + Write-Host " [URL] Endpoint: $CONTAINER_APP_FQDN" + } + if ($APP_PORTAL_URL) { + Write-Host " [Link] Portal URL: $APP_PORTAL_URL" + } +} else { + Write-Host " [Info] Content Processor app not found or not deployed." +} + Write-Host "" Write-Host "[Info] Web App Details:" -Write-Host " [OK] Name: $CONTAINER_WEB_APP_NAME" -Write-Host " [URL] Endpoint: $CONTAINER_WEB_APP_FQDN" -Write-Host " [Link] Portal URL: $WEB_APP_PORTAL_URL" +if ($CONTAINER_WEB_APP_NAME) { + Write-Host " [OK] Name: $CONTAINER_WEB_APP_NAME" + Write-Host " [URL] Endpoint: $CONTAINER_WEB_APP_FQDN" + if ($WEB_APP_PORTAL_URL) { + Write-Host " [Link] Portal URL: $WEB_APP_PORTAL_URL" + } +} else { + Write-Host " [Info] Web app not found or not deployed." +} Write-Host "" Write-Host "[Info] API App Details:" -Write-Host " [OK] Name: $CONTAINER_API_APP_NAME" -Write-Host " [URL] Endpoint: $CONTAINER_API_APP_FQDN" -Write-Host " [Link] Portal URL: $API_APP_PORTAL_URL" +if ($CONTAINER_API_APP_NAME) { + Write-Host " [OK] Name: $CONTAINER_API_APP_NAME" + Write-Host " [URL] Endpoint: $CONTAINER_API_APP_FQDN" + if ($API_APP_PORTAL_URL) { + Write-Host " [Link] Portal URL: $API_APP_PORTAL_URL" + } +} else { + Write-Host " [Info] API app not found or not deployed." +} Write-Host "" Write-Host "[Info] Workflow App Details:" -Write-Host " [OK] Name: $CONTAINER_WORKFLOW_APP_NAME" -Write-Host " [Link] Portal URL: $WORKFLOW_APP_PORTAL_URL" +if ($CONTAINER_WORKFLOW_APP_NAME) { + Write-Host " [OK] Name: $CONTAINER_WORKFLOW_APP_NAME" + if ($WORKFLOW_APP_PORTAL_URL) { + Write-Host " [Link] Portal URL: $WORKFLOW_APP_PORTAL_URL" + } +} else { + Write-Host " [Info] Workflow app not found or not deployed." +} Write-Host "" Write-Host "[Package] Registering schemas and creating schema set..." -Write-Host " [Wait] Waiting for API to be ready..." +Write-Host " [Wait] Waiting for API to be ready at: $ApiBaseUrl" $MaxRetries = 10 $RetryInterval = 15 -$ApiBaseUrl = "https://$CONTAINER_API_APP_FQDN" $ApiReady = $false for ($i = 1; $i -le $MaxRetries; $i++) { @@ -247,11 +411,20 @@ Write-Host "Refreshing Content Understanding Cognitive Services account..." Write-Host ("=" * 60) $CU_ACCOUNT_NAME = "" -try { - $CU_ACCOUNT_NAME = (azd env get-value CONTENT_UNDERSTANDING_ACCOUNT_NAME 2>$null) - if (-not $CU_ACCOUNT_NAME) { $CU_ACCOUNT_NAME = "" } -} catch { - $CU_ACCOUNT_NAME = "" +if ($IsAvmDeployment) { + # In AVM mode, use parameter if provided + if (-not [string]::IsNullOrEmpty($ContentUnderstandingAccountName)) { + $CU_ACCOUNT_NAME = $ContentUnderstandingAccountName + Write-Host " Using specified Content Understanding account: $CU_ACCOUNT_NAME" + } +} else { + # In AZD mode, try to get from azd env + try { + $CU_ACCOUNT_NAME = (azd env get-value CONTENT_UNDERSTANDING_ACCOUNT_NAME 2>$null) + if (-not $CU_ACCOUNT_NAME) { $CU_ACCOUNT_NAME = "" } + } catch { + $CU_ACCOUNT_NAME = "" + } } # Verify the account from the env value still exists; if not, fall back to discovering @@ -287,11 +460,17 @@ if (-not $CU_ACCOUNT_NAME) { if ($CuAccounts.Count -eq 1) { $CU_ACCOUNT_NAME = $CuAccounts[0] Write-Host " Discovered AIServices account in resource group: $CU_ACCOUNT_NAME" - # Refresh the azd env so subsequent runs use the correct value. - try { azd env set CONTENT_UNDERSTANDING_ACCOUNT_NAME $CU_ACCOUNT_NAME 2>$null | Out-Null } catch { } + # Refresh the azd env so subsequent runs use the correct value (only in AZD mode) + if (-not $IsAvmDeployment) { + try { azd env set CONTENT_UNDERSTANDING_ACCOUNT_NAME $CU_ACCOUNT_NAME 2>$null | Out-Null } catch { } + } } elseif ($CuAccounts.Count -gt 1) { Write-Host " [Warn] Multiple AIServices accounts found in resource group '$RESOURCE_GROUP': $($CuAccounts -join ', ')" - Write-Host " Please set CONTENT_UNDERSTANDING_ACCOUNT_NAME in azd env to the correct account name. Skipping refresh." + if ($IsAvmDeployment) { + Write-Host " Please specify the correct account name manually. Skipping refresh." + } else { + Write-Host " Please set CONTENT_UNDERSTANDING_ACCOUNT_NAME in azd env to the correct account name. Skipping refresh." + } } else { Write-Host " [Warn] No Content Understanding (AIServices) account found in resource group '$RESOURCE_GROUP'. Skipping refresh." } @@ -310,3 +489,4 @@ if ($CU_ACCOUNT_NAME) { Write-Host " az error: $UpdateOutputStr" } } + \ No newline at end of file From 1cac94775bc14b662f8e9c738941a74887acd2d9 Mon Sep 17 00:00:00 2001 From: Prachig-Microsoft Date: Mon, 3 Aug 2026 22:17:36 +0530 Subject: [PATCH 2/5] fix(post-deploy): harden API readiness check for AVM deployment flow - Add -MaxApiRetries (default 20) and -ApiRetryIntervalSeconds (default 15) parameters, giving a 5-minute default wait budget instead of 2.5 minutes. This accounts for the container app pulling a freshly-built image right after acr_build_push.ps1 and passing its startup probe before schema registration is attempted. - On readiness failure, print the last HTTP/connection error plus diagnostics (az containerapp revision list status/replicas and recent az containerapp logs show console output) instead of silently skipping, making failures actionable. - Build the schema-upload multipart body as raw bytes (MemoryStream) instead of round-tripping file bytes through UTF8.GetString, avoiding potential corruption of non-ASCII schema content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- infra/scripts/post_deployment.ps1 | 101 ++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 18 deletions(-) diff --git a/infra/scripts/post_deployment.ps1 b/infra/scripts/post_deployment.ps1 index 0002d852..b9e1ba42 100644 --- a/infra/scripts/post_deployment.ps1 +++ b/infra/scripts/post_deployment.ps1 @@ -17,6 +17,15 @@ .PARAMETER ContentUnderstandingAccountName Name of the Content Understanding (AI Services) account to refresh (optional - will be auto-discovered if not provided). +.PARAMETER MaxApiRetries + Number of attempts to poll the API readiness endpoint before giving up (default: 20). + +.PARAMETER ApiRetryIntervalSeconds + Seconds to wait between API readiness polling attempts (default: 15). Combined with + MaxApiRetries this gives a default total wait budget of 5 minutes, which allows for the + container app to pull a freshly-built image (e.g. right after acr_build_push.ps1) and pass + its startup probe before schema registration is attempted. + .EXAMPLE # AVM deployment with parameters .\post_deployment.ps1 -ResourceGroupName "my-rg" -ApiBaseUrl "https://my-api.azurecontainerapps.io" @@ -45,7 +54,13 @@ param( [string]$SubscriptionId, [Parameter(Mandatory=$false)] - [string]$ContentUnderstandingAccountName + [string]$ContentUnderstandingAccountName, + + [Parameter(Mandatory=$false)] + [int]$MaxApiRetries = 20, + + [Parameter(Mandatory=$false)] + [int]$ApiRetryIntervalSeconds = 15 ) # Stop script on any error @@ -217,9 +232,10 @@ Write-Host "" Write-Host "[Package] Registering schemas and creating schema set..." Write-Host " [Wait] Waiting for API to be ready at: $ApiBaseUrl" -$MaxRetries = 10 -$RetryInterval = 15 +$MaxRetries = $MaxApiRetries +$RetryInterval = $ApiRetryIntervalSeconds $ApiReady = $false +$LastError = $null for ($i = 1; $i -le $MaxRetries; $i++) { try { @@ -228,17 +244,47 @@ for ($i = 1; $i -le $MaxRetries; $i++) { Write-Host " [OK] API is ready." $ApiReady = $true break + } else { + $LastError = "HTTP status $($response.StatusCode)" } } catch { - # Ignore - API not ready yet + # Capture the error so we can surface it if the API never becomes ready. + # This turns "silently skip" into an actionable diagnostic (e.g. 502/503 + # from ingress while the new revision is still starting, DNS failures, + # or TLS/cert errors) instead of leaving the user to guess. + $LastError = $_.Exception.Message } - Write-Host " Attempt $i/$MaxRetries - API not ready, retrying in ${RetryInterval}s..." + Write-Host " Attempt $i/$MaxRetries - API not ready ($LastError), retrying in ${RetryInterval}s..." Start-Sleep -Seconds $RetryInterval } if (-not $ApiReady) { - Write-Host " API did not become ready after $MaxRetries attempts. Skipping schema registration." - Write-Host " Run manually after the API is ready." + Write-Host " [Error] API did not become ready after $MaxRetries attempts (interval: ${RetryInterval}s, total wait: $($MaxRetries * $RetryInterval)s)." + Write-Host " Last error: $LastError" + Write-Host " Skipping schema registration. Run manually after the API is ready." + + # Best-effort diagnostics to help root-cause why the container app never + # became reachable (e.g. still pulling the freshly built image, crash-looping + # because required config/RBAC hasn't propagated yet, or provisioning failed). + if ($CONTAINER_API_APP_NAME -and $RESOURCE_GROUP) { + Write-Host "" + Write-Host " [Diag] Container app '$CONTAINER_API_APP_NAME' status in resource group '$RESOURCE_GROUP':" + try { + $RevisionInfo = az containerapp revision list -g $RESOURCE_GROUP -n $CONTAINER_API_APP_NAME ` + --query "[?properties.active].{name:name, provisioningState:properties.provisioningState, runningState:properties.runningState, replicas:properties.replicas, createdTime:properties.createdTime}" ` + -o table 2>&1 + Write-Host ($RevisionInfo | Out-String) + } catch { + Write-Host " [Warn] Could not retrieve revision status: $_" + } + try { + Write-Host " [Diag] Recent console logs (last 50 lines):" + $Logs = az containerapp logs show -g $RESOURCE_GROUP -n $CONTAINER_API_APP_NAME --type console --tail 50 2>&1 + Write-Host ($Logs | Out-String) + } catch { + Write-Host " [Warn] Could not retrieve container logs: $_" + } + } } else { # ---------- Schema registration (no Python dependency) ---------- $SchemaInfoFile = Join-Path $FullPath "schema_info.json" @@ -304,21 +350,40 @@ if (-not $ApiReady) { $boundary = [System.Guid]::NewGuid().ToString() $LF = "`r`n" - $bodyLines = ( - "--$boundary", - "Content-Disposition: form-data; name=`"data`"$LF", - $dataPayload, - "--$boundary", - "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"", - "Content-Type: $contentType$LF", - [System.Text.Encoding]::UTF8.GetString($fileBytes), - "--$boundary--$LF" - ) -join $LF + + # Build the multipart body as raw bytes rather than round-tripping the file + # content through a string. Converting file bytes to a string and back can + # corrupt content that isn't plain ASCII (e.g. UTF-8 BOMs or non-ASCII + # characters in schema descriptions), because Invoke-RestMethod may not + # re-encode a string body as UTF-8. Writing bytes directly avoids this. + $MemoryStream = New-Object System.IO.MemoryStream + try { + $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + $WriteText = { + param($Text) + $bytes = $Utf8NoBom.GetBytes($Text) + $MemoryStream.Write($bytes, 0, $bytes.Length) + } + + & $WriteText "--$boundary$LF" + & $WriteText "Content-Disposition: form-data; name=`"data`"$LF$LF" + & $WriteText "$dataPayload$LF" + + & $WriteText "--$boundary$LF" + & $WriteText "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"$LF" + & $WriteText "Content-Type: $contentType$LF$LF" + $MemoryStream.Write($fileBytes, 0, $fileBytes.Length) + & $WriteText "$LF--$boundary--$LF" + + $bodyBytes = $MemoryStream.ToArray() + } finally { + $MemoryStream.Dispose() + } try { $resp = Invoke-RestMethod -Uri $SchemaVaultUrl -Method POST ` -ContentType "multipart/form-data; boundary=$boundary" ` - -Body $bodyLines -TimeoutSec 60 -ErrorAction Stop + -Body $bodyBytes -TimeoutSec 60 -ErrorAction Stop $schemaId = $resp.Id Write-Host " Successfully registered: $Description's Schema Id - $schemaId" $Registered[$ClassName] = $schemaId From 48145d54e26c73b29bf9709d78652913f972b0a0 Mon Sep 17 00:00:00 2001 From: Prachig-Microsoft Date: Tue, 4 Aug 2026 13:19:38 +0530 Subject: [PATCH 3/5] Harden post_deployment.ps1 with self-healing AVM pre-flight checks Adds pre-flight checks that detect and auto-correct known AVM deployment issues before running schema registration, so the script works reliably against AVM-deployed resource groups (deployed via the bicep-registry ContentProcessingAVM fork) without requiring a .env file: - Storage account publicNetworkAccess: detects Disabled with 0 private endpoints on Non-WAF deployments and re-enables it. - Cosmos DB publicNetworkAccess: same self-healing check for Cosmos DB (real bug in the published avm/res/document-db/database-account module). - Web container app ingressTargetPort: detects when it defaulted to 80 instead of 3000 and corrects it. - API Easy Auth: detects when anonymous schema registration calls are blocked and handles it. - AI Services multi-account auto-selection when more than one aicu-* account exists in the resource group. Also updated the script to accept -ResourceGroupName as a parameter instead of requiring a .env file, since that workflow is needed for AVM registry deployments. Verified end-to-end against live resource groups (pgcp4, pgcp11) deployed via the AVM fork: all pre-flight checks correctly no-op when resources are already healthy, and correctly self-heal when the known AVM bugs are present. Schema and schema set registration confirmed working after each fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- infra/scripts/post_deployment.ps1 | 128 +++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) diff --git a/infra/scripts/post_deployment.ps1 b/infra/scripts/post_deployment.ps1 index b9e1ba42..885da834 100644 --- a/infra/scripts/post_deployment.ps1 +++ b/infra/scripts/post_deployment.ps1 @@ -228,6 +228,118 @@ if ($CONTAINER_WORKFLOW_APP_NAME) { Write-Host " [Info] Workflow app not found or not deployed." } +# ---------- Pre-flight: storage account public network access (AVM mode) ---------- +if ($IsAvmDeployment) { + Write-Host "" + Write-Host "[Check] Verifying storage account network access..." + try { + $RgType = az group show -n $RESOURCE_GROUP --query "tags.Type" -o tsv 2>$null + $StorageAccounts = @(az storage account list -g $RESOURCE_GROUP --query "[].name" -o tsv 2>$null) + $StorageAccounts = @($StorageAccounts | Where-Object { $_ -and $_.Trim() -ne "" }) + + foreach ($SaName in $StorageAccounts) { + $SaInfoJson = az storage account show -g $RESOURCE_GROUP -n $SaName --query "{publicNetworkAccess:publicNetworkAccess, peCount:length(privateEndpointConnections)}" -o json 2>$null + if ([string]::IsNullOrEmpty($SaInfoJson)) { continue } + $SaInfo = $SaInfoJson | ConvertFrom-Json + if ($SaInfo.publicNetworkAccess -eq 'Disabled' -and $SaInfo.peCount -eq 0) { + if ($RgType -eq 'Non-WAF') { + Write-Host " [Warn] Storage account '$SaName' has publicNetworkAccess=Disabled with no private endpoints, but this is a Non-WAF (no private networking) deployment." + Write-Host " This would cause storage-dependent API calls to fail with AuthorizationFailure. Auto-correcting: enabling public network access..." + az storage account update -g $RESOURCE_GROUP -n $SaName --public-network-access Enabled -o none 2>$null + Write-Host " [OK] Public network access enabled on '$SaName'." + } else { + Write-Host " [Warn] Storage account '$SaName' has publicNetworkAccess=Disabled with no private endpoints." + Write-Host " If this resource group was not deployed with private networking/VNet integration, storage-dependent API calls will fail. Verify manually." + } + } + } + } catch { + Write-Host " [Warn] Could not verify storage account network configuration: $($_.Exception.Message)" + } +} + +# ---------- Pre-flight: Cosmos DB public network access (AVM mode) ---------- +if ($IsAvmDeployment) { + Write-Host "" + Write-Host "[Check] Verifying Cosmos DB network access..." + try { + $RgType = az group show -n $RESOURCE_GROUP --query "tags.Type" -o tsv 2>$null + $CosmosAccounts = @(az cosmosdb list -g $RESOURCE_GROUP --query "[].name" -o tsv 2>$null) + $CosmosAccounts = @($CosmosAccounts | Where-Object { $_ -and $_.Trim() -ne "" }) + + foreach ($CosmosName in $CosmosAccounts) { + $CosmosInfoJson = az cosmosdb show -g $RESOURCE_GROUP -n $CosmosName --query "{publicNetworkAccess:publicNetworkAccess, peCount:length(privateEndpointConnections)}" -o json 2>$null + if ([string]::IsNullOrEmpty($CosmosInfoJson)) { continue } + $CosmosInfo = $CosmosInfoJson | ConvertFrom-Json + if ($CosmosInfo.publicNetworkAccess -eq 'Disabled' -and $CosmosInfo.peCount -eq 0) { + if ($RgType -eq 'Non-WAF') { + Write-Host " [Warn] Cosmos DB account '$CosmosName' has publicNetworkAccess=Disabled with no private endpoints, but this is a Non-WAF (no private networking) deployment." + Write-Host " This blocks the API's MongoDB connection with 'Request blocked by network firewall'. Auto-correcting: enabling public network access..." + az cosmosdb update -g $RESOURCE_GROUP -n $CosmosName --public-network-access Enabled -o none 2>$null + Write-Host " [OK] Public network access enabled on '$CosmosName'. Note: Cosmos DB changes can take several minutes to propagate." + } else { + Write-Host " [Warn] Cosmos DB account '$CosmosName' has publicNetworkAccess=Disabled with no private endpoints." + Write-Host " If this resource group was not deployed with private networking/VNet integration, the API's MongoDB connection will fail. Verify manually." + } + } + } + } catch { + Write-Host " [Warn] Could not verify Cosmos DB network configuration: $($_.Exception.Message)" + } +} + +# ---------- Pre-flight: web container app ingress target port check ---------- +$ExpectedWebTargetPort = 3000 +if ($IsAvmDeployment -and $CONTAINER_WEB_APP_NAME) { + Write-Host "" + Write-Host "[Check] Verifying web container app ingress target port..." + try { + $CurrentWebTargetPort = az containerapp show -g $RESOURCE_GROUP -n $CONTAINER_WEB_APP_NAME --query "properties.configuration.ingress.targetPort" -o tsv 2>$null + if ($CurrentWebTargetPort -and $CurrentWebTargetPort -ne "$ExpectedWebTargetPort") { + Write-Host " [Warn] Web app '$CONTAINER_WEB_APP_NAME' ingress target port is $CurrentWebTargetPort, but the web image serves on $ExpectedWebTargetPort." + Write-Host " This causes the revision to get stuck in ActivationFailed once the real web image is deployed. Auto-correcting..." + az containerapp ingress update -g $RESOURCE_GROUP -n $CONTAINER_WEB_APP_NAME --target-port $ExpectedWebTargetPort -o none 2>$null + Write-Host " [OK] Web app ingress target port set to $ExpectedWebTargetPort." + } + } catch { + Write-Host " [Warn] Could not verify/correct web app ingress target port: $($_.Exception.Message)" + } +} + +# ---------- Pre-flight: API container app authentication check ---------- +$ApiAuthOriginalAction = $null +if ($CONTAINER_API_APP_NAME -and $RESOURCE_GROUP) { + Write-Host "" + Write-Host "[Check] Verifying API container app authentication settings..." + try { + $AuthAction = az containerapp auth show -g $RESOURCE_GROUP -n $CONTAINER_API_APP_NAME --query "globalValidation.unauthenticatedClientAction" -o tsv 2>$null + if ($AuthAction -and $AuthAction -ne 'AllowAnonymous') { + Write-Host " [Warn] API container app has authentication enabled (unauthenticatedClientAction=$AuthAction)." + Write-Host " Temporarily allowing anonymous access for schema registration; original setting will be restored afterwards..." + az containerapp auth update -g $RESOURCE_GROUP -n $CONTAINER_API_APP_NAME --unauthenticated-client-action AllowAnonymous -o none 2>$null + $ApiAuthOriginalAction = $AuthAction + # Allow the change to propagate to the running revision before polling (observed ~30-60s). + Start-Sleep -Seconds 30 + } + } catch { + Write-Host " [Warn] Could not verify/adjust API authentication settings: $($_.Exception.Message)" + } +} + +function Restore-ApiAuthSetting { + if ($ApiAuthOriginalAction -and $CONTAINER_API_APP_NAME -and $RESOURCE_GROUP) { + Write-Host "" + Write-Host "[Cleanup] Restoring API container app authentication setting to '$ApiAuthOriginalAction'..." + try { + az containerapp auth update -g $RESOURCE_GROUP -n $CONTAINER_API_APP_NAME --unauthenticated-client-action $ApiAuthOriginalAction -o none 2>$null + Write-Host " [OK] Authentication setting restored." + } catch { + Write-Host " [Warn] Could not restore authentication setting automatically: $($_.Exception.Message)" + Write-Host " Please verify/restore manually: az containerapp auth update -g $RESOURCE_GROUP -n $CONTAINER_API_APP_NAME --unauthenticated-client-action $ApiAuthOriginalAction" + } + } +} + Write-Host "" Write-Host "[Package] Registering schemas and creating schema set..." Write-Host " [Wait] Waiting for API to be ready at: $ApiBaseUrl" @@ -285,6 +397,8 @@ if (-not $ApiReady) { Write-Host " [Warn] Could not retrieve container logs: $_" } } + + Restore-ApiAuthSetting } else { # ---------- Schema registration (no Python dependency) ---------- $SchemaInfoFile = Join-Path $FullPath "schema_info.json" @@ -467,6 +581,8 @@ if (-not $ApiReady) { Write-Host "Schema registration process completed." Write-Host " Schemas registered: $($Registered.Count)" Write-Host ("=" * 60) + + Restore-ApiAuthSetting } # --- Refresh Content Understanding Cognitive Services account --- @@ -531,8 +647,16 @@ if (-not $CU_ACCOUNT_NAME) { } } elseif ($CuAccounts.Count -gt 1) { Write-Host " [Warn] Multiple AIServices accounts found in resource group '$RESOURCE_GROUP': $($CuAccounts -join ', ')" - if ($IsAvmDeployment) { - Write-Host " Please specify the correct account name manually. Skipping refresh." + # Auto-select if exactly one account matches the 'aicu-' naming convention. + $AicuMatches = @($CuAccounts | Where-Object { $_ -like 'aicu-*' }) + if ($AicuMatches.Count -eq 1) { + $CU_ACCOUNT_NAME = $AicuMatches[0] + Write-Host " Auto-selected Content Understanding account by naming convention: $CU_ACCOUNT_NAME" + if (-not $IsAvmDeployment) { + try { azd env set CONTENT_UNDERSTANDING_ACCOUNT_NAME $CU_ACCOUNT_NAME 2>$null | Out-Null } catch { } + } + } elseif ($IsAvmDeployment) { + Write-Host " Please specify the correct account name manually via -ContentUnderstandingAccountName. Skipping refresh." } else { Write-Host " Please set CONTENT_UNDERSTANDING_ACCOUNT_NAME in azd env to the correct account name. Skipping refresh." } From 321c4bd300a4ff498052de5b301827ad1df0a430 Mon Sep 17 00:00:00 2001 From: Prachig-Microsoft Date: Tue, 4 Aug 2026 13:30:21 +0530 Subject: [PATCH 4/5] Update AVM post-deployment documentation to match the current flow docs/AVMPostDeploymentGuide.md was out of date - it described registering schemas via a standalone Python script (register_schema.py) with no ACR build/push step at all. Updated it to reflect the actual required sequence after deploying via the AVM bicep registry module: 1. Build and push container images (infra/scripts/acr_build_push.ps1 "") - the AVM module provisions ACR/Container Apps but does not build or push application images itself. 2. Run infra/scripts/post_deployment.ps1 -ResourceGroupName "" [-ApiBaseUrl ""] to register schemas and create the schema set (replaces the old Python script; ApiBaseUrl is optional thanks to auto-discovery). 3. Configure authentication (unchanged). Also documented the AVM-specific self-healing pre-flight checks the script now runs (storage/Cosmos DB network access, web ingress port, API auth) so users understand what they do. Updated prerequisites to drop the Python/pip requirement (no longer used) and added PowerShell as a requirement instead. Added a short pointer in README.md's Getting Started section so users who deploy via the AVM registry module (rather than azd up) know to follow the AVM Post Deployment Guide instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 2 ++ docs/AVMPostDeploymentGuide.md | 64 +++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index a2af2842..812010c7 100644 --- a/README.md +++ b/README.md @@ -276,6 +276,8 @@ Follow the quick deploy steps on the deployment guide to deploy this solution to [Click here to launch the deployment guide](./docs/DeploymentGuide.md) +> **Deploying via Azure Verified Modules (AVM)?** If you deployed this solution from the [AVM bicep registry module](https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/sa/content-processing) instead of `azd up`, see the [AVM Post Deployment Guide](./docs/AVMPostDeploymentGuide.md) for the required follow-up steps (build/push images, register schemas, configure authentication). + | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/microsoft/content-processing-solution-accelerator) | [![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/content-processing-solution-accelerator) | [![Open in Visual Studio Code Web](https://img.shields.io/static/v1?style=for-the-badge&label=Visual%20Studio%20Code%20(Web)&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvY29udGVudC1wcm9jZXNzaW5nLXNvbHV0aW9uLWFjY2VsZXJhdG9yL3JlZnMvaGVhZHMvbWFpbi9pbmZyYS92c2NvZGVfd2ViIiwgImluZGV4VXJsIjogIi9pbmRleC5qc29uIiwgInZhcmlhYmxlcyI6IHsiYWdlbnRJZCI6ICIiLCAiY29ubmVjdGlvblN0cmluZyI6ICIiLCAidGhyZWFkSWQiOiAiIiwgInVzZXJNZXNzYWdlIjogIiIsICJwbGF5Z3JvdW5kTmFtZSI6ICIiLCAibG9jYXRpb24iOiAiIiwgInN1YnNjcmlwdGlvbklkIjogIiIsICJyZXNvdXJjZUlkIjogIiIsICJwcm9qZWN0UmVzb3VyY2VJZCI6ICIiLCAiZW5kcG9pbnQiOiAiIn0sICJjb2RlUm91dGUiOiBbImFpLXByb2plY3RzLXNkayIsICJweXRob24iLCAiZGVmYXVsdC1henVyZS1hdXRoIiwgImVuZHBvaW50Il19) | |---|---|---| diff --git a/docs/AVMPostDeploymentGuide.md b/docs/AVMPostDeploymentGuide.md index e0a1fe0b..78a20308 100644 --- a/docs/AVMPostDeploymentGuide.md +++ b/docs/AVMPostDeploymentGuide.md @@ -8,12 +8,13 @@ This document provides guidance on post-deployment steps after deploying the Con ## Overview -After successfully deploying the Content Processing Solution Accelerator using the AVM template, you need to: +After successfully deploying the Content Processing Solution Accelerator using the AVM template, you need to, **in this order**: -1. **Register schemas** — upload schema files, create a schema set, and link them together -2. **Configure authentication** — set up app registration for secure access +1. **Build and push container images** — the AVM deployment provisions the Azure Container Registry (ACR) and Container Apps, but does not build/push application images. Run `acr_build_push` so the container apps pick up the real images. +2. **Run the post-deployment script** — registers schemas, creates the schema set, and self-heals a handful of known AVM configuration gaps (see [Notes on AVM-specific pre-flight checks](#notes-on-avm-specific-pre-flight-checks) below). +3. **Configure authentication** — set up app registration for secure access. -> **Note:** When deploying via `azd up`, schema registration happens automatically through a post-provisioning hook. AVM deployments require the manual steps below. +> **Note:** When deploying via `azd up`, image build/push and schema registration both happen automatically through post-provisioning hooks. AVM deployments require the manual steps below because the AVM module doesn't run those hooks. ## Prerequisites @@ -21,19 +22,11 @@ Before starting, ensure you have: ### Required Software -1. **[Python](https://www.python.org/downloads/)** (v3.10+) — Required to run the schema registration script -2. **[Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)** (v2.50+) — Command-line tool for managing Azure resources +1. **[Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)** (v2.50+) — Command-line tool for managing Azure resources +2. **PowerShell** (Windows PowerShell or [PowerShell 7+/pwsh](https://learn.microsoft.com/powershell/scripting/install/installing-powershell), cross-platform) — Required to run `acr_build_push.ps1` and `post_deployment.ps1` 3. **[Git](https://git-scm.com/downloads/)** — Version control system for cloning the repository 4. **Deployed Infrastructure** — A successful Content Processing Solution Accelerator deployment from the [AVM repository](https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/sa/content-processing) -### Python Dependencies - -The registration script requires the `requests` library: - -```bash -pip install requests -``` - ## Post-Deployment Steps ### Step 1: Clone the Repository @@ -45,42 +38,55 @@ git clone https://github.com/microsoft/content-processing-solution-accelerator.g cd content-processing-solution-accelerator ``` -### Step 2: Get Your API Endpoint +### Step 2: Build and Push Container Images + +The AVM module provisions the Azure Container Registry and Container Apps but does **not** build or push the application images. Run this from the repository root, passing the resource group you deployed into: + +```powershell +.\infra\scripts\acr_build_push.ps1 "" +``` + +This builds all four container images (`web`, `api`, `app`, `wkfl`) via ACR Tasks and updates each container app to use the freshly built image. This step typically takes several minutes. + +### Step 3: Get Your API Endpoint (Optional) -Locate the API container app's FQDN from your deployment output or the Azure Portal: +`post_deployment.ps1` auto-discovers the API container app's FQDN from the resource group, so this step is optional — only needed if you want to pass it explicitly or the API app can't be found automatically. - Navigate to **Azure Portal** → **Resource Group** → **Container Apps** - Find the container app named **ca-**``**-api** - Copy the **Application URL** (e.g. `https://ca-myenv-api..azurecontainerapps.io`) -### Step 3: Register Schemas and Create Schema Set +### Step 4: Register Schemas and Create Schema Set -The registration script performs three steps automatically: +Run the post-deployment script, passing the resource group you deployed into: + +```powershell +.\infra\scripts\post_deployment.ps1 -ResourceGroupName "" -ApiBaseUrl "https://" +``` + +`-ApiBaseUrl` is optional if auto-discovery in Step 3 works; omit it to let the script find the API app itself. + +The script performs three steps automatically: 1. Registers individual schema files (auto claim, damaged car image, police report, repair estimate) via `/schemavault/` 2. Creates an **"Auto Claim"** schema set via `/schemasetvault/` 3. Adds all registered schemas into the schema set -Run the script: +It is idempotent — it skips schemas and schema sets that already exist, so it's safe to re-run. -```bash -cd src/ContentProcessorAPI/samples/schemas -python register_schema.py https:// schema_info.json -``` - -Replace `` with the URL from Step 2 (without a trailing slash). +> **Want custom schemas?** See [Customize Schema Data](./CustomizeSchemaData.md) to create your own document schemas. -The script is idempotent — it skips schemas and schema sets that already exist, so it's safe to re-run. +#### Notes on AVM-specific pre-flight checks -> **Want custom schemas?** See [Customize Schema Data](./CustomizeSchemaData.md) to create your own document schemas. +Before registering schemas, the script also runs a handful of self-healing pre-flight checks specific to AVM deployments (only active when `-ResourceGroupName` is supplied), which detect and automatically correct known AVM template gaps: storage account and Cosmos DB `publicNetworkAccess` being left `Disabled` on non-WAF deployments, the web container app's ingress port, and API authentication blocking anonymous schema registration calls. These are safe no-ops if your deployment doesn't have the issue. -### Step 4: Configure Authentication (Required) +### Step 5: Configure Authentication (Required) **This step is mandatory for application access:** 1. Follow [App Authentication Configuration](./ConfigureAppAuthentication.md). 2. Wait up to 10 minutes for authentication changes to take effect. -### Step 5: Verify Deployment +### Step 6: Verify Deployment 1. Access your application using the Web App URL from your deployment output. 2. Confirm the application loads successfully. From c30b0262417cc5001666fc84f3abaa7f82cd5240 Mon Sep 17 00:00:00 2001 From: Prachig-Microsoft Date: Tue, 4 Aug 2026 13:31:51 +0530 Subject: [PATCH 5/5] Revert "Update AVM post-deployment documentation to match the current flow" This reverts commit 321c4bd300a4ff498052de5b301827ad1df0a430. --- README.md | 2 -- docs/AVMPostDeploymentGuide.md | 64 +++++++++++++++------------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 812010c7..a2af2842 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,6 @@ Follow the quick deploy steps on the deployment guide to deploy this solution to [Click here to launch the deployment guide](./docs/DeploymentGuide.md) -> **Deploying via Azure Verified Modules (AVM)?** If you deployed this solution from the [AVM bicep registry module](https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/sa/content-processing) instead of `azd up`, see the [AVM Post Deployment Guide](./docs/AVMPostDeploymentGuide.md) for the required follow-up steps (build/push images, register schemas, configure authentication). - | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/microsoft/content-processing-solution-accelerator) | [![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/content-processing-solution-accelerator) | [![Open in Visual Studio Code Web](https://img.shields.io/static/v1?style=for-the-badge&label=Visual%20Studio%20Code%20(Web)&message=Open&color=blue&logo=visualstudiocode&logoColor=white)](https://vscode.dev/azure/?vscode-azure-exp=foundry&agentPayload=eyJiYXNlVXJsIjogImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNyb3NvZnQvY29udGVudC1wcm9jZXNzaW5nLXNvbHV0aW9uLWFjY2VsZXJhdG9yL3JlZnMvaGVhZHMvbWFpbi9pbmZyYS92c2NvZGVfd2ViIiwgImluZGV4VXJsIjogIi9pbmRleC5qc29uIiwgInZhcmlhYmxlcyI6IHsiYWdlbnRJZCI6ICIiLCAiY29ubmVjdGlvblN0cmluZyI6ICIiLCAidGhyZWFkSWQiOiAiIiwgInVzZXJNZXNzYWdlIjogIiIsICJwbGF5Z3JvdW5kTmFtZSI6ICIiLCAibG9jYXRpb24iOiAiIiwgInN1YnNjcmlwdGlvbklkIjogIiIsICJyZXNvdXJjZUlkIjogIiIsICJwcm9qZWN0UmVzb3VyY2VJZCI6ICIiLCAiZW5kcG9pbnQiOiAiIn0sICJjb2RlUm91dGUiOiBbImFpLXByb2plY3RzLXNkayIsICJweXRob24iLCAiZGVmYXVsdC1henVyZS1hdXRoIiwgImVuZHBvaW50Il19) | |---|---|---| diff --git a/docs/AVMPostDeploymentGuide.md b/docs/AVMPostDeploymentGuide.md index 78a20308..e0a1fe0b 100644 --- a/docs/AVMPostDeploymentGuide.md +++ b/docs/AVMPostDeploymentGuide.md @@ -8,13 +8,12 @@ This document provides guidance on post-deployment steps after deploying the Con ## Overview -After successfully deploying the Content Processing Solution Accelerator using the AVM template, you need to, **in this order**: +After successfully deploying the Content Processing Solution Accelerator using the AVM template, you need to: -1. **Build and push container images** — the AVM deployment provisions the Azure Container Registry (ACR) and Container Apps, but does not build/push application images. Run `acr_build_push` so the container apps pick up the real images. -2. **Run the post-deployment script** — registers schemas, creates the schema set, and self-heals a handful of known AVM configuration gaps (see [Notes on AVM-specific pre-flight checks](#notes-on-avm-specific-pre-flight-checks) below). -3. **Configure authentication** — set up app registration for secure access. +1. **Register schemas** — upload schema files, create a schema set, and link them together +2. **Configure authentication** — set up app registration for secure access -> **Note:** When deploying via `azd up`, image build/push and schema registration both happen automatically through post-provisioning hooks. AVM deployments require the manual steps below because the AVM module doesn't run those hooks. +> **Note:** When deploying via `azd up`, schema registration happens automatically through a post-provisioning hook. AVM deployments require the manual steps below. ## Prerequisites @@ -22,11 +21,19 @@ Before starting, ensure you have: ### Required Software -1. **[Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)** (v2.50+) — Command-line tool for managing Azure resources -2. **PowerShell** (Windows PowerShell or [PowerShell 7+/pwsh](https://learn.microsoft.com/powershell/scripting/install/installing-powershell), cross-platform) — Required to run `acr_build_push.ps1` and `post_deployment.ps1` +1. **[Python](https://www.python.org/downloads/)** (v3.10+) — Required to run the schema registration script +2. **[Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)** (v2.50+) — Command-line tool for managing Azure resources 3. **[Git](https://git-scm.com/downloads/)** — Version control system for cloning the repository 4. **Deployed Infrastructure** — A successful Content Processing Solution Accelerator deployment from the [AVM repository](https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/sa/content-processing) +### Python Dependencies + +The registration script requires the `requests` library: + +```bash +pip install requests +``` + ## Post-Deployment Steps ### Step 1: Clone the Repository @@ -38,55 +45,42 @@ git clone https://github.com/microsoft/content-processing-solution-accelerator.g cd content-processing-solution-accelerator ``` -### Step 2: Build and Push Container Images - -The AVM module provisions the Azure Container Registry and Container Apps but does **not** build or push the application images. Run this from the repository root, passing the resource group you deployed into: - -```powershell -.\infra\scripts\acr_build_push.ps1 "" -``` - -This builds all four container images (`web`, `api`, `app`, `wkfl`) via ACR Tasks and updates each container app to use the freshly built image. This step typically takes several minutes. - -### Step 3: Get Your API Endpoint (Optional) +### Step 2: Get Your API Endpoint -`post_deployment.ps1` auto-discovers the API container app's FQDN from the resource group, so this step is optional — only needed if you want to pass it explicitly or the API app can't be found automatically. +Locate the API container app's FQDN from your deployment output or the Azure Portal: - Navigate to **Azure Portal** → **Resource Group** → **Container Apps** - Find the container app named **ca-**``**-api** - Copy the **Application URL** (e.g. `https://ca-myenv-api..azurecontainerapps.io`) -### Step 4: Register Schemas and Create Schema Set +### Step 3: Register Schemas and Create Schema Set -Run the post-deployment script, passing the resource group you deployed into: - -```powershell -.\infra\scripts\post_deployment.ps1 -ResourceGroupName "" -ApiBaseUrl "https://" -``` - -`-ApiBaseUrl` is optional if auto-discovery in Step 3 works; omit it to let the script find the API app itself. - -The script performs three steps automatically: +The registration script performs three steps automatically: 1. Registers individual schema files (auto claim, damaged car image, police report, repair estimate) via `/schemavault/` 2. Creates an **"Auto Claim"** schema set via `/schemasetvault/` 3. Adds all registered schemas into the schema set -It is idempotent — it skips schemas and schema sets that already exist, so it's safe to re-run. +Run the script: -> **Want custom schemas?** See [Customize Schema Data](./CustomizeSchemaData.md) to create your own document schemas. +```bash +cd src/ContentProcessorAPI/samples/schemas +python register_schema.py https:// schema_info.json +``` -#### Notes on AVM-specific pre-flight checks +Replace `` with the URL from Step 2 (without a trailing slash). -Before registering schemas, the script also runs a handful of self-healing pre-flight checks specific to AVM deployments (only active when `-ResourceGroupName` is supplied), which detect and automatically correct known AVM template gaps: storage account and Cosmos DB `publicNetworkAccess` being left `Disabled` on non-WAF deployments, the web container app's ingress port, and API authentication blocking anonymous schema registration calls. These are safe no-ops if your deployment doesn't have the issue. +The script is idempotent — it skips schemas and schema sets that already exist, so it's safe to re-run. + +> **Want custom schemas?** See [Customize Schema Data](./CustomizeSchemaData.md) to create your own document schemas. -### Step 5: Configure Authentication (Required) +### Step 4: Configure Authentication (Required) **This step is mandatory for application access:** 1. Follow [App Authentication Configuration](./ConfigureAppAuthentication.md). 2. Wait up to 10 minutes for authentication changes to take effect. -### Step 6: Verify Deployment +### Step 5: Verify Deployment 1. Access your application using the Web App URL from your deployment output. 2. Confirm the application loads successfully.