Skip to content

Commit 7235e80

Browse files
added fallback logic
1 parent 233ebad commit 7235e80

4 files changed

Lines changed: 562 additions & 51 deletions

File tree

infra/scripts/deploy_to_azure.ps1

Lines changed: 121 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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 "╚═════════════════════
720825
Write-Host ""
721826

722827
Check-Prerequisites
828+
Check-AzureRoles
723829
Discover-Resources
724830
Resolve-Acr
725831
Determine-Services

infra/scripts/deploy_to_azure.sh

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,54 @@ check_prerequisites() {
207207
log_success "Logged into Azure CLI"
208208
}
209209

210+
# ==============================================================================
211+
# Step 1b: Azure Role / Permission Check
212+
# ==============================================================================
213+
#
214+
# Per docs/DeploymentGuide.md, the deploying account needs:
215+
# - Contributor (or Owner) on the subscription -- to update resources
216+
# - User Access Administrator OR Role Based Access Control Administrator
217+
# (or Owner) -- to assign the AcrPull role to managed identities
218+
# This check is non-fatal: group-inherited roles may not always enumerate.
219+
# ==============================================================================
220+
221+
check_azure_roles() {
222+
log_step "Step 1b: Checking Azure Roles & Permissions"
223+
224+
local sub_id user_id
225+
sub_id=$(az account show --query id -o tsv 2>/dev/null || true)
226+
user_id=$(az ad signed-in-user show --query id -o tsv 2>/dev/null || true)
227+
if [[ -z "$sub_id" || -z "$user_id" ]]; then
228+
log_warn "Could not determine subscription or user identity -- skipping role check."
229+
return
230+
fi
231+
232+
local scope="/subscriptions/$sub_id"
233+
local roles_raw
234+
roles_raw=$(az role assignment list --assignee "$user_id" --scope "$scope" \
235+
--include-inherited --include-groups --query "[].roleDefinitionName" -o tsv 2>/dev/null || true)
236+
if [[ -z "$roles_raw" ]]; then
237+
log_warn "Unable to enumerate role assignments at $scope."
238+
log_warn "Required: Contributor + (User Access Administrator OR Role Based Access Control Administrator), or Owner."
239+
return
240+
fi
241+
242+
local has_res_mgmt=false has_role_mgmt=false
243+
while IFS= read -r r; do
244+
case "$r" in
245+
Owner) has_res_mgmt=true; has_role_mgmt=true ;;
246+
Contributor) has_res_mgmt=true ;;
247+
"User Access Administrator"|"Role Based Access Control Administrator") has_role_mgmt=true ;;
248+
esac
249+
done <<< "$roles_raw"
250+
251+
if $has_res_mgmt; then log_success "Resource management role found (Owner/Contributor)"
252+
else log_warn "Missing 'Contributor' (or 'Owner') at subscription scope -- Azure resource updates may fail."; fi
253+
254+
if $has_role_mgmt; then log_success "Role-assignment permission found (Owner/UAA/RBAC Admin)"
255+
else log_warn "Missing 'User Access Administrator' / 'Role Based Access Control Administrator' (or 'Owner') -- AcrPull role assignment may fail. Pass --skip-role-assignment if roles are already in place."; fi
256+
}
257+
210258
# ==============================================================================
211259
# Step 2: Validate Resource Group & Discover Resources
212260
# ==============================================================================
@@ -576,21 +624,44 @@ build_and_push() {
576624
fi
577625

578626
log_info "Logging into ACR: $ACR_NAME..."
579-
az acr login --name "$ACR_NAME"
627+
if ! az acr login --name "$ACR_NAME"; then
628+
log_error "ACR login failed for '$ACR_NAME'."
629+
log_error " Likely causes:"
630+
log_error " - Your account lacks 'AcrPush' / 'Contributor' on the registry."
631+
log_error " - Docker daemon not running."
632+
log_error " - Tenant blocks docker-credential helpers (try: az acr login -n $ACR_NAME --expose-token)."
633+
exit 1
634+
fi
580635
log_success "ACR login successful"
581636

582637
export DOCKER_BUILDKIT=1
583638

639+
# Track per-service success so a partial failure does not strand the others.
640+
# Uses parallel arrays so we can iterate in order in the summary.
641+
BUILD_RESULT_NAMES=()
642+
BUILD_RESULT_STATUS=()
643+
_record_build_result() { BUILD_RESULT_NAMES+=("$1"); BUILD_RESULT_STATUS+=("$2"); }
644+
584645
if [[ "$DEPLOY_BACKEND" == true ]]; then
585646
local full_image="$ACR_LOGIN_SERVER/$BACKEND_IMAGE_NAME:$IMAGE_TAG"
586647
log_info "Building backend image: $full_image"
587648
if [[ "$DRY_RUN" == true ]]; then
588649
log_info "[DRY RUN] Would build: docker build -t $full_image $BACKEND_DIR"
650+
_record_build_result backend dry-run
651+
elif ! docker build -t "$full_image" "$(_winpath "$BACKEND_DIR")"; then
652+
log_error "Backend image build FAILED -- continuing with other services"
653+
_record_build_result backend build-failed
654+
DEPLOY_BACKEND=false
589655
else
590-
docker build -t "$full_image" "$(_winpath "$BACKEND_DIR")"
591656
log_success "Backend image built"
592-
docker push "$full_image"
593-
log_success "Backend image pushed: $full_image"
657+
if ! docker push "$full_image"; then
658+
log_error "Backend image push FAILED -- continuing with other services"
659+
_record_build_result backend push-failed
660+
DEPLOY_BACKEND=false
661+
else
662+
log_success "Backend image pushed: $full_image"
663+
_record_build_result backend ok
664+
fi
594665
fi
595666
fi
596667

@@ -599,11 +670,21 @@ build_and_push() {
599670
log_info "Building MCP image: $full_image"
600671
if [[ "$DRY_RUN" == true ]]; then
601672
log_info "[DRY RUN] Would build: docker build -t $full_image $MCP_DIR"
673+
_record_build_result mcp dry-run
674+
elif ! docker build -t "$full_image" "$(_winpath "$MCP_DIR")"; then
675+
log_error "MCP image build FAILED -- continuing with other services"
676+
_record_build_result mcp build-failed
677+
DEPLOY_MCP=false
602678
else
603-
docker build -t "$full_image" "$(_winpath "$MCP_DIR")"
604679
log_success "MCP image built"
605-
docker push "$full_image"
606-
log_success "MCP image pushed: $full_image"
680+
if ! docker push "$full_image"; then
681+
log_error "MCP image push FAILED -- continuing with other services"
682+
_record_build_result mcp push-failed
683+
DEPLOY_MCP=false
684+
else
685+
log_success "MCP image pushed: $full_image"
686+
_record_build_result mcp ok
687+
fi
607688
fi
608689
fi
609690

@@ -612,14 +693,34 @@ build_and_push() {
612693
log_info "Building frontend image: $full_image"
613694
if [[ "$DRY_RUN" == true ]]; then
614695
log_info "[DRY RUN] Would build: docker build -t $full_image $FRONTEND_DIR"
696+
_record_build_result frontend dry-run
697+
elif ! docker build -t "$full_image" "$(_winpath "$FRONTEND_DIR")"; then
698+
log_error "Frontend image build FAILED -- continuing with other services"
699+
_record_build_result frontend build-failed
700+
DEPLOY_FRONTEND=false
615701
else
616-
docker build -t "$full_image" "$(_winpath "$FRONTEND_DIR")"
617702
log_success "Frontend image built"
618-
docker push "$full_image"
619-
log_success "Frontend image pushed: $full_image"
703+
if ! docker push "$full_image"; then
704+
log_error "Frontend image push FAILED -- continuing with other services"
705+
_record_build_result frontend push-failed
706+
DEPLOY_FRONTEND=false
707+
else
708+
log_success "Frontend image pushed: $full_image"
709+
_record_build_result frontend ok
710+
fi
620711
fi
621712
fi
622713

714+
# If all selected services failed to build/push, bail before touching Azure resources
715+
local ok_count=0 i
716+
for i in "${BUILD_RESULT_STATUS[@]}"; do
717+
if [[ "$i" == "ok" || "$i" == "dry-run" ]]; then ok_count=$((ok_count + 1)); fi
718+
done
719+
if [[ ${#BUILD_RESULT_STATUS[@]} -gt 0 && $ok_count -eq 0 ]]; then
720+
log_error "All image builds/pushes failed -- aborting before touching Azure resources."
721+
exit 1
722+
fi
723+
623724
if [[ "$BUILD_ONLY" == true ]]; then
624725
log_success "Build & push complete (--build-only mode, skipping Azure update)"
625726
return
@@ -810,6 +911,18 @@ print_summary() {
810911
echo " Image Tag: $IMAGE_TAG"
811912
echo ""
812913

914+
if [[ ${#BUILD_RESULT_NAMES[@]:-0} -gt 0 ]]; then
915+
echo " Build results:"
916+
local i name status glyph
917+
for i in "${!BUILD_RESULT_NAMES[@]}"; do
918+
name="${BUILD_RESULT_NAMES[$i]}"
919+
status="${BUILD_RESULT_STATUS[$i]}"
920+
if [[ "$status" == "ok" || "$status" == "dry-run" ]]; then glyph="[OK] "; else glyph="[FAIL]"; fi
921+
printf " %s %-9s %s\n" "$glyph" "$name" "$status"
922+
done
923+
echo ""
924+
fi
925+
813926
if [[ "$DEPLOY_BACKEND" == true && -n "$BACKEND_CA" ]]; then
814927
echo " Backend: $ACR_LOGIN_SERVER/$BACKEND_IMAGE_NAME:$IMAGE_TAG"
815928
fi
@@ -876,6 +989,7 @@ main() {
876989

877990
parse_args "$@"
878991
check_prerequisites
992+
check_azure_roles
879993
validate_and_discover
880994
resolve_acr
881995
determine_services

0 commit comments

Comments
 (0)