Private/Invoke-AACHttp.ps1
|
function Invoke-AACHttp { <# .SYNOPSIS Calls an Azure API once, through the pooled HttpClient, retrying what is worth retrying - and returns the response's status and text, or throws with Azure's own reason. .DESCRIPTION Every Azure call of the module goes through here (Invoke-AACArmRequest for Resource Manager, Invoke-AACResourceGraphQuery, Invoke-AACCostQuery) or through Invoke-AACArmParallel, with the same rules: - throttling (429) and server errors (5xx) are retried up to four times, waiting as long as the longest retry header asks (Get-AACRetryDelay), else 5, 10, 15, 20 seconds; never more than 60 at a time - a dropped connection is retried after a second or two - any other failure throws, with the HTTP status in the exception's Data['StatusCode'] and Azure's message - with the query APIs' nested innererror detail - as its message (Get-AACErrorMessage) Returns @{ Status; Content (the response text) }. -Resource picks the token's audience (Get-AACAccessToken -Resource). #> [CmdletBinding()] [OutputType([hashtable])] param( [ValidateSet('Get', 'Post', 'Put', 'Delete')] [string] $Method = 'Get', [Parameter(Mandatory)] [string] $Uri, [string] $Body, [string] $Resource = 'https://management.azure.com', [ValidateRange(1, 10)] [int] $MaxAttempts = 5 ) for ($attempt = 1; ; $attempt++) { $send = @{ Method = $Method; Uri = $Uri; Token = (Get-AACAccessToken -Resource $Resource) } if ($PSBoundParameters.ContainsKey('Body')) { $send.Body = $Body } $response = $null $status = 0 $content = '' $failure = '' try { $response = (Send-AACHttpRequest @send).GetAwaiter().GetResult() $status = [int]$response.StatusCode $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() } catch { $failure = $_.Exception.GetBaseException().Message } try { if ($status -ge 200 -and $status -lt 300) { return @{ Status = $status; Content = $content } } if ($attempt -lt $MaxAttempts -and ($status -eq 0 -or $status -eq 429 -or $status -ge 500)) { $wait = Get-AACRetryDelay -Response $response -Status $status -Attempt $attempt Start-Sleep -Milliseconds ([int](1000 * $wait)) continue } # Azure's own reason, with the nested detail the query APIs give. $message = Get-AACErrorMessage -Content $content -Fallback $(if ($failure) { $failure } else { "Response status code does not indicate success: $status ($($response.ReasonPhrase))." }) $exception = [System.Exception]::new($message) $exception.Data['StatusCode'] = $status throw $exception } finally { if ($response) { $response.Dispose() } } } } |