Public/Invoke-AvmUpdate.ps1
|
function Invoke-AvmUpdate { <# .SYNOPSIS End-to-end AVM update pipeline: scan → plan → report → approve → update. .DESCRIPTION Orchestrates the full AvmUpdater workflow: 1. Get-AvmUpdatePlan (scan + version lookup + risk analysis). 2. Filter by -IncludeRisk. 3. New-AvmUpdateReport (always generated, even with zero candidates). 4. If zero candidates, print "Everything is up to date" and exit 0. 5. Approve-AvmUpdate (interactive, PR, etc.). 6. Update-AvmModuleVersion (local mode) or surface PR validation results (github/azuredevops mode where updates ran inside approval). 7. Print final summary (updated, skipped, rolled back, warnings, report path, PR URL). Idempotent — re-running when already current is a no-op. .PARAMETER Path Root directory to scan. Defaults to the current directory. .PARAMETER Exclude Array of glob patterns to exclude. .PARAMETER ApprovalMode Approval mechanism: 'local', 'github', or 'azuredevops'. Defaults to 'local'. .PARAMETER IncludeRisk Risk tiers to include in the run. Defaults to all tiers. .PARAMETER DryRun Run everything except persisting changes or opening PRs. .PARAMETER OutputDirectory Directory for report files. Defaults to './avm-report'. .PARAMETER ConfigPath Path to avmupdater.config.json. .PARAMETER ExitOnFailure If set, calls `exit 1` when one or more updates were rolled back due to failed validation. Off by default so interactive shells are never killed — the caller should instead inspect the returned object's `rolledBack[]` property. Intended for non-interactive contexts (e.g. `pwsh -File script.ps1 -ExitOnFailure`) where a non-zero process exit code is required to signal failure. .OUTPUTS PSCustomObject with: updated[], skipped[], rolledBack[], warnings[], reportPath, prUrl, prUrls[]. When github.splitLowFromHigherRisk is enabled and both LOW and MEDIUM+/UNKNOWN items exist, two PRs are opened and prUrls[] contains both (prUrl holds the first, for backward compatibility). This object is the source of truth for success/failure — inspect `rolledBack.Count` rather than relying on the process exit code (which is only set to 1 when `-ExitOnFailure` is supplied). .EXAMPLE Invoke-AvmUpdate -Path ./infra .EXAMPLE Invoke-AvmUpdate -Path . -ApprovalMode github -IncludeRisk @('LOW','MEDIUM') -DryRun #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Position = 0)] [string]$Path = '.', [Parameter()] [string[]]$Exclude = @(), [Parameter()] [ValidateSet('local', 'github', 'azuredevops')] [string]$ApprovalMode = 'local', [Parameter()] [ValidateSet('LOW', 'MEDIUM', 'HIGH', 'UNKNOWN')] [string[]]$IncludeRisk = @('LOW', 'MEDIUM', 'HIGH', 'UNKNOWN'), [Parameter()] [switch]$DryRun, [Parameter()] [string]$OutputDirectory = './avm-report', [Parameter()] [string]$ConfigPath, [Parameter()] [switch]$ExitOnFailure ) $cfg = Get-AvmConfig -ConfigPath $ConfigPath if (-not $OutputDirectory) { $OutputDirectory = $cfg.report.outputDirectory } Write-Host "`nAvmUpdater — AVM Version Check" -ForegroundColor Cyan Write-Host ("─" * 60) -ForegroundColor DarkGray if ($DryRun) { Write-Host "[DRY RUN mode — no files will be changed]`n" -ForegroundColor Yellow } # Step 1: Scan + version lookup + risk analysis Write-Host "Step 1/5: Scanning $Path for AVM references..." -ForegroundColor White $plan = Get-AvmUpdatePlan -Path $Path -Exclude $Exclude -ConfigPath $ConfigPath # Step 2: Filter by IncludeRisk $filteredCandidates = @($plan.candidates | Where-Object { $_.riskTier -in $IncludeRisk }) $filteredPlan = [PSCustomObject]@{ candidates = $filteredCandidates lookupFailed = $plan.lookupFailed ignored = $plan.ignored pinned = $plan.pinned upToDateByConstraint = $plan.upToDateByConstraint skippedConstraints = $plan.skippedConstraints summary = $plan.summary } # Step 3: Generate report (always, even on DryRun or zero candidates) Write-Host "Step 2/5: Generating report..." -ForegroundColor White $reportResult = New-AvmUpdateReport -Plan $filteredPlan -OutputDirectory $OutputDirectory Write-Host " Report: $($reportResult.reportPath)" -ForegroundColor DarkGray # Step 4: Zero candidates = no-op if ($filteredCandidates.Count -eq 0 -and $plan.lookupFailed.Count -eq 0) { Write-Host "`nEverything is up to date. No updates found." -ForegroundColor Green Write-Host "Report: $($reportResult.reportPath)" -ForegroundColor DarkGray return [PSCustomObject]@{ updated = @(); skipped = @(); rolledBack = @(); warnings = @() reportPath = $reportResult.reportPath jsonPath = $reportResult.jsonPath prUrl = $null prUrls = @() } } Write-Host " Found $($filteredCandidates.Count) update candidate(s); $($plan.lookupFailed.Count) lookup failure(s)." -ForegroundColor White # Step 5: Approval gate Write-Host "Step 3/5: Running approval gate ($ApprovalMode)..." -ForegroundColor White $approvalResult = Approve-AvmUpdate -Plan $filteredPlan -ReportPath $reportResult.reportPath -ApprovalMode $ApprovalMode -WorkingDirectory $Path -DryRun:$DryRun # Step 6: Apply updates (local mode only — github/azuredevops already applied inside approval) $updateResult = $null if (-not $DryRun -and $ApprovalMode -eq 'local' -and $approvalResult.approvedItems.Count -gt 0) { Write-Host "Step 4/5: Applying $($approvalResult.approvedItems.Count) approved update(s)..." -ForegroundColor White $updateResult = Update-AvmModuleVersion -ApprovedPlan $approvalResult -DryRun:$DryRun } elseif ($DryRun) { Write-Host "Step 4/5: [DRY RUN] Skipping file modifications." -ForegroundColor Yellow } else { Write-Host "Step 4/5: Updates applied inside PR branch (approval mode: $ApprovalMode)." -ForegroundColor DarkGray } # Step 7: Final summary Write-Host "`nStep 5/5: Summary" -ForegroundColor White Write-Host ("─" * 60) -ForegroundColor DarkGray $updatedItems = @(); if ($updateResult) { $updatedItems = @($updateResult.applied) } $rolledBack = @(); if ($updateResult) { $rolledBack = @($updateResult.rolledBack) } $warningItems = @(); if ($updateResult) { $warningItems = @($updateResult.warnings) } $skippedItems = @(); if ($approvalResult -and $approvalResult.skippedItems) { $skippedItems = @($approvalResult.skippedItems) } Write-Host " Updated: $($updatedItems.Count)" -ForegroundColor Green Write-Host " Skipped: $($skippedItems.Count)" -ForegroundColor Yellow Write-Host " Rolled back: $($rolledBack.Count)" -ForegroundColor $(if ($rolledBack.Count -gt 0) { 'Red' } else { 'Green' }) Write-Host " Warnings: $($warningItems.Count)" -ForegroundColor $(if ($warningItems.Count -gt 0) { 'Yellow' } else { 'Green' }) Write-Host " Report: $($reportResult.reportPath)" -ForegroundColor DarkGray $prUrlsToShow = @(); if ($approvalResult.PSObject.Properties['prUrls']) { $prUrlsToShow = @($approvalResult.prUrls) } if ($prUrlsToShow.Count -gt 1) { foreach ($u in $prUrlsToShow) { Write-Host " PR URL: $u" -ForegroundColor Cyan } } elseif ($approvalResult.prUrl) { Write-Host " PR URL: $($approvalResult.prUrl)" -ForegroundColor Cyan } if ($rolledBack.Count -gt 0) { Write-Warning "Some updates failed validation and were rolled back:" foreach ($r in $rolledBack) { Write-Warning " $($r.item.module): $($r.error)" } } $result = [PSCustomObject]@{ updated = [object[]]$updatedItems skipped = [object[]]$skippedItems rolledBack = [object[]]$rolledBack warnings = [object[]]$warningItems reportPath = $reportResult.reportPath jsonPath = $reportResult.jsonPath prUrl = $approvalResult.prUrl prUrls = [object[]]$prUrlsToShow } if ($ExitOnFailure -and $rolledBack.Count -gt 0) { exit 1 } return $result } |