Private/Invoke-AACArmWrite.ps1
|
function Invoke-AACArmWrite { <# .SYNOPSIS Creates, updates or deletes one Azure Resource Manager resource with PUT, PATCH or DELETE, and waits for Azure to finish - the write path of Deploy-AACStorageAccount. .DESCRIPTION Sends the request through Invoke-AACHttp (retries, Azure's reasons). A long-running operation - 201 or 202, with an Azure-AsyncOperation or Location header - is followed until Azure says it is done: Azure-AsyncOperation polled until status is Succeeded, Failed or Canceled; Failed and Canceled throw with Azure's error Location polled until it stops answering 202 honouring Retry-After (5 seconds otherwise), for at most -TimeoutMinutes. Then, for PUT and PATCH, the resource is read again and returned (hashtables), so the caller sees what Azure made of it; DELETE returns nothing. A failure throws with Azure's message, and Data['Code'] (RequestDisallowedByPolicy when a policy denied it) and Data['StatusCode']. #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Put', 'Patch', 'Delete')] [string] $Method, # The resource's path with its api-version, as for Invoke-AACArmRequest. [Parameter(Mandatory)] [string] $Uri, [System.Collections.IDictionary] $Body, [ValidateRange(1, 120)] [int] $TimeoutMinutes = 30 ) $request = @{ Method = $Method; Uri = $Uri } if ($Body) { $request.Body = ConvertTo-Json -InputObject $Body -Depth 50 -Compress } $response = Invoke-AACHttp @request $deadline = [datetime]::UtcNow.AddMinutes($TimeoutMinutes) $wait = { param($Headers) $seconds = 5 if ($Headers -and $Headers.ContainsKey('Retry-After')) { $parsed = 0; if ([int]::TryParse([string]$Headers['Retry-After'], [ref]$parsed)) { $seconds = [Math]::Min([Math]::Max($parsed, 1), 60) } } if ([datetime]::UtcNow.AddSeconds($seconds) -gt $deadline) { throw "Azure didn't finish $($Method.ToUpperInvariant()) $Uri within $TimeoutMinutes minute(s)." } Start-Sleep -Seconds $seconds } if ($response.Status -in 201, 202 -and $response.Headers) { if ($response.Headers.ContainsKey('Azure-AsyncOperation')) { $operation = [string]$response.Headers['Azure-AsyncOperation'] $headers = $response.Headers while ($true) { & $wait $headers $poll = Invoke-AACHttp -Uri $operation $headers = $poll.Headers $state = if ($poll.Content) { ConvertFrom-Json -InputObject $poll.Content -AsHashtable -Depth 50 } else { @{} } $status = [string]$state['status'] if ($status -eq 'Succeeded') { break } if ($status -in 'Failed', 'Canceled') { $reason = if ($state['error'] -is [System.Collections.IDictionary]) { "$($state['error']['code']): $($state['error']['message'])" } else { $status } $exception = [System.Exception]::new("Azure couldn't $($Method.ToLowerInvariant()) $Uri - $reason") $exception.Data['Code'] = $(if ($state['error'] -is [System.Collections.IDictionary]) { [string]$state['error']['code'] } else { $status }) throw $exception } } } elseif ($response.Headers.ContainsKey('Location') -and $response.Status -eq 202) { $location = [string]$response.Headers['Location'] $headers = $response.Headers while ($true) { & $wait $headers $poll = Invoke-AACHttp -Uri $location $headers = $poll.Headers if ($poll.Status -ne 202) { break } } } } if ($Method -eq 'Delete') { return } Invoke-AACArmRequest -Uri $Uri } |