Private/Resolve-AzLocalClusterAllowList.ps1
|
function Resolve-AzLocalClusterAllowList { ######################################## <# .SYNOPSIS Resolves the effective allowedUpdateVersions allow-list for a single cluster from a parsed apply-updates schedule (schema v2), applying the per-ring-override-beats-global-default precedence. .DESCRIPTION v0.9.1 mapping helper shared by the readiness assessment override. Given a cluster's UpdateRing tag value and the typed schedule config produced by Get-AzLocalApplyUpdatesScheduleConfig, this returns the update allow-list that applies to that cluster: 1. PER-RING OVERRIDE (highest priority): the FIRST schedule row whose 'rings' cell covers the cluster's ring AND carries an explicit 'allowedUpdateVersions' override (surfaced by the schedule parser as row.AllowedUpdateVersionsParsed) wins. Rows are evaluated in schedule order, so the first matching override is deterministic. A row 'rings' value of '***' is treated as a wildcard matching every ring (the documented "every cluster" sentinel). 2. TOP-LEVEL FLEET DEFAULT: when no matching row supplies an override, the schedule's top-level allowedUpdateVersions (cfg.AllowedUpdateVersions) is used. 3. NONE: when neither is present, an empty allow-list is returned (Source = 'None'). The reserved sentinel 'Latest' (case-insensitive, appearing ALONE) means "no constraint - install the latest Ready update on each cluster". The caller checks IsLatest to decide whether to apply the filter at all. This helper performs NO Azure calls and has no side effects - it is a pure decision function over the already-parsed config object, so it is cheap to call once per cluster and trivially unit-testable. .PARAMETER UpdateRing The cluster's 'UpdateRing' tag value. May be $null / empty (an untagged cluster cannot match a per-ring override, so it falls back to the top-level default). .PARAMETER Schedule The typed config object returned by Get-AzLocalApplyUpdatesScheduleConfig (exposes .AllowedUpdateVersions [string[]] and .Schedule rows each with .rings and .AllowedUpdateVersionsParsed). .OUTPUTS [PSCustomObject] with: EffectiveAllowList [string[]] the cleaned, non-empty allow-list entries (may be @('Latest'); empty when Source='None') Source 'RowOverride' | 'TopLevel' | 'None' IsLatest [bool] the effective list is the 'Latest' no-constraint sentinel MatchedRing [string] the ring matched to a row override, or '' #> ######################################## [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $false)] [AllowNull()] [AllowEmptyString()] [string]$UpdateRing, [Parameter(Mandatory = $true)] [psobject]$Schedule ) Set-StrictMode -Version Latest # Top-level fleet default (cleaned of empty / whitespace tokens). $topAllow = @() if ($Schedule.PSObject.Properties['AllowedUpdateVersions'] -and $Schedule.AllowedUpdateVersions) { $topAllow = @($Schedule.AllowedUpdateVersions | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }) } $ring = if ($UpdateRing) { ([string]$UpdateRing).Trim() } else { '' } # Per-ring override: first matching row that carries an explicit override. $rowAllow = $null $matchedRing = '' if (-not [string]::IsNullOrWhiteSpace($ring) -and $Schedule.PSObject.Properties['Schedule'] -and $Schedule.Schedule) { foreach ($row in @($Schedule.Schedule)) { if (-not $row.PSObject.Properties['rings'] -or [string]::IsNullOrWhiteSpace([string]$row.rings)) { continue } $ringTokens = @(([string]$row.rings) -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $isWildcard = ($ringTokens -contains '***') $isRingHit = $isWildcard -or (@($ringTokens | Where-Object { [string]::Equals($_, $ring, [System.StringComparison]::OrdinalIgnoreCase) }).Count -gt 0) if (-not $isRingHit) { continue } if ($row.PSObject.Properties['AllowedUpdateVersionsParsed'] -and $row.AllowedUpdateVersionsParsed) { $rowAllow = @($row.AllowedUpdateVersionsParsed | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }) $matchedRing = $ring break } } } if ($null -ne $rowAllow -and @($rowAllow).Count -gt 0) { $effective = @($rowAllow) $source = 'RowOverride' } elseif ($topAllow.Count -gt 0) { $effective = @($topAllow) $source = 'TopLevel' $matchedRing = '' } else { $effective = @() $source = 'None' $matchedRing = '' } $isLatest = ($effective.Count -gt 0) -and -not (@($effective | Where-Object { -not [string]::Equals([string]$_, 'Latest', [System.StringComparison]::OrdinalIgnoreCase) }).Count -gt 0) return [PSCustomObject]@{ EffectiveAllowList = $effective Source = $source IsLatest = $isLatest MatchedRing = $matchedRing } } |