@@ -131,6 +131,47 @@ function Check-Prerequisites {
131131 Write-LogSuccess " Logged into Azure CLI"
132132}
133133
134+ # ==============================================================================
135+ # Step 1b: Azure Role / Permission Check
136+ # ==============================================================================
137+ #
138+ # Per docs/DeploymentGuide.md, the deploying account needs:
139+ # - Contributor (or Owner) on the subscription -- to update resources
140+ # - User Access Administrator OR Role Based Access Control Administrator
141+ # (or Owner) -- to assign the AcrPull role to managed identities
142+ # This check is non-fatal: group-inherited roles may not always enumerate.
143+ # ==============================================================================
144+
145+ function Check-AzureRoles {
146+ Write-LogStep " Step 1b: Checking Azure Roles & Permissions"
147+
148+ $subId = az account show -- query id - o tsv 2> $null
149+ $userId = az ad signed- in- user show -- query id - o tsv 2> $null
150+ if (-not $subId -or -not $userId ) {
151+ Write-LogWarn " Could not determine subscription or user identity -- skipping role check."
152+ return
153+ }
154+
155+ $scope = " /subscriptions/$subId "
156+ $rolesRaw = az role assignment list -- assignee $userId -- scope $scope `
157+ -- include- inherited -- include- groups -- query " [].roleDefinitionName" - o tsv 2> $null
158+ if (-not $rolesRaw ) {
159+ Write-LogWarn " Unable to enumerate role assignments at $scope ."
160+ Write-LogWarn " Required: Contributor + (User Access Administrator OR Role Based Access Control Administrator), or Owner."
161+ return
162+ }
163+
164+ $roles = ($rolesRaw -split " `r ?`n " | Where-Object { $_ -ne " " })
165+ $hasResMgmt = ($roles -contains ' Owner' ) -or ($roles -contains ' Contributor' )
166+ $hasRoleMgmt = ($roles -contains ' Owner' ) -or ($roles -contains ' User Access Administrator' ) -or ($roles -contains ' Role Based Access Control Administrator' )
167+
168+ if ($hasResMgmt ) { Write-LogSuccess " Resource management role found (Owner/Contributor)" }
169+ else { Write-LogWarn " Missing 'Contributor' (or 'Owner') at subscription scope -- Azure resource updates may fail." }
170+
171+ if ($hasRoleMgmt ) { Write-LogSuccess " Role-assignment permission found (Owner/UAA/RBAC Admin)" }
172+ else { Write-LogWarn " Missing 'User Access Administrator' / 'Role Based Access Control Administrator' (or 'Owner') -- AcrPull role assignment may fail. Pass -SkipRoleAssignment if roles are already in place." }
173+ }
174+
134175# ==============================================================================
135176# Step 2: Discover Azure Resources
136177# ==============================================================================
@@ -454,22 +495,45 @@ function Build-AndPush {
454495
455496 Write-LogInfo " Logging into ACR: $script :AcrName ..."
456497 az acr login -- name $script :AcrName
498+ if ($LASTEXITCODE -ne 0 ) {
499+ Write-LogError " ACR login failed for '$script :AcrName '."
500+ Write-LogError " Likely causes:"
501+ Write-LogError " - Your account lacks 'AcrPush' / 'Contributor' on the registry."
502+ Write-LogError " - Docker daemon not running."
503+ Write-LogError " - Tenant blocks docker-credential helpers (try: az acr login -n $script :AcrName --expose-token)."
504+ exit 1
505+ }
457506 Write-LogSuccess " ACR login successful"
458507
459508 $env: DOCKER_BUILDKIT = " 1"
460509
510+ # Track per-service success so a partial failure does not strand the others
511+ $script :BuildResults = [ordered ]@ {}
512+
461513 if ($script :DeployBackend ) {
462514 $fullImage = " $ ( $script :AcrLoginServer ) /$BackendImageName `:$ ( $script :ImageTag ) "
463515 Write-LogInfo " Building backend image: $fullImage "
464516 if ($DryRun ) {
465517 Write-LogInfo " [DRY RUN] Would build: docker build -t $fullImage $BackendDir "
518+ $script :BuildResults [" backend" ] = " dry-run"
466519 } else {
467520 docker build - t $fullImage $BackendDir
468- if ($LASTEXITCODE -ne 0 ) { Write-LogError " Backend image build FAILED" ; exit 1 }
469- Write-LogSuccess " Backend image built"
470- docker push $fullImage
471- if ($LASTEXITCODE -ne 0 ) { Write-LogError " Backend image push FAILED" ; exit 1 }
472- Write-LogSuccess " Backend image pushed: $fullImage "
521+ if ($LASTEXITCODE -ne 0 ) {
522+ Write-LogError " Backend image build FAILED -- continuing with other services"
523+ $script :BuildResults [" backend" ] = " build-failed"
524+ $script :DeployBackend = $false
525+ } else {
526+ Write-LogSuccess " Backend image built"
527+ docker push $fullImage
528+ if ($LASTEXITCODE -ne 0 ) {
529+ Write-LogError " Backend image push FAILED -- continuing with other services"
530+ $script :BuildResults [" backend" ] = " push-failed"
531+ $script :DeployBackend = $false
532+ } else {
533+ Write-LogSuccess " Backend image pushed: $fullImage "
534+ $script :BuildResults [" backend" ] = " ok"
535+ }
536+ }
473537 }
474538 }
475539
@@ -478,13 +542,25 @@ function Build-AndPush {
478542 Write-LogInfo " Building MCP image: $fullImage "
479543 if ($DryRun ) {
480544 Write-LogInfo " [DRY RUN] Would build: docker build -t $fullImage $McpDir "
545+ $script :BuildResults [" mcp" ] = " dry-run"
481546 } else {
482547 docker build - t $fullImage $McpDir
483- if ($LASTEXITCODE -ne 0 ) { Write-LogError " MCP image build FAILED" ; exit 1 }
484- Write-LogSuccess " MCP image built"
485- docker push $fullImage
486- if ($LASTEXITCODE -ne 0 ) { Write-LogError " MCP image push FAILED" ; exit 1 }
487- Write-LogSuccess " MCP image pushed: $fullImage "
548+ if ($LASTEXITCODE -ne 0 ) {
549+ Write-LogError " MCP image build FAILED -- continuing with other services"
550+ $script :BuildResults [" mcp" ] = " build-failed"
551+ $script :DeployMcp = $false
552+ } else {
553+ Write-LogSuccess " MCP image built"
554+ docker push $fullImage
555+ if ($LASTEXITCODE -ne 0 ) {
556+ Write-LogError " MCP image push FAILED -- continuing with other services"
557+ $script :BuildResults [" mcp" ] = " push-failed"
558+ $script :DeployMcp = $false
559+ } else {
560+ Write-LogSuccess " MCP image pushed: $fullImage "
561+ $script :BuildResults [" mcp" ] = " ok"
562+ }
563+ }
488564 }
489565 }
490566
@@ -493,16 +569,35 @@ function Build-AndPush {
493569 Write-LogInfo " Building frontend image: $fullImage "
494570 if ($DryRun ) {
495571 Write-LogInfo " [DRY RUN] Would build: docker build -t $fullImage $FrontendDir "
572+ $script :BuildResults [" frontend" ] = " dry-run"
496573 } else {
497574 docker build - t $fullImage $FrontendDir
498- if ($LASTEXITCODE -ne 0 ) { Write-LogError " Frontend image build FAILED" ; exit 1 }
499- Write-LogSuccess " Frontend image built"
500- docker push $fullImage
501- if ($LASTEXITCODE -ne 0 ) { Write-LogError " Frontend image push FAILED" ; exit 1 }
502- Write-LogSuccess " Frontend image pushed: $fullImage "
575+ if ($LASTEXITCODE -ne 0 ) {
576+ Write-LogError " Frontend image build FAILED -- continuing with other services"
577+ $script :BuildResults [" frontend" ] = " build-failed"
578+ $script :DeployFrontend = $false
579+ } else {
580+ Write-LogSuccess " Frontend image built"
581+ docker push $fullImage
582+ if ($LASTEXITCODE -ne 0 ) {
583+ Write-LogError " Frontend image push FAILED -- continuing with other services"
584+ $script :BuildResults [" frontend" ] = " push-failed"
585+ $script :DeployFrontend = $false
586+ } else {
587+ Write-LogSuccess " Frontend image pushed: $fullImage "
588+ $script :BuildResults [" frontend" ] = " ok"
589+ }
590+ }
503591 }
504592 }
505593
594+ # If all selected services failed to build/push, bail before touching Azure resources
595+ $okCount = ($script :BuildResults.Values | Where-Object { $_ -eq " ok" -or $_ -eq " dry-run" }).Count
596+ if ($okCount -eq 0 -and $script :BuildResults.Count -gt 0 ) {
597+ Write-LogError " All image builds/pushes failed -- aborting before touching Azure resources."
598+ exit 1
599+ }
600+
506601 if ($BuildOnly ) {
507602 Write-LogSuccess " Build & push complete (-BuildOnly mode, skipping Azure update)"
508603 }
@@ -656,6 +751,16 @@ function Print-Summary {
656751 Write-Host " Image Tag: $ ( $script :ImageTag ) "
657752 Write-Host " "
658753
754+ if ($script :BuildResults -and $script :BuildResults.Count -gt 0 ) {
755+ Write-Host " Build results:"
756+ foreach ($k in $script :BuildResults.Keys ) {
757+ $v = $script :BuildResults [$k ]
758+ $glyph = if ($v -eq " ok" -or $v -eq " dry-run" ) { " [OK]" } else { " [FAIL]" }
759+ Write-Host (" {0,-6} {1,-9} {2}" -f $glyph , $k , $v )
760+ }
761+ Write-Host " "
762+ }
763+
659764 if ($script :DeployBackend -and $script :BackendCA ) {
660765 Write-Host " Backend: $ ( $script :AcrLoginServer ) /$BackendImageName `:$ ( $script :ImageTag ) "
661766 }
@@ -720,6 +825,7 @@ Write-Host "╚═════════════════════
720825Write-Host " "
721826
722827Check- Prerequisites
828+ Check- AzureRoles
723829Discover- Resources
724830Resolve-Acr
725831Determine- Services
0 commit comments