Providers/FreeIPA/Private/Invoke-FreeIPABatch.ps1
|
function Invoke-FreeIPABatch { <# .SYNOPSIS Sends many FreeIPA commands in a few round trips through the JSON-RPC batch method .DESCRIPTION FreeIPA answers one command in well under a second, but the seed had 357 users, 413 hosts and 836 DNS records to create one call each, and the round trip was where the thirteen minutes went. The realm's batch method carries a list of commands in one request and answers with one result per command, in order, so a step can decide every object one at a time and send them fifty at a time. Each command is a Method, its Arguments, its Options and, optionally, the IgnoreError names Invoke-FreeIPARequest would have taken and a Tag for the caller to find its answer by. Each answer carries the command back with Success, Ignored, Result (the command's envelope, so .result is the entry as with Invoke-FreeIPARequest), ErrorName and ErrorMessage. A command the realm refused fails alone; the others in its chunk stand. A chunk the realm could not take at all - a transport failure, an expired session that could not be renewed - fails every command in it with the same message, so no command is ever silently unanswered. .PARAMETER Command The commands, as hashtables or objects with Method, Arguments, Options, IgnoreError, Tag .PARAMETER ChunkSize How many commands travel in one request .PARAMETER Connection The connection; the active one when omitted .OUTPUTS PSCustomObject typed FreeIPABatchResult, one per command, in order .EXAMPLE PS> Invoke-FreeIPABatch -Command @(@{ Method = 'user_add'; Arguments = @('jnino'); Options = $options; Tag = 'jnino' }) One user, one answer, with Success and the entry. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowNull()] [object[]]$Command, [Parameter()] [ValidateRange(1, 500)] [int]$ChunkSize = 50, [Parameter()] [hashtable]$Connection ) if (-not $Connection) { $Connection = Get-FreeIPAConnection } $commands = @($Command | Where-Object { $null -ne $_ }) if ($commands.Count -eq 0) { return } $answerFor = { param($item, $success, $ignored, $result, $name, $message) [PSCustomObject]@{ PSTypeName = 'FreeIPABatchResult' Command = $item Success = [bool]$success Ignored = [bool]$ignored Result = $result ErrorName = $name ErrorMessage = $message } } for ($start = 0; $start -lt $commands.Count; $start += $ChunkSize) { $chunk = @($commands[$start..([Math]::Min($start + $ChunkSize - 1, $commands.Count - 1))]) $calls = @(foreach ($item in $chunk) { $options = @{} if ($item.Options) { foreach ($key in $item.Options.Keys) { if ($null -ne $item.Options[$key]) { $options[[string]$key] = $item.Options[$key] } } } if ($Connection.ApiVersion -and -not $options.ContainsKey('version')) { $options['version'] = [string]$Connection.ApiVersion } @{ method = [string]$item.Method; params = @(, [object[]]@($item.Arguments)) + @(, $options) } }) $answers = @() try { $envelope = Invoke-FreeIPARequest -Method 'batch' -Arguments $calls -Options @{} -Connection $Connection if ($envelope -and $envelope.PSObject.Properties['results']) { $answers = @($envelope.results) } } catch { $message = $_.Exception.Message foreach ($item in $chunk) { & $answerFor $item $false $false $null 'BatchFailed' $message } continue } for ($i = 0; $i -lt $chunk.Count; $i++) { $item = $chunk[$i] $answer = if ($i -lt $answers.Count) { $answers[$i] } else { $null } if ($null -eq $answer) { & $answerFor $item $false $false $null 'NoAnswer' "FreeIPA $($item.Method) went unanswered in its batch." continue } $errorText = if ($answer.PSObject.Properties['error']) { [string]$answer.error } else { '' } if (-not $errorText) { & $answerFor $item $true $false $answer $null $null continue } $errorName = if ($answer.PSObject.Properties['error_name']) { [string]$answer.error_name } else { '' } if ($errorName -and @($item.IgnoreError) -contains $errorName) { Write-Verbose "FreeIPA $($item.Method) answered $errorName, which the caller expected." & $answerFor $item $true $true $null $errorName $errorText continue } $errorCode = if ($answer.PSObject.Properties['error_code']) { [string]$answer.error_code } else { '' } & $answerFor $item $false $false $null $errorName ("FreeIPA $($item.Method) failed ($errorName $errorCode): $errorText" -replace '\s+\)', ')') } } } |