forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new windows module: win_hosts (ansible#46450)
* Add win_hosts module added win_hosts module for easier manipulation of hosts entries in "%windir%\system32\drivers\etc\hosts" for windows systems * Update win_hosts.py * Add alias support to win_hosts module (#1) * win_hosts supports aliases added support for adding / removing aliases from a host entry, rather than adding a new entry added ability for win_hosts to detect aliases: `192.168.1.1 alias1 alias2 alias3` ``` win_hosts: host_name: alias2 ip_address: 192.168.1.1 ``` will result in `192.168.1.1 alias1 alias3` also includes `replace` and `add` as options for `ip_action` (`replace` is default) for example: ``` 192.168.1.1 my_reused_alias 192.168.1.2 my_reused_alias ``` with ``` win_hosts: host_name: my_reused_alias ip_address: 192.168.1.3 ip_action: add ``` the result will be ``` 192.168.1.1 my_reused_alias 192.168.1.2 my_reused_alias ``` but with `ip_action=replace` the result would be ``` 192.168.1.3 my_reused_alias ``` * fixed metadata version and version added * fix line endings * upload fixed line endings try to upload the file with the fixed line endings * aliases and canonical names are separate entities. added IPv4 and IPv6 validation * only makes changes if "check_mode" is false * improved behavior for duplicate aliases/entries. * adding tests * missing aliases file * fix trailing whitespace and uses explicit paths * Tweak tests to copy and restore original hosts file
- Loading branch information
Showing
7 changed files
with
638 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
#!powershell | ||
|
||
# Copyright: (c) 2018, Micah Hunsberger (@mhunsber) | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
#Requires -Module Ansible.ModuleUtils.Legacy | ||
|
||
Set-StrictMode -Version 2 | ||
$ErrorActionPreference = "Stop" | ||
|
||
$params = Parse-Args -arguments $args -supports_check_mode $true | ||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false | ||
$diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false | ||
|
||
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present" | ||
$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') | ||
$ip_address = Get-AnsibleParam -obj $params -name "ip_address" -type "str" -default "" -failifempty ($state -eq 'present') | ||
$action = Get-AnsibleParam -obj $params -name "action" -type "str" -default "set" -validateset "add","remove","set" | ||
|
||
$tmp = [ipaddress]::None | ||
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" | ||
} | ||
$ip_address_type = $tmp.AddressFamily | ||
|
||
$hosts_file = Get-Item -LiteralPath "$env:SystemRoot\System32\drivers\etc\hosts" | ||
|
||
$result = @{ | ||
changed = $false | ||
diff = @{ | ||
prepared = "" | ||
} | ||
} | ||
|
||
Function Get-CommentIndex($line) { | ||
$c_index = $line.IndexOf('#') | ||
if($c_index -lt 0) { | ||
$c_index = $line.Length | ||
} | ||
return $c_index | ||
} | ||
|
||
Function Get-HostEntryParts($line) { | ||
$success = $true | ||
$c_index = Get-CommentIndex -line $line | ||
$pure_line = $line.Substring(0,$c_index).Trim() | ||
$bits = $pure_line -split "\s+" | ||
if($bits.Length -lt 2){ | ||
return @{ | ||
success = $false | ||
ip_address = "" | ||
ip_type = "" | ||
canonical_name = "" | ||
aliases = @() | ||
} | ||
} | ||
$ip_obj = [ipaddress]::None | ||
if(-not [ipaddress]::TryParse($bits[0], [ref]$ip_obj) ){ | ||
$success = $false | ||
} | ||
$cname = $bits[1] | ||
$als = New-Object string[] ($bits.Length - 2) | ||
[array]::Copy($bits, 2, $als, 0, $als.Length) | ||
return @{ | ||
success = $success | ||
ip_address = $ip_obj.IPAddressToString | ||
ip_type = $ip_obj.AddressFamily | ||
canonical_name = $cname | ||
aliases = $als | ||
} | ||
} | ||
|
||
Function Find-HostName($line, $name) { | ||
$c_idx = Get-CommentIndex -line $line | ||
$re = New-Object regex ("\s+$($name.Replace('.',"\."))(\s|$)", [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) | ||
$match = $re.Match($line, 0, $c_idx) | ||
return $match | ||
} | ||
|
||
Function Remove-HostEntry($list, $idx) { | ||
$result.changed = $true | ||
$removed = $false | ||
|
||
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) { | ||
$result.changed = $true | ||
$line = "$ip $cname $($aliases -join ' ')" | ||
if($diff_mode) { | ||
$result.diff.prepared += "`n+[$line]`n" | ||
} | ||
if(-not $check_mode) { | ||
$list.Add($line) | Out-Null | ||
} | ||
} | ||
|
||
Function Remove-HostnamesFromEntry($list, $idx, $aliases) { | ||
$line = $list[$idx] | ||
$line_removed = $false | ||
|
||
foreach($name in $aliases){ | ||
$match = Find-HostName -line $line -name $name | ||
if($match.Success){ | ||
$line = $line.Remove($match.Index + 1, $match.Length -1) | ||
# was this the last alias? (check for space characters after trimming) | ||
if($line.Substring(0,(Get-CommentIndex -line $line)).Trim() -inotmatch "\s") { | ||
if($diff_mode){ | ||
$result.diff.prepared += "`n-[$($list[$idx])]`n" | ||
} | ||
if(-not $check_mode) { | ||
$list.RemoveAt($idx) | ||
$line_removed = $true | ||
} | ||
# we're done | ||
return @{ | ||
line_removed = $line_removed | ||
} | ||
} | ||
} | ||
} | ||
if($line -ne $list[$idx]){ | ||
$result.changed = $true | ||
if($diff_mode) { | ||
$result.diff.prepared += "`n-[$($list[$idx])]`n+[$line]`n" | ||
} | ||
if(-not $check_mode) { | ||
$list[$idx] = $line | ||
} | ||
} | ||
return @{ | ||
line_removed = $line_removed | ||
} | ||
} | ||
|
||
Function Add-AliasesToEntry($list, $idx, $aliases) { | ||
$line = $list[$idx] | ||
foreach($name in $aliases){ | ||
$match = Find-HostName -line $line -name $name | ||
if(-not $match.Success) { | ||
# just add the alias before the comment | ||
$line = $line.Insert((Get-CommentIndex -line $line), " $name ") | ||
} | ||
} | ||
if($line -ne $list[$idx]){ | ||
$result.changed = $true | ||
if($diff_mode) { | ||
$result.diff.prepared += "`n-[$($list[$idx])]`n+[$line]`n" | ||
} | ||
if(-not $check_mode) { | ||
$list[$idx] = $line | ||
} | ||
} | ||
} | ||
|
||
$hosts_lines = New-Object System.Collections.ArrayList | ||
|
||
Get-Content -LiteralPath $hosts_file.FullName | ForEach-Object { $hosts_lines.Add($_) } | Out-Null | ||
|
||
if ($state -eq 'absent') { | ||
# go through and remove canonical_name and ip | ||
for($idx = 0; $idx -lt $hosts_lines.Count; $idx++) { | ||
$entry = $hosts_lines[$idx] | ||
# skip comment lines | ||
if(-not $entry.Trim().StartsWith('#')) { | ||
$entry_parts = Get-HostEntryParts -line $entry | ||
if($entry_parts.success) { | ||
if(-not $ip_address -or $entry_parts.ip_address -eq $ip_address) { | ||
if(-not $canonical_name -or $entry_parts.canonical_name -eq $canonical_name) { | ||
if(Remove-HostEntry -list $hosts_lines -idx $idx){ | ||
# keep index correct if we removed the line | ||
$idx = $idx - 1 | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if($state -eq 'present') { | ||
$entry_idx = -1 | ||
$aliases_to_keep = @() | ||
# go through lines, find the entry and determine what to remove based on action | ||
for($idx = 0; $idx -lt $hosts_lines.Count; $idx++) { | ||
$entry = $hosts_lines[$idx] | ||
# skip comment lines | ||
if(-not $entry.Trim().StartsWith('#')) { | ||
$entry_parts = Get-HostEntryParts -line $entry | ||
if($entry_parts.success) { | ||
$aliases_to_remove = @() | ||
if($entry_parts.ip_address -eq $ip_address) { | ||
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 | ||
|
||
if($action -eq 'set') { | ||
# remove the entry's aliases that are not in $aliases | ||
$aliases_to_remove = $entry_parts.aliases | Where-Object { $aliases -notcontains $_ } | ||
} elseif($action -eq 'remove') { | ||
$aliases_to_remove = $aliases | ||
} | ||
} else { | ||
# this is the right ip_address, but not the cname we were looking for. | ||
# we need to make sure none of aliases or canonical_name exist for this entry | ||
# since the given canonical_name should be an A/AAAA record, | ||
# and aliases should be cname records for the canonical_name. | ||
$aliases_to_remove = $aliases + $canonical_name | ||
} | ||
} else { | ||
# this is not the ip_address we are looking for | ||
if ($ip_address_type -eq $entry_parts.ip_type) { | ||
if ($entry_parts.canonical_name -eq $canonical_name) { | ||
# remove the entry | ||
if (Remove-HostEntry -list $hosts_lines -idx $idx){ | ||
# keep index correct if we removed the line | ||
$idx = $idx - 1 | ||
} | ||
if ($action -ne "set") { | ||
# keep old aliases intact | ||
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ } | ||
} | ||
} elseif ($action -eq "remove") { | ||
# just remove canonical_name. user may want alias(es) mapped to this canonical name | ||
$aliases_to_remove = $canonical_name | ||
} elseif ($aliases -contains $entry_parts.canonical_name) { | ||
# remove the entry | ||
if (Remove-HostEntry -list $hosts_lines -idx $idx) { | ||
# keep index correct if we removed the line | ||
$idx = $idx - 1 | ||
} | ||
if ($action -eq "add") { | ||
# keep old aliases intact | ||
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ } | ||
} | ||
} else { | ||
# ensure canonical_name and aliases removed from this entry | ||
$aliases_to_remove = $aliases + $canonical_name | ||
} | ||
} 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 | ||
} | ||
} | ||
|
||
if($aliases_to_remove) { | ||
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 | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if($entry_idx -ge 0) { | ||
# we found the entry | ||
$aliases_to_add = @() | ||
$entry_parts = Get-HostEntryParts -line $hosts_lines[$entry_idx] | ||
if($action -eq 'remove') { | ||
# just preserve any previously removed aliases | ||
$aliases_to_add = $aliases_to_keep | Where-Object { $entry_parts.aliases -notcontains $_ } | ||
} 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 $_ } | ||
} | ||
|
||
if($aliases_to_add) { | ||
Add-AliasesToEntry -list $hosts_lines -idx $entry_idx -aliases $aliases_to_add | ||
} | ||
} else { | ||
# add the entry at the end | ||
if($action -eq 'remove') { | ||
if($aliases_to_keep) { | ||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name -aliases $aliases_to_keep | ||
} else { | ||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name | ||
} | ||
} else { | ||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name -aliases ($aliases + $aliases_to_keep) | ||
} | ||
} | ||
} | ||
|
||
if( $result.changed -and -not $check_mode ) { | ||
Set-Content -LiteralPath $hosts_file.FullName -Value $hosts_lines | ||
} | ||
|
||
Exit-Json $result |
Oops, something went wrong.