win_hosts to use Ansible.Basic CSharp Util and better diff support (#58600)

* switch win_hosts to use csharp util

* update win_hosts doc to match doc guide

* changed linking format for option values
This commit is contained in:
Micah Hunsberger 2019-07-21 19:08:36 -04:00 committed by Jordan Borean
parent 015119df8c
commit 74598b212e
2 changed files with 59 additions and 92 deletions

View file

@ -3,36 +3,39 @@
# Copyright: (c) 2018, Micah Hunsberger (@mhunsber) # Copyright: (c) 2018, Micah Hunsberger (@mhunsber)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy #AnsibleRequires -CSharpUtil Ansible.Basic
Set-StrictMode -Version 2 Set-StrictMode -Version 2
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$params = Parse-Args -arguments $args -supports_check_mode $true $spec = @{
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false options = @{
$diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false state = @{ type = "str"; choices = "absent", "present"; default = "present" }
aliases = @{ type = "list"; elements = "str" }
canonical_name = @{ type = "str" }
ip_address = @{ type = "str" }
action = @{ type = "str"; choices = "add", "remove", "set"; default = "set" }
}
required_if = @(,@( "state", "present", @("canonical_name", "ip_address")))
supports_check_mode = $true
}
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present" $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$aliases = Get-AnsibleParam -obj $params -name "aliases" -type "list" -failifempty $false
$canonical_name = Get-AnsibleParam -obj $params -name "canonical_name" -type "str" -failifempty ($state -eq 'present') $state = $module.Params.state
$ip_address = Get-AnsibleParam -obj $params -name "ip_address" -type "str" -default "" -failifempty ($state -eq 'present') $aliases = $module.Params.aliases
$action = Get-AnsibleParam -obj $params -name "action" -type "str" -default "set" -validateset "add","remove","set" $canonical_name = $module.Params.canonical_name
$ip_address = $module.Params.ip_address
$action = $module.Params.action
$tmp = [ipaddress]::None $tmp = [ipaddress]::None
if($ip_address -and -not [ipaddress]::TryParse($ip_address, [ref]$tmp)){ if($ip_address -and -not [ipaddress]::TryParse($ip_address, [ref]$tmp)){
Fail-Json -obj @{} -message "win_hosts: Argument ip_address needs to be a valid ip address, but was $ip_address" $module.FailJson("win_hosts: Argument ip_address needs to be a valid ip address, but was $ip_address")
} }
$ip_address_type = $tmp.AddressFamily $ip_address_type = $tmp.AddressFamily
$hosts_file = Get-Item -LiteralPath "$env:SystemRoot\System32\drivers\etc\hosts" $hosts_file = Get-Item -LiteralPath "$env:SystemRoot\System32\drivers\etc\hosts"
$result = @{
changed = $false
diff = @{
prepared = ""
}
}
Function Get-CommentIndex($line) { Function Get-CommentIndex($line) {
$c_index = $line.IndexOf('#') $c_index = $line.IndexOf('#')
if($c_index -lt 0) { if($c_index -lt 0) {
@ -79,30 +82,14 @@ Function Find-HostName($line, $name) {
} }
Function Remove-HostEntry($list, $idx) { Function Remove-HostEntry($list, $idx) {
$result.changed = $true $module.Result.changed = $true
$removed = $false $list.RemoveAt($idx)
if($diff_mode) {
$result.diff.prepared += "`n-[$($list[$idx])]`n"
}
if(-not $check_mode) {
$list.RemoveAt($idx)
$removed = $true
}
return $removed
} }
Function Add-HostEntry($list, $cname, $aliases, $ip) { Function Add-HostEntry($list, $cname, $aliases, $ip) {
$result.changed = $true $module.Result.changed = $true
$line = "$ip $cname $($aliases -join ' ')" $line = "$ip $cname $($aliases -join ' ')"
if($diff_mode) { $list.Add($line) | Out-Null
$result.diff.prepared += "`n+[$line]`n"
}
if(-not $check_mode) {
$list.Add($line) | Out-Null
}
} }
Function Remove-HostnamesFromEntry($list, $idx, $aliases) { Function Remove-HostnamesFromEntry($list, $idx, $aliases) {
@ -115,13 +102,8 @@ Function Remove-HostnamesFromEntry($list, $idx, $aliases) {
$line = $line.Remove($match.Index + 1, $match.Length -1) $line = $line.Remove($match.Index + 1, $match.Length -1)
# was this the last alias? (check for space characters after trimming) # was this the last alias? (check for space characters after trimming)
if($line.Substring(0,(Get-CommentIndex -line $line)).Trim() -inotmatch "\s") { if($line.Substring(0,(Get-CommentIndex -line $line)).Trim() -inotmatch "\s") {
if($diff_mode){ $list.RemoveAt($idx)
$result.diff.prepared += "`n-[$($list[$idx])]`n" $line_removed = $true
}
if(-not $check_mode) {
$list.RemoveAt($idx)
$line_removed = $true
}
# we're done # we're done
return @{ return @{
line_removed = $line_removed line_removed = $line_removed
@ -130,13 +112,8 @@ Function Remove-HostnamesFromEntry($list, $idx, $aliases) {
} }
} }
if($line -ne $list[$idx]){ if($line -ne $list[$idx]){
$result.changed = $true $module.Result.changed = $true
if($diff_mode) { $list[$idx] = $line
$result.diff.prepared += "`n-[$($list[$idx])]`n+[$line]`n"
}
if(-not $check_mode) {
$list[$idx] = $line
}
} }
return @{ return @{
line_removed = $line_removed line_removed = $line_removed
@ -153,19 +130,15 @@ Function Add-AliasesToEntry($list, $idx, $aliases) {
} }
} }
if($line -ne $list[$idx]){ if($line -ne $list[$idx]){
$result.changed = $true $module.Result.changed = $true
if($diff_mode) { $list[$idx] = $line
$result.diff.prepared += "`n-[$($list[$idx])]`n+[$line]`n"
}
if(-not $check_mode) {
$list[$idx] = $line
}
} }
} }
$hosts_lines = New-Object System.Collections.ArrayList $hosts_lines = New-Object System.Collections.ArrayList
Get-Content -LiteralPath $hosts_file.FullName | ForEach-Object { $hosts_lines.Add($_) } | Out-Null Get-Content -LiteralPath $hosts_file.FullName | ForEach-Object { $hosts_lines.Add($_) } | Out-Null
$module.Diff.before = ($hosts_lines -join "`n") + "`n"
if ($state -eq 'absent') { if ($state -eq 'absent') {
# go through and remove canonical_name and ip # go through and remove canonical_name and ip
@ -200,11 +173,9 @@ if($state -eq 'present') {
$aliases_to_remove = @() $aliases_to_remove = @()
if($entry_parts.ip_address -eq $ip_address) { if($entry_parts.ip_address -eq $ip_address) {
if($entry_parts.canonical_name -eq $canonical_name) { if($entry_parts.canonical_name -eq $canonical_name) {
# don't need to worry about line being removed since canonical_name is present
$entry_idx = $idx $entry_idx = $idx
if($action -eq 'set') { if($action -eq 'set') {
# remove the entry's aliases that are not in $aliases
$aliases_to_remove = $entry_parts.aliases | Where-Object { $aliases -notcontains $_ } $aliases_to_remove = $entry_parts.aliases | Where-Object { $aliases -notcontains $_ }
} elseif($action -eq 'remove') { } elseif($action -eq 'remove') {
$aliases_to_remove = $aliases $aliases_to_remove = $aliases
@ -220,41 +191,31 @@ if($state -eq 'present') {
# this is not the ip_address we are looking for # this is not the ip_address we are looking for
if ($ip_address_type -eq $entry_parts.ip_type) { if ($ip_address_type -eq $entry_parts.ip_type) {
if ($entry_parts.canonical_name -eq $canonical_name) { if ($entry_parts.canonical_name -eq $canonical_name) {
# remove the entry Remove-HostEntry -list $hosts_lines -idx $idx
if (Remove-HostEntry -list $hosts_lines -idx $idx){ $idx = $idx - 1
# keep index correct if we removed the line
$idx = $idx - 1
}
if ($action -ne "set") { if ($action -ne "set") {
# keep old aliases intact # keep old aliases intact
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ } $aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ }
} }
} elseif ($action -eq "remove") { } elseif ($action -eq "remove") {
# just remove canonical_name. user may want alias(es) mapped to this canonical name
$aliases_to_remove = $canonical_name $aliases_to_remove = $canonical_name
} elseif ($aliases -contains $entry_parts.canonical_name) { } elseif ($aliases -contains $entry_parts.canonical_name) {
# remove the entry Remove-HostEntry -list $hosts_lines -idx $idx
if (Remove-HostEntry -list $hosts_lines -idx $idx) { $idx = $idx - 1
# keep index correct if we removed the line
$idx = $idx - 1
}
if ($action -eq "add") { if ($action -eq "add") {
# keep old aliases intact # keep old aliases intact
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ } $aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ }
} }
} else { } else {
# ensure canonical_name and aliases removed from this entry
$aliases_to_remove = $aliases + $canonical_name $aliases_to_remove = $aliases + $canonical_name
} }
} else { } else {
# Just ignore if the types don't match.
# TODO: Better ipv6 support. There is odd behavior for when an alias can be used for both ipv6 and ipv4 # TODO: Better ipv6 support. There is odd behavior for when an alias can be used for both ipv6 and ipv4
} }
} }
if($aliases_to_remove) { if($aliases_to_remove) {
if((Remove-HostnamesFromEntry -list $hosts_lines -idx $idx -aliases $aliases_to_remove).line_removed) { if((Remove-HostnamesFromEntry -list $hosts_lines -idx $idx -aliases $aliases_to_remove).line_removed) {
# keep index correct if we removed the line
$idx = $idx - 1 $idx = $idx - 1
} }
} }
@ -263,14 +224,11 @@ if($state -eq 'present') {
} }
if($entry_idx -ge 0) { if($entry_idx -ge 0) {
# we found the entry
$aliases_to_add = @() $aliases_to_add = @()
$entry_parts = Get-HostEntryParts -line $hosts_lines[$entry_idx] $entry_parts = Get-HostEntryParts -line $hosts_lines[$entry_idx]
if($action -eq 'remove') { if($action -eq 'remove') {
# just preserve any previously removed aliases
$aliases_to_add = $aliases_to_keep | Where-Object { $entry_parts.aliases -notcontains $_ } $aliases_to_add = $aliases_to_keep | Where-Object { $entry_parts.aliases -notcontains $_ }
} else { } else {
# we want to add provided aliases and previously removed aliases that are not already in the list
$aliases_to_add = ($aliases + $aliases_to_keep) | Where-Object { $entry_parts.aliases -notcontains $_ } $aliases_to_add = ($aliases + $aliases_to_keep) | Where-Object { $entry_parts.aliases -notcontains $_ }
} }
@ -291,8 +249,9 @@ if($state -eq 'present') {
} }
} }
if( $result.changed -and -not $check_mode ) { $module.Diff.after = ($hosts_lines -join "`n") + "`n"
if( $module.Result.changed -and -not $module.CheckMode ) {
Set-Content -LiteralPath $hosts_file.FullName -Value $hosts_lines Set-Content -LiteralPath $hosts_file.FullName -Value $hosts_lines
} }
Exit-Json $result $module.ExitJson()

View file

@ -18,62 +18,70 @@ version_added: '2.8'
short_description: Manages hosts file entries on Windows. short_description: Manages hosts file entries on Windows.
description: description:
- Manages hosts file entries on Windows. - Manages hosts file entries on Windows.
- Maps IPv4 or IPv6 addresses to canonical names - Maps IPv4 or IPv6 addresses to canonical names.
- Adds, removes, or sets cname records for ip and hostname pairs - Adds, removes, or sets cname records for ip and hostname pairs.
- Modifies %windir%\system32\drivers\etc\hosts. - Modifies %windir%\\system32\\drivers\\etc\\hosts.
options: options:
state: state:
description: description:
- Whether the entry should be present or absent. - Whether the entry should be present or absent.
- If only C(canonical_name) is provided when C(state=absent), then - If only I(canonical_name) is provided when C(state=absent), then
all hosts entries with the canonical name of I(canonical_name) all hosts entries with the canonical name of I(canonical_name)
will be removed. will be removed.
- If only C(ip_address) is provided when C(state=absent), then all - If only I(ip_address) is provided when C(state=absent), then all
hosts entries with the ip address of I(ip_address) will be removed. hosts entries with the ip address of I(ip_address) will be removed.
- If C(ip_address) and C(canonical_name) are both omitted when - If I(ip_address) and I(canonical_name) are both omitted when
C(state=absent), then all hosts entries will be removed. C(state=absent), then all hosts entries will be removed.
choices: choices:
- absent - absent
- present - present
default: present default: present
type: str
canonical_name: canonical_name:
description: description:
- A canonical name for the host entry. - A canonical name for the host entry.
- required for C(state=present). - required for C(state=present).
type: str
ip_address: ip_address:
description: description:
- The ip address for the host entry. - The ip address for the host entry.
- Can be either IPv4 (A record) or IPv6 (AAAA record). - Can be either IPv4 (A record) or IPv6 (AAAA record).
- Required for C(state=present). - Required for C(state=present).
type: str
aliases: aliases:
description: description:
- A list of additional names (cname records) for the host entry. - A list of additional names (cname records) for the host entry.
- Only applicable when C(state=present). - Only applicable when C(state=present).
type: list
action: action:
choices: choices:
- add - add
- remove - remove
- set - set
description: description:
- Controls the behavior of C(aliases). - Controls the behavior of I(aliases).
- Only applicable when C(state=present). - Only applicable when C(state=present).
- If C(add), each alias in I(aliases) will be added to the host entry. - If C(add), each alias in I(aliases) will be added to the host entry.
- If C(set), each alias in I(aliases) will be added to the host entry, - If C(set), each alias in I(aliases) will be added to the host entry,
and other aliases will be removed from the entry. and other aliases will be removed from the entry.
default: set default: set
type: str
author: author:
- Micah Hunsberger (@mhunsber) - Micah Hunsberger (@mhunsber)
notes: notes:
- Each canonical name can only be mapped to one IPv4 and one IPv6 address. - Each canonical name can only be mapped to one IPv4 and one IPv6 address.
If C(canonical_name) is provided with C(state=present) and is found If I(canonical_name) is provided with C(state=present) and is found
to be mapped to another IP address that is the same type as, but unique to be mapped to another IP address that is the same type as, but unique
from C(ip_address), then C(canonical_name) and all C(aliases) will from I(ip_address), then I(canonical_name) and all I(aliases) will
be removed from the entry and added to an entry with the provided IP address. be removed from the entry and added to an entry with the provided IP address.
- Each alias can only be mapped to one canonical name. If C(aliases) is provided - Each alias can only be mapped to one canonical name. If I(aliases) is provided
with C(state=present) and an alias is found to be mapped to another canonical with C(state=present) and an alias is found to be mapped to another canonical
name, then the alias will be removed from the entry and added to or removed name, then the alias will be removed from the entry and either added to or removed
from (based on I(action)) an entry with the provided canonical name. from (depending on I(action)) an entry with the provided canonical name.
- See also M(win_template), M(win_file), M(win_copy) seealso:
- module: win_template
- module: win_file
- module: win_copy
''' '''
EXAMPLES = r''' EXAMPLES = r'''