Providers/FreeIPA/Public/New-FreeIPAHost.ps1
|
function New-FreeIPAHost { <# .EXTERNALHELP TestEnvironment-Help.xml .SYNOPSIS Creates the seeded FreeIPA hosts from Data\FreeIPAHosts.csv, in their host groups #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param( [Parameter()] [string[]]$HostName, [Parameter()] [ValidateSet('Core', 'Bulk')] [string[]]$Tier, [Parameter()] [switch]$SkipHostgroups, [Parameter()] [switch]$ShowProgress, [Parameter()] [switch]$PassThru ) $connection = Get-FreeIPAConnection $marker = Get-FreeIPASeedMarker -Connection $connection $csvPath = Join-Path -Path (Get-FreeIPADataPath) -ChildPath 'FreeIPAHosts.csv' $rows = @(Import-Csv -Path $csvPath -Encoding UTF8) if ($Tier) { $rows = @($rows | Where-Object { $Tier -contains $_.Tier }) } if ($HostName) { $rows = @($rows | Where-Object { $HostName -contains $_.Name }) $unknown = @($HostName | Where-Object { $rows.Name -notcontains $_ }) if ($unknown) { throw "No definition in $csvPath for: $($unknown -join ', ')" } } $result = [PSCustomObject]@{ TotalHosts = $rows.Count CreatedHosts = 0 UpdatedHosts = 0 MembershipsApplied = 0 Hosts = @() Errors = @() } $existing = @{} foreach ($entry in (Get-FreeIPASeededObject -Type Hosts -Connection $connection)) { $existing[[string](@($entry.fqdn)[0])] = $entry } $split = { param($value) @([string]$value -split ';' | Where-Object { $_ }) } # A host with an address gets its A record, and its PTR, from FreeIPA on creation - but # only into the seed's own zone, and only if that zone exists. Without it (DNS skipped, # or a realm with no DNS) every host is a record without an address, as before. The # zone is looked up once, at the first host that needs it, so -WhatIf reads nothing. $zone = Get-FreeIPASeedZone -Marker $marker -Connection $connection $zoneReady = $null $zoneIsReady = { if ($null -eq $zoneReady) { $shownZone = Invoke-FreeIPARequest -Method 'dnszone_show' -Arguments $zone.Forward -Connection $connection -IgnoreError 'NotFound' $script:__zoneReady = [bool]($shownZone -and $shownZone.result) Set-Variable -Name zoneReady -Scope 1 -Value $script:__zoneReady Remove-Variable -Name __zoneReady -Scope Script if (-not $zoneReady) { Write-Warning "DNS zone $($zone.Forward) is not there, so the hosts are created without addresses." } } $zoneReady } $hosts = [System.Collections.Generic.List[object]]::new() $created = @{} $membersOf = @{} $managedBy = @{} $index = 0 foreach ($row in $rows) { $fqdn = Resolve-FreeIPASeedName -Key $row.Name -Kind Host -Marker $marker -Connection $connection $index++ Write-TestProgress -Activity 'Seeding hosts' -Status "$index of $($rows.Count): $fqdn" ` -PercentComplete ([int](100 * $index / [Math]::Max(1, $rows.Count))) -ShowProgress:$ShowProgress if (-not $PSCmdlet.ShouldProcess($fqdn, 'Create FreeIPA host')) { continue } try { $options = @{ description = ('{0} {1}' -f $row.Description, $marker.Marker).Trim() userclass = [object[]]@(@($marker.Tag) + @(& $split $row.Class)) } if ($row.OperatingSystem) { $options['nsosversion'] = $row.OperatingSystem } if ($row.Platform) { $options['nshardwareplatform'] = $row.Platform } if ($row.Locality) { $options['l'] = $row.Locality } if ($row.Location) { $options['nshostlocation'] = $row.Location } if ($row.MacAddress) { $options['macaddress'] = [object[]]@(& $split $row.MacAddress) } if ($row.SshPublicKey) { $options['ipasshpubkey'] = [object[]]@(& $split $row.SshPublicKey) } if ($row.AuthIndicator) { $options['krbprincipalauthind'] = [object[]]@(& $split $row.AuthIndicator) } if ($existing.ContainsKey($fqdn)) { $null = Invoke-FreeIPARequest -Method 'host_mod' -Arguments $fqdn -Options $options -Connection $connection -IgnoreError 'EmptyModlist' $result.UpdatedHosts++ Write-Verbose "Updated host $fqdn" } else { # Force, so the realm's DNS is not consulted for a name it does not know. With # an address, FreeIPA writes the A record into the seed's zone and the PTR # into the seed's reverse zone; the realm's own zone is never touched. $options['force'] = $true if ($row.IPAddress -and (& $zoneIsReady)) { $options['ip_address'] = $row.IPAddress } $null = Invoke-FreeIPARequest -Method 'host_add' -Arguments $fqdn -Options $options -Connection $connection $result.CreatedHosts++ Write-Verbose "Created host $fqdn" } $created[$row.Name] = $fqdn if (-not $SkipHostgroups) { foreach ($groupKey in (& $split $row.Hostgroups)) { if (-not $membersOf.ContainsKey($groupKey)) { $membersOf[$groupKey] = [System.Collections.Generic.List[string]]::new() } $membersOf[$groupKey].Add($fqdn) } } if ($row.ManagedBy) { $managedBy[$fqdn] = $row.ManagedBy } $hosts.Add([PSCustomObject]@{ Key = $row.Name Name = $fqdn Class = $row.Class IPAddress = $row.IPAddress Hostgroups = @(& $split $row.Hostgroups) }) } catch { $message = "Failed to create host '$fqdn': $($_.Exception.Message)" $result.Errors += $message Write-Error $message } } Write-TestProgress -Activity 'Seeding hosts' -Completed -ShowProgress:$ShowProgress # Membership, one call per host group, in chunks a single request comfortably carries. foreach ($groupKey in ($membersOf.Keys | Sort-Object)) { $groupName = Resolve-FreeIPASeedName -Key $groupKey -Marker $marker -Connection $connection $members = @($membersOf[$groupKey]) if (-not $PSCmdlet.ShouldProcess($groupName, "Add $($members.Count) member host(s)")) { continue } for ($start = 0; $start -lt $members.Count; $start += 100) { $chunk = @($members[$start..([Math]::Min($start + 99, $members.Count - 1))]) try { $outcome = Invoke-FreeIPARequest -Method 'hostgroup_add_member' -Arguments $groupName -Connection $connection ` -Options @{ host = [object[]]$chunk } $result.MembershipsApplied += [int]$outcome.completed foreach ($failure in @(Get-FreeIPAMemberFailure -Outcome $outcome)) { if ($failure -like '*already a member*') { continue } $message = "Could not add to host group '$groupName': $failure" $result.Errors += $message Write-Error $message } } catch { $message = "Failed to add members to host group '$groupName': $($_.Exception.Message)" $result.Errors += $message Write-Error $message } } } foreach ($fqdn in ($managedBy.Keys | Sort-Object)) { $managerKey = $managedBy[$fqdn] $managerFqdn = Resolve-FreeIPASeedName -Key $managerKey -Kind Host -Marker $marker -Connection $connection if (-not ($created.ContainsKey($managerKey) -or $existing.ContainsKey($managerFqdn))) { Write-Warning "Host '$fqdn' is managed by '$managerKey', which does not exist. Left unmanaged." continue } if (-not $PSCmdlet.ShouldProcess($fqdn, "Managed by $managerFqdn")) { continue } try { $outcome = Invoke-FreeIPARequest -Method 'host_add_managedby' -Arguments $fqdn -Connection $connection ` -Options @{ host = [object[]]@($managerFqdn) } foreach ($failure in @(Get-FreeIPAMemberFailure -Outcome $outcome)) { if ($failure -like '*already a member*') { continue } $message = "Could not set the manager of '$fqdn': $failure" $result.Errors += $message Write-Error $message } } catch { $message = "Failed to set the manager of '$fqdn': $($_.Exception.Message)" $result.Errors += $message Write-Error $message } } $result.Hosts = $hosts.ToArray() Write-Verbose ("Hosts: $($result.CreatedHosts) created, $($result.UpdatedHosts) updated, " + "$($result.MembershipsApplied) memberships, $($result.Errors.Count) problems") if ($PassThru) { return $result } } |