Private/Resolve-JsmAlertId.ps1
|
function Resolve-JsmAlertId { <# .SYNOPSIS Resolves an alert identifier of the given type to the alert's UUID. .DESCRIPTION The JSM Cloud Operations alert endpoints (GET /v1/alerts/{id}, close, acknowledge) only accept the alert UUID; there is no identifierType query parameter as there was in the legacy Opsgenie API. This helper maps the other identifier types to a UUID client-side: - id: returned unchanged, no API call. - alias: GET /v1/alerts/alias?alias={alias}. That endpoint only resolves open alerts, so on a 404 it falls back to GET /v1/alerts?query=alias:"{alias}", filtered to exact matches. - tiny: GET /v1/alerts?query=tinyId:{tinyId}, filtered to exact matches. Both searches match loosely, so results are paged newest-first (100 per page, at most 10 pages). The alias search stops at the first page with an exact match, since that match is the newest and no open alert can share the alias. The tinyId search reads every page so an open match on a later page still wins. tinyIds are reused over time, and an alias is only unique among open alerts, so a query can return several alerts. When that happens the single non-closed match wins; otherwise the most recently created match is used and a warning is written. Not exported; not callable by module consumers. .PARAMETER Id The alert identifier to resolve. .PARAMETER IdentifierType How to interpret -Id: 'id' (alert UUID), 'tiny' (tinyId), or 'alias'. .EXAMPLE Resolve-JsmAlertId -Id '623551' -IdentifierType 'tiny' Returns the UUID of the alert whose tinyId is 623551. .OUTPUTS System.String The alert UUID. .NOTES Throws when no alert matches. HTTP errors from Invoke-JsmApi propagate as-is. #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Id, [Parameter(Mandatory = $true)] [ValidateSet('id', 'tiny', 'alias')] [string] $IdentifierType ) begin { Write-Verbose 'Starting Resolve-JsmAlertId' } process { try { if ($IdentifierType -eq 'id') { Write-Output $Id return } if ($IdentifierType -eq 'alias') { try { $alert = Invoke-JsmApi -Method 'Get' -Path '/alerts/alias' -Query @{ alias = $Id } Write-Output $alert.id return } catch { # The alias endpoint only resolves open alerts; anything other than a 404 is a real error. if ([int]$_.Exception.Response.StatusCode -ne 404) { throw } } $escapedAlias = $Id -replace '([\\"])', '\$1' $searchQuery = "alias:`"$escapedAlias`"" $matchProperty = 'alias' $label = 'alias' } else { if ($Id -notmatch '^\d+$') { throw "Invalid tinyId '$Id'. A tinyId is a whole number, such as 623551." } $searchQuery = "tinyId:$Id" $matchProperty = 'tinyId' $label = 'tinyId' } # The query matches loosely (tinyId:77070 also returns 770701, and alias:"errors" # returns every alert whose alias contains that word), so exact matches can sit # behind pages of loose ones. Page newest-first and keep exact matches only. $pageSize = 100 $maximumPages = 10 $alertMatches = [System.Collections.Generic.List[object]]::new() $isPartialResult = $false for ($pageIndex = 0; $pageIndex -lt $maximumPages; $pageIndex++) { $queryParameters = @{ query = $searchQuery size = $pageSize offset = $pageIndex * $pageSize sort = 'createdAt' order = 'desc' } $response = Invoke-JsmApi -Method 'Get' -Path '/alerts' -Query $queryParameters $pageValues = @($response.values) foreach ($alertValue in $pageValues) { if ([string]$alertValue.$matchProperty -ceq $Id) { $alertMatches.Add($alertValue) } } if ($pageValues.Count -lt $pageSize) { break } # No open alert has this alias (the alias endpoint returned 404), so the # newest exact match is the answer and later pages cannot change it. if ($IdentifierType -eq 'alias' -and $alertMatches.Count -gt 0) { $isPartialResult = $true break } if ($pageIndex -eq $maximumPages - 1) { $isPartialResult = $true Write-Warning "Stopped searching for $label '$Id' after the first $($maximumPages * $pageSize) search results." } } if ($alertMatches.Count -eq 0) { if ($isPartialResult) { throw "No alert found with $label '$Id' in the first $($maximumPages * $pageSize) search results. Pass the alert UUID instead." } throw "No alert found with $label '$Id'." } $openMatches = @($alertMatches | Where-Object { $_.status -ne 'closed' }) if ($openMatches.Count -eq 1) { Write-Output $openMatches[0].id } else { $candidates = if ($openMatches.Count -gt 1) { $openMatches } else { $alertMatches } $newest = $candidates | Sort-Object -Property { [datetime]$_.createdAt } -Descending | Select-Object -First 1 if ($candidates.Count -gt 1) { $matchCount = if ($isPartialResult) { "at least $($candidates.Count)" } else { $candidates.Count } Write-Warning "$label '$Id' matches $matchCount alerts; using the most recently created ($($newest.id)). Pass the UUID to target a different one." } Write-Output $newest.id } } catch { throw } } end { Write-Verbose 'Completed Resolve-JsmAlertId' } } |