Private/Get-AzLocalApplyScheduleSourceBanner.ps1
|
function Get-AzLocalApplyScheduleSourceBanner { <# .SYNOPSIS Builds a short operator-facing markdown banner that explains, for a SCHEDULE-triggered (cron) apply-updates run, that the targeted UpdateRing(s) are derived from the caller's apply-updates-schedule.yml, which cycle day the current run falls on, and which rings that maps to. .DESCRIPTION v0.9.17. Rendered at the TOP of both the "Check Cluster Readiness" (readiness gate) and the "Apply Updates" step summaries so it is clear the pipeline acts ONLY on the operator's own schedule configuration - no rings are chosen by the tooling. The banner is SELF-CONTAINED and read-only: it detects the pipeline host + trigger, resolves the schedule file path the same way Resolve-AzLocalPipelineUpdateRing does (APPLY_UPDATES_SCHEDULE_PATH env or the host default), re-reads the schedule and resolves the current (cycleWeek, dayOfWeek) -> ring mapping for display. It returns an EMPTY array (renders nothing) when: - the trigger is not a schedule/cron firing (manual runs are opt-out of this banner per design - the operator chose the ring), OR - no schedule file is present at the resolved path, OR - the schedule cannot be read/resolved (never blocks the run - the banner is purely informational). .PARAMETER SchedulePath Path to apply-updates-schedule.yml. Defaults to env var APPLY_UPDATES_SCHEDULE_PATH, or './.github/apply-updates-schedule.yml' (GitHub) / './apply-updates-schedule.yml' (Azure DevOps / Local). .PARAMETER ResolveForDateUtc Optional 'yyyy-MM-dd' UTC date to resolve for (preview). Empty = now. .PARAMETER Trigger 'Manual' or 'Schedule'. Auto-detected from the pipeline host when omitted (GitHub: GITHUB_EVENT_NAME != 'workflow_dispatch' is Schedule; ADO: BUILD_REASON == 'Schedule'). .PARAMETER IncludeAuditRecommendation Switch. When set, appends a line recommending the operator run the 'Config: 3 - Apply-Updates Schedule Coverage Audit' pipeline for the full per-day view of their entire cycle. Used on the Apply Updates step banner. .OUTPUTS [string[]] - markdown lines (blockquote paragraph), or empty array. .NOTES Author : AzLocal.UpdateManagement Version : 0.9.17 #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter(Mandatory = $false)] [AllowEmptyString()] [string]$SchedulePath = '', [Parameter(Mandatory = $false)] [AllowEmptyString()] [string]$ResolveForDateUtc = '', [Parameter(Mandatory = $false)] [ValidateSet('Manual', 'Schedule', '')] [string]$Trigger = '', [switch]$IncludeAuditRecommendation ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $pipelineHost = Get-AzLocalPipelineHost # Trigger auto-detection (host-specific) - mirrors Resolve-AzLocalPipelineUpdateRing. if (-not $Trigger) { switch ($pipelineHost) { 'GitHub' { $eventName = $env:GITHUB_EVENT_NAME if (-not $eventName) { $eventName = 'workflow_dispatch' } $Trigger = if ($eventName -eq 'workflow_dispatch') { 'Manual' } else { 'Schedule' } } 'AzureDevOps' { $buildReason = $env:BUILD_REASON $Trigger = if ($buildReason -eq 'Schedule') { 'Schedule' } else { 'Manual' } } default { $Trigger = 'Manual' } } } # Banner is only for schedule/cron firings - a manual run chose its own ring. if ($Trigger -ne 'Schedule') { return @() } # SchedulePath default (host-specific) - mirrors Resolve-AzLocalPipelineUpdateRing. if (-not $SchedulePath) { $envPath = $env:APPLY_UPDATES_SCHEDULE_PATH if ($envPath) { $SchedulePath = $envPath } else { $SchedulePath = if ($pipelineHost -eq 'GitHub') { './.github/apply-updates-schedule.yml' } else { './apply-updates-schedule.yml' } } } if (-not (Test-Path -LiteralPath $SchedulePath)) { return @() } $resolveAt = [datetime]::UtcNow if (-not [string]::IsNullOrWhiteSpace($ResolveForDateUtc)) { try { $resolveAt = [datetime]::ParseExact( $ResolveForDateUtc.Trim(), 'yyyy-MM-dd', [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal -bor [System.Globalization.DateTimeStyles]::AdjustToUniversal ) } catch { $null = $_ # malformed preview date - fall back to now, never block the banner } } try { $cfg = Get-AzLocalApplyUpdatesScheduleConfig -Path $SchedulePath $decision = Resolve-AzLocalCurrentUpdateRing -Schedule $cfg -Now $resolveAt } catch { # Purely informational - never let a schedule read/parse issue break the run. Write-Verbose ("Get-AzLocalApplyScheduleSourceBanner: could not resolve schedule '{0}': {1}" -f $SchedulePath, $_.Exception.Message) return @() } $cycleWeek = [int]$decision.CycleWeek $cycleTotal = [int]$cfg.CycleWeeks $dayName = [string]$decision.DayOfWeekName $dateDisplay = $decision.NowUtc.ToString('yyyy-MM-dd') $ringsMatched = @($decision.Rings) $hasRings = ($ringsMatched.Count -gt 0) $ringsDisplay = if ($hasRings) { ($ringsMatched -join ', ') } else { '' } $bt = [string][char]96 # backtick, to emit markdown code spans without escaping $lines = [System.Collections.Generic.List[string]]::new() $lines.Add("> **Schedule-driven run - targeting is derived from your own configuration.**") $lines.Add("> ") $lines.Add("> The UpdateRing(s) this run targets are read from your apply-updates schedule file " + $bt + $SchedulePath + $bt + " (in your own repository) - not chosen by the pipeline.") $lines.Add("> ") if ($hasRings) { $lines.Add("> Today - **$dayName, $dateDisplay UTC** - is **cycle week $cycleWeek of $cycleTotal**, which your schedule maps to ring(s): **$ringsDisplay**.") } else { $lines.Add("> Today - **$dayName, $dateDisplay UTC** - is **cycle week $cycleWeek of $cycleTotal**, which your schedule maps to **no rings**, so this run will make no changes.") } $lines.Add("> ") $lines.Add("> This pipeline acts only on the rings your schedule marks eligible for the current cycle day; no other clusters are touched, and each cluster is gated by its " + $bt + "UpdateStartWindow" + $bt + " tag value, which uses UTC timezone.") if ($IncludeAuditRecommendation) { $lines.Add("> ") $lines.Add("> To review your entire cycle at a glance (every date mapped to its eligible rings), run the **'Config: 3 - Apply-Updates Schedule Coverage Audit'** pipeline and review its output.") } $lines.Add("") return $lines.ToArray() } |